There are two main places that things can be stored in Java. The first is the Heap, that's used for dynamically allocated objects. new
.
In addition each running thread gets its own stack, and it gets an amount of memory allocated to that stack.
When you call a method then data is pushed into the stack to record the method call, the parameters being passed in, and any local variables being allocated. A method with five local variables and three parameters will use more stack space than a void doStuff()
method with no local variables will.
The main advantages of the stack are that there is no memory fragmentation, everything for one method call is allocated on the top of the stack, and that returning from methods is easy. To return from a method you just unwind the stack back to the previous method, set any value needed for the return value and you are done.
Because the stack is a fixed size per thread, (note that the Java Spec does not require a fixed size, but most JVM implementations at the time of writing use a fixed size) and because space on the stack is needed whenever you make a method call hopefully it should now be clear why it can run out and what can cause it to run out. There isn't a fixed number of method calls, there isn't anything specific about recursion, you get the exception which you try to call a method and there isn't enough memory.
Of course the size of stacks is set high enough that it is highly unlikely to happen in regular code. In recursive code though it can be quite easy to recurse to huge depths, and at that point you start running into this error.