[concurrency-interest] Default Stack Size on 64-bit JVMs

Nathan Reynolds nathan.reynolds at oracle.com
Wed Dec 2 12:40:01 EST 2015


 > In the mean time, there is some sentiment that default thread stack 
sizes on 64bit JVMs should be expanded keep up with the times. On 32bit 
systems the 1MB limit is completely defensible. But expanding to say 
64MB on 64bit systems would reduce practical encounters with SOE in 
these kinds of constructions by a factor of 64 or so.

I've had this debate on a C++ application as well.  The debate started 
because a lot of people were working on stack overflow issues.  Each 
time I would tell them to first fix the infinite recursion issue.  They 
would try to take the easy route and would set the stack size to 
something very large and hope the problem would go away.  It didn't.  
After the recursion bugs were eliminated, we got to the point where the 
application really did need to do that much recursion and the recursion 
was acceptably finite.  We then went with larger stack sizes and chose a 
sane default size.  After changing the default size, we bumped into a 
couple of problems.  Nothing that says we shouldn't increase the stack 
size but maybe it should be documented.

First, the larger stack size eats up address space.  As far as resources 
go, it is very cheap at this point in time on a 64-bit machine.  25 
years ago we said the same thing about virtual address space on 32-bit 
machines.  We've got about 60 years before we will start exhausting the 
64-bit address space.  So, let's ignore that for now.  Today's problem 
comes when doing sizing calculations. People will see the address space 
size and figure that is what is needed for the process in terms of RAM.  
Then the entire sizing calculation goes haywire and machines end up over 
sized.  As a cloud provider, I don't mind people spending more money on 
RAM they aren't using.  :)

Second, it can take a while to overflow the 1 MB stack.  I've had a 
couple of situations where it took several minutes in Java to cause a 
stack overflow.  Obviously, the program isn't just doing recursive 
calls.  There is a computation or GC happening all the way down the 
stack.  A 64 MB stack would take a couple of hours.  This means the 
thread is busy for a long time and other operations will *hopefully* 
timeout waiting for it.

I think a smart approach would be to examine code (open source?) or 
production deployments and see how often the stack sizing parameter is 
used and what value it is set to.  This will give us a good idea if the 
default stack size needs to be adjusted and what is a good stack size to 
use.

-Nathan

On 12/2/2015 5:52 AM, Doug Lea wrote:
> On 12/02/2015 05:34 AM, Millies, Sebastian wrote:
>> I have thought of a way of doing tail-recursion elimination with 
>> CompletableFuture. It works, but I do not understand why. There must 
>> be some inter-thread communication going on about which I have not 
>> been able to find documentation. Perhaps someone here can enlighten me.
>>
>> public static BigInteger fibTailRecursive(int n) {
>>      return n <= 2 ? ONE : fibTailRecursiveAux(n, ZERO, ONE);
>> }
>>
>> private static BigInteger fibTailRecursiveAux(int n, BigInteger a, 
>> BigInteger b) {
>>      return n <= 0 ? a : fibTailRecursiveAux(n - 1, b, a.add(b));
>> }
>>
>> And here is the "optimized" version with TRE,
>>
>> public static BigInteger fibCF(int n) {
>>          return n <= 2 ? ONE : fibCF(n, ZERO, ONE).join();
>> }
>>
>> private static CompletableFuture<BigInteger> fibCF(int n, BigInteger 
>> a, BigInteger b) {
>>          return n <= 0 ? terminate(a) : tailcall(() -> fibCF(n - 1, 
>> b, a.add(b)));
>> }
>>
>> Finally, here are my problematic implementations of terminate() and 
>> tailcall():
>>
>> public static <T> CompletableFuture<T> terminate(T t) {
>>          return completedFuture(t);
>> }
>>
>> public static <T> CompletableFuture<T> 
>> tailcall(Supplier<CompletableFuture<T>> s) {
>>          return CF.thenComposeAsync(_x -> s.get(), THREAD);
>> }
>
> (Using default Async would work at least as well.
> Aside: using direct executors (run in the same thread as caller)
> is almost never a good idea with CompletableFutures.)
>
>>
>> private static final CompletableFuture<?> CF = completedFuture(null);
>> private static final Executor THREAD = 
>> Executors.newSingleThreadExecutor(new 
>> ThreadFactoryBuilder().setDaemon(true).build());
>>
>
> This is a variation of the issues brought up by Chris Purcell a few 
> months
> ago. In async mode, CF generally avoids StackOverflowError (SOE),
> because threads help clear out dependent tasks, which they do anyway to
> maximize parallelism.
>
> In non-async mode, you can still get SOE because java compilers do not
> loopify tail recursion. It would be possible to better avoid this
> kind of non-async SOE by thread-local trampolining at the expense
> of reducing parallelism, so we don't want to do it.
>
> It's still an open question whether we can find some scheme that
> combines both advantages.
>
> In the mean time, there is some sentiment that default thread stack
> sizes on 64bit JVMs should be expanded keep up with the times. On
> 32bit systems the 1MB limit is completely defensible. But expanding
> to say 64MB on 64bit systems would reduce practical encounters with
> SOE in these kinds of constructions by a factor of 64 or so.
>
> -Doug
>
>
> _______________________________________________
> Concurrency-interest mailing list
> Concurrency-interest at cs.oswego.edu
> http://cs.oswego.edu/mailman/listinfo/concurrency-interest
>


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://cs.oswego.edu/pipermail/concurrency-interest/attachments/20151202/62aa4b49/attachment.html>


More information about the Concurrency-interest mailing list