avoid running out of OS resources due to zombie thread accumulation

The bug here is that when a thread exits and becomes a "zombie", the
OS resources associated with it are not necessarily released until we
actually join and dispose of that thread.  Since that only happens
during garbage collection, and collection normally only happens in
response to heap memory pressure, there's no guarantee that we'll GC
frequently enough to clean up zombies promptly and avoid running out
of resources.

The solution is to force a GC whenever we start a new thread and there
are at least N zombies waiting to be disposed, where N=16 for now.
This commit is contained in:
Joel Dice 2012-02-03 12:00:02 -07:00
parent b45528203d
commit c3256c2874
3 changed files with 38 additions and 2 deletions

View File

@ -3231,12 +3231,20 @@ EXPORT(JVM_DisableCompiler)(Thread*, jclass)
// ignore
}
uint64_t
jvmStartThread(Thread* t, uintptr_t* arguments)
{
jobject thread = reinterpret_cast<jobject>(arguments[0]);
return startThread(t, *thread) != 0;
}
extern "C" JNIEXPORT void JNICALL
EXPORT(JVM_StartThread)(Thread* t, jobject thread)
{
ENTER(t, Thread::ActiveState);
uintptr_t arguments[] = { reinterpret_cast<uintptr_t>(thread) };
startThread(t, *thread);
run(t, jvmStartThread, arguments);
}
extern "C" JNIEXPORT void JNICALL

View File

@ -229,6 +229,9 @@ turnOffTheLights(Thread* t)
visitAll(t, t->m->rootThread, disposeNoRemove);
System* s = m->system;
expect(s, m->threadCount == 0);
Heap* h = m->heap;
Processor* p = m->processor;
Classpath* c = m->classpath;
@ -2435,6 +2438,7 @@ Machine::Machine(System* system, Heap* heap, Finder* bootFinder,
propertyCount(propertyCount),
arguments(arguments),
argumentCount(argumentCount),
threadCount(0),
activeCount(0),
liveCount(0),
daemonCount(0),
@ -2653,6 +2657,8 @@ Thread::dispose()
}
}
-- m->threadCount;
m->heap->free(defaultHeap, ThreadHeapSizeInBytes);
m->processor->dispose(this);
@ -2864,6 +2870,7 @@ enter(Thread* t, Thread::State s)
INCREMENT(&(t->m->activeCount), 1);
if (t->state == Thread::NoState) {
++ t->m->liveCount;
++ t->m->threadCount;
}
t->state = s;
} break;

View File

@ -120,6 +120,10 @@ const unsigned ThreadHeapPoolSize = 64;
const unsigned FixedFootprintThresholdInBytes
= ThreadHeapPoolSize * ThreadHeapSizeInBytes;
// number of zombie threads which may accumulate before we force a GC
// to clean them up:
const unsigned ZombieCollectionThreshold = 16;
enum FieldCode {
VoidField,
ByteField,
@ -1299,6 +1303,7 @@ class Machine {
unsigned propertyCount;
const char** arguments;
unsigned argumentCount;
unsigned threadCount;
unsigned activeCount;
unsigned liveCount;
unsigned daemonCount;
@ -1994,6 +1999,7 @@ addThread(Thread* t, Thread* p)
assert(t, p->state == Thread::NoState);
p->state = Thread::IdleState;
++ t->m->threadCount;
++ t->m->liveCount;
p->peer = p->parent->child;
@ -2012,6 +2018,7 @@ removeThread(Thread* t, Thread* p)
assert(t, p->state == Thread::IdleState);
-- t->m->liveCount;
-- t->m->threadCount;
t->m->stateLock->notifyAll(t->systemThread);
@ -2020,11 +2027,25 @@ removeThread(Thread* t, Thread* p)
if (p->javaThread) {
threadPeer(t, p->javaThread) = 0;
}
p->dispose();
}
inline Thread*
startThread(Thread* t, object javaThread)
{
{ PROTECT(t, javaThread);
stress(t);
ACQUIRE_RAW(t, t->m->stateLock);
if (t->m->threadCount > t->m->liveCount + ZombieCollectionThreshold) {
fprintf(stderr, "hit zombie collection threshold\n");
collect(t, Heap::MinorCollection);
}
}
Thread* p = t->m->processor->makeThread(t->m, javaThread, t);
addThread(t, p);