Java Threads and the -Xss Stack Size argument
At the office we’re having a memory-management issue on a very large multi-threaded application. Basically, we’re throwing so many threads that we’re running out of native heap space. We don’t seem to be having Java heap problems, the issue is within the 2GB process space limitation on a 32bit Windows machine. So, after considerable research, I’ve stumbled upon what I think might be the solution.
It seems that when a Java process spawns a new thread, it allocates a section of memory called the Thread Stack in the native process space. It’s basically setting aside resources for the thread to do whatever it needs to do. This is outside of the Java heap, but within the JVM runtime process, and therefore inside the native heap provided by the JVM’s host OS.
Internet sources vary on the default size of the thread stack, and it may vary from OS to OS, but I’ve seen people claiming the default thread stack size is 8k up to 32k per thread, regardless of the actual stack size requirements of the thread in question. So, depending on the complexity and size of the code in the thread, there may be a ton of resources being allocated that are just going to waste.
Here’s the little test app I ginned up to check this stuff out.
net/willyray/tests/threadage/MyThread.java
package net.willyray.tests.threadage;
public class MyThread implements Runnable {
private int threadNumber;
public MyThread(int threadNumber) {
super();
this.threadNumber = threadNumber;
}
//each thread will loop 100 times, printing out
//it's number and how far it's gotten in the loop.
public void run() {
for (int i=1; i<=100; i++){
//this whole sync-ed section executes
//before the thread sleeps
synchronized("synckey"){
System.out.print(this.threadNumber);
System.out.print(":");
System.out.println(i);
}
}
}
}
net/willyray/tests/threadage/ThreadDriver.java
package net.willyray.tests.threadage;
public class ThreadDriver {
//create a certain number of threads and start them running
public ThreadDriver(Integer numThreads){
for (int i = 0; i<numThreads.intValue() ; i++){
MyThread mt = new MyThread(i);
Thread t = new Thread(mt);
t.start();
}
}
public static void main(String[] args) {
if (args.length != 1) {
System.out.println("Usage: $gt;java -Xss
ThreadDriver “);
} else {
ThreadDriver td = new ThreadDriver(Integer.valueOf(args[0]));
}
}
}
If I run ThreadDriver from the command line like so:
>java net.willyray.tests.threadage.ThreadDriver 10000
it creates 10,000 threads of my little counter class, and each starts counting, vying for processor time, and slowly making their respective ways up to a count of 100. I watch my Task Manager->Processes window, and I can see the Java process immediately skyrocket up to about 150,000k worth of memory usage. This is the highest on my list, topping both Eclipse and Firefox 2.
If I run it like this:
>java -Xss1k net.willyray.tests.threadage.ThreadDriver 10000
The -Xss1k argument tells the JVM to set the default stack size to 1024 bytes. I still fire the same exact code, doing the same exact work, but now my process runs in around 50,000k of memory.
Programming lesson: Sweat the small stuff, don’t trust the documentation, and take all questions immediately to the internet.
This Java memory management stuff is a real rabbit hole, but I find it kind of interesting. It makes a very perverse sort of sense when you really get down into the nitty-gritty of it.
Outstanding questions from this test rig: Are there performance hits from managing the threads in the smaller stacks? I can see where with less room to work with it might be a burden to manage the stack… still, at some point I’ll slap some kind of rudimentary timer on the thing and see what the results are.
Until next time…
/w
Wednesday, May 28th, 2008