Odi's astoundingly incomplete notes

New entries | Code

Java stack size myths

How Java uses the stack is basically undocumented. So we developers are left with finding out ourselves. Today I wrote some interesting test code, that delivers surprising results. For my tests I used Sun JDK 1.5.0_12 under RedHat Enterprise Linux 3.

First I wanted to know the stack size that the VM chooses for threads. I found that the ulimit -s parameter has no direct influence on the stack size chosen by the VM. The default is apparently 512kb, and can be changed freely with the -Xss and -XX:ThreadStackSize parameters. But I could not find a difference in behaviour between those parameters. They appear to do the same thing. Further I found that this Linux machine is able to create new threads at a rate of about 5000 per second.

I performed these tests by creating new threads and setting them asleep in a blocking wait call immediately. I kept creating threads until the VM ran out of memory. With 512k stacks the number of threads was around 3700, for 256k stacks around 7300, for 128k around 13700. That leads to the following memory consumption by the stacks:
 3700 x 512kB = 1850MB
 7300 x 256kB = 1825MB
13700 x 128kB = 1713MB
Of course a 32-bit process is limited to 4GB of address space (minus 1 or 2 GB for the kernel). So it is only natural that the memory is close to these 2GB minus the heap size. (Note that Stack is never allocated from the heap.)

Next I tested how deep I could call into a recursive method until I get stack overflows. I did this by modifying the initial program, so that each thread would recurse into a method until it hit stack overflow, then fall asleep. The thread recorded the maximum recursion depth. That's where I got really weird results. Especially with the server VM the maximum depth varies over a wide range between 1750 and 5700 calls (128k stacks)! That's far from constant, which would have been my first guess. With the client VM the number is generally lower but doesn't vary that much: between 1100 and 1650.
 Also the maximum depth seems to be lower at the beginning of the test and increases toward the end.

If someone wants to repeat my tests, here is the test code that I was using. I am very interested to get further insights on how exactly stack works in Java.

posted on 2007-06-14 18:51 UTC in Code | 6 comments | permalink
You would expect the allowed stack size to vary to some extent, based on the different optimisation paths that take place as the code is compiled. I can easily imagine that the stack overhead would be different between the interpreted and native code paths for example.

On java 6 the test would likely not even finish, since it does tail recursion optimisation, meaning that your f(i) call would not take any space in the stack.

I've been experimenting with some thread capacity issues - trying to raise the number of threads we can handle in our app. Doing the math on your example raised a question: 13,700 threads x 128k stacks > 1.7G. You example code lists using 1G heap size (min and max). If the stacks are allocated from the heap, then this can't work because the heap would have been expended before 13700 threads. If they are allocated outside the heap, this implies the JVM is able to access 2.7G of ram. Based on our experiments, this is not happening either.

What am I missing?
Chris, I have added a small paragraph on the maths. Stack is not allocated from the heap. And in those tests the heap was (apparently) not 1GB but rather 128 or 256MB.
cat /proc/meminfo
total: used: free: shared: buffers: cached:
Mem: 4189683712 1384755200 2804928512 0 150441984 656486400
Swap: 2146754560 0 2146754560
MemTotal: 4091488 kB
MemFree: 2739188 kB
MemShared: 0 kB
Buffers: 146916 kB
Cached: 641100 kB
SwapCached: 0 kB
Active: 734156 kB
ActiveAnon: 428588 kB
ActiveCache: 305568 kB
Inact_dirty: 371072 kB
Inact_laundry: 111372 kB
Inact_clean: 0 kB
Inact_target: 243320 kB
HighTotal: 3276528 kB
HighFree: 2193932 kB
LowTotal: 814960 kB
LowFree: 545256 kB
SwapTotal: 2096440 kB
SwapFree: 2096440 kB
CommitLimit: 4142184 kB
Committed_AS: 3304968 kB
HugePages_Total: 0
HugePages_Free: 0
Hugepagesize: 2048 kB

Max. threads: 3329
Max. recursions: 26832
Min. recursions: 8304
Threads per second: 195.8593

I ran it several times, the min and max are consistent.

Where the GC print outs are coming from?

Lowering the -XX:StackShadowPages might give better results for your tests. See also http://www.oracle.com/technetwork/java/javase/crashes-137240.html#gbyzz