What Is the Default Stack Size, Can It Grow, How Does It Work with Garbage Collection

What is the default stack size, can it grow, how does it work with garbage collection?

How much a stack can grow?

You can use a VM option named ss to adjust the maximum stack size. A VM option is usually passed using -X{option}. So you can use java -Xss1M to set the maximum of stack size to 1M.

Each thread has at least one stack. Some Java Virtual Machines (JVM) put Java stack (Java method calls) and native stack (Native method calls in VM) into one stack, and perform stack unwinding using a "Managed to Native Frame", known as M2nFrame. Some JVMs keep two stacks separately. The Xss set the size of the Java Stack in most cases.

For many JVMs, they put different default values for stack size on different platforms.



Can we limit this growth?

When a method call occurs, a new stack frame will be created on the stack of that thread. The stack will contain local variables, parameters, return address, etc. In Java, you can never put an object on stack, only object reference can be stored on stack. Since array is also an object in Java, arrays are also not stored on stack. So, if you reduce the amount of your local primitive variables, parameters by grouping them into objects, you can reduce the space on stack. Actually, the fact that we cannot explicitly put objects on Java stack affects the performance some time (cache miss).



Does stack has some default minimum value or default maximum value?

As I said before, different VMs are different, and may change over versions. See here.



How does garbage collection work on stack?

Garbage collections in Java is a hot topic. Garbage collection aims to collect unreachable objects in the heap. So that needs a definition of 'reachable.' Everything on the stack constitutes part of the root set references in GC. Everything that is reachable from every stack of every thread should be considered as live. There are some other root set references, like Thread objects and some class objects.

This is only a very vague use of stack on GC. Currently most JVMs are using a generational GC. This article gives brief introduction about Java GC. And recently I read a very good article talking about the GC on .NET platform. The GC on Oracle JVM is quite similar so I think that might also help you.

Java thread stack memory occupation

Thread stacks are allocated in the native memory (not in the Java heap), the default stack size is between 256 k and 1 M (depending on OS and whether the JVM is 32 or 64-bit), you can control it with the -Xss JVM option. If you run out of stack memory, you get the "java.lang.OutOfMemoryError: unable to create new native thread" error.

If you want to track the stack memory usage on Windows on the OS level, use VMMap instead of Task Manager, because it shows you the stack memory.

You can also track the stack memory usage within Java if you have a recent JDK with -XX:NativeMemoryTracking and jcmd, the details are here.

I don't know what is wrong with your test, I suggest to put a Thread.sleep in your code to make sure that all threads are running.

What is thread stack size option(-Xss) given to jvm? Why does it have a limit of atleast 68k in a windows pc?

-Xss allows to configure Java thread stack size according to application needs:

  • larger stack size is for an application that uses recursive algorithms or otherwise deep method calls;
  • smaller stack size is for an application that runs thousands of threads - you may want to save memory occupied by thread stacks.

Bear in mind that HotSpot JVM also utilizes the same Java thread stack for the native methods and JVM runtime calls (e.g. class loading). This means Java thread stack is used not only for Java methods, but JVM should reserve some stack pages for its own operation as well.

The minimum required stack size is calculated by the formula:

(StackYellowPages + StackRedPages + StackShadowPages + 2*BytesPerWord + 1) * 4096

where

  • StackYellowPages and StackRedPages are required to detect and handle StackOverflowError;
  • StackShadowPages are reserved for native methods;
  • 2*4 (32-bit JVM) or 2*8 (64-bit JVM) is for VM runtime functions;
  • extra 1 is for JIT compiler recursion in main thread;
  • 4096 is the default page size.

E.g. for 32-bit Windows JVM minimum stack size = (3 + 1 + 4 + 2*4 + 1) * 4K = 68K

BTW, you may reduce the minumum required stack size using these JVM options: (not recommended!)

-XX:StackYellowPages=1 -XX:StackRedPages=1 -XX:StackShadowPages=1

What and where are the stack and heap?

The stack is the memory set aside as scratch space for a thread of execution. When a function is called, a block is reserved on the top of the stack for local variables and some bookkeeping data. When that function returns, the block becomes unused and can be used the next time a function is called. The stack is always reserved in a LIFO (last in first out) order; the most recently reserved block is always the next block to be freed. This makes it really simple to keep track of the stack; freeing a block from the stack is nothing more than adjusting one pointer.

The heap is memory set aside for dynamic allocation. Unlike the stack, there's no enforced pattern to the allocation and deallocation of blocks from the heap; you can allocate a block at any time and free it at any time. This makes it much more complex to keep track of which parts of the heap are allocated or free at any given time; there are many custom heap allocators available to tune heap performance for different usage patterns.

Each thread gets a stack, while there's typically only one heap for the application (although it isn't uncommon to have multiple heaps for different types of allocation).

To answer your questions directly:

To what extent are they controlled by the OS or language runtime?

The OS allocates the stack for each system-level thread when the thread is created. Typically the OS is called by the language runtime to allocate the heap for the application.

What is their scope?

The stack is attached to a thread, so when the thread exits the stack is reclaimed. The heap is typically allocated at application startup by the runtime, and is reclaimed when the application (technically process) exits.

What determines the size of each of them?

The size of the stack is set when a thread is created. The size of the heap is set on application startup, but can grow as space is needed (the allocator requests more memory from the operating system).

What makes one faster?

The stack is faster because the access pattern makes it trivial to allocate and deallocate memory from it (a pointer/integer is simply incremented or decremented), while the heap has much more complex bookkeeping involved in an allocation or deallocation. Also, each byte in the stack tends to be reused very frequently which means it tends to be mapped to the processor's cache, making it very fast. Another performance hit for the heap is that the heap, being mostly a global resource, typically has to be multi-threading safe, i.e. each allocation and deallocation needs to be - typically - synchronized with "all" other heap accesses in the program.

A clear demonstration:


Image source: vikashazrati.wordpress.com

How much stack does a recursion use (removing node from list)?

How much stack does a recursion use (removing node from list)?

That's a difficult question to answer without profiling the code or having specifics on a particular JVM. That's because the Java Virtual Machine Specification (JVMS) leaves many details to the implementor.

JVMS Chapter 2: Implementation details that are not part of the
Java Virtual Machine's specification would unnecessarily constrain the
creativity of implementors. For example, the memory layout of run-time
data areas, the garbage-collection algorithm used, and any internal
optimization of the Java Virtual Machine instructions (for example,
translating them into machine code) are left to the discretion of the
implementor.

Part of those details are the stack and stack frame implementations. And since it seems you're actually asking about the relation between the stack created by the recursive remove and the list the algorithm is processing, we have to consider that the stack frame may not even be as big on the stack as you think (or propose in your question).

JVMS (§2.5.2): Because the Java Virtual Machine stack is never
manipulated directly except to push and pop frames, frames may be
heap allocated.

That means there could just be a single reference put onto the stack for each stack frame. If so, that would make the relation between the stack and the list it's processing truly insignificant for the example you provide. Let's look at some numbers:

According to Algorithms (Sedgewick, Wayne):

  • An Integer is 24 bytes

    Overhead + instance variable (int) + padding

    = 16 + 4 + 4 = 24 bytes

  • A non-static recursively defined Node class nested in the associated
    List is 40 bytes

    Overhead + references + additional overhead (reference to List class)

    = 16 + 8 + 8 + 8 = 40 bytes

Therefore, we can see there's 64 bytes per entry in the list. So, if there's an implementation where the frames are heap allocated the relation between the stack and the list you're processing would be 8/64 = 1/8 bytes.

It's more difficult to determine the size of the stack when the stack frames are allocated on the stack. In fact, we need further implementation details to determine it.

JVMS (§2.6. Frames):
The sizes of the local variable array and the operand stack are
determined at compile-time and are supplied along with the code for
the method associated with the frame (§4.7.3). Thus the size of the
frame data structure depends only on the implementation of the Java
Virtual Machine, and the memory for these structures can be allocated
simultaneously on method invocation.

Even so, I'd say, given your example, it's not likely the stack will grow at, or near, 1:1 in relation to a list (talking about total size in memory here). Sure, you could present a specific problem that would validate your argument but it would likely be trivial and likely not with the example you've provided.

Where can I find default -Xss (stack size) value for Oracle JVM?

What a long, strange trip it's been getting to an answer to this question, first posted in 2011--and getting all kinds of answers in the years since. Some proposed using the -XX:+PrintFlagsFinal JVM argument, which may be best for most, but it did not help for Windows (always reports 0). And some folks shared various resources (for various JVMs and versions), some of which did try to help answer this question but often did not, or did not clarify for those who may be running an Oracle JVM on Windows.

Here's at least a bit more clarity: with Java 11 being (in 2021) the current latest LTS (long-term support release) version of the Oracle JVM, I've found a document for that current version which DOES list the specific defaults for XSS (aka ThreadStackSize), and for different OS's. It's at https://docs.oracle.com/en/java/javase/11/tools/java.html#GUID-3B1CE181-CD30-4178-9602-230B800D4FAE. Here's what it reports (in case that link breaks in the future):

  • Linux/x64 (64-bit): 1024 KB
  • macOS (64-bit): 1024 KB
  • Oracle Solaris/x64 (64-bit): 1024 KB
  • Windows: The default value depends on virtual memory

Sadly, Windows folks are still left to wonder: what could that mean, "depends on virtual memory"? I suppose it means the virtual memory of Windows itself of course, but even then how do we translate that into what we should expect the actual stack size for a jvm thread to be?

And back to my original question: I'd wanted to know what the default was, because so often someone might suggest the solution to some problem is to change that -XSS jvm arg (to some value they would propose). But how can we know if we are making it larger or smaller than the default? Depending on the problem being solved, that can be vital to know!

After 10 years, I guess it's a mythical quest that may not be concluded. But who knows, maybe someone seeing this now or in the future may ride to the rescue with new info. Or perhaps Java 17 (the expected next LTS version) may change things. (To be clear, there is no version of that page I shared, for Java 13 or above, as I write.)

If nothing else, I leave this for posterity, to at least help someone else facing this question to know that they're not crazy in finding it so hard to get a straight answer (especially about the default XSS value for Windows, and whether changing it would be raising or lowering the size relative to the default).



Related Topics



Leave a reply



Submit