diff --git a/classpath/java/lang/Runtime.java b/classpath/java/lang/Runtime.java index 51f15006c7..63a029301a 100644 --- a/classpath/java/lang/Runtime.java +++ b/classpath/java/lang/Runtime.java @@ -75,6 +75,8 @@ public class Runtime { public native long totalMemory(); + public static native void dumpHeap(String outputFile); + private static class MyProcess extends Process { private long pid; private final int in; diff --git a/classpath/java/lang/reflect/Method.java b/classpath/java/lang/reflect/Method.java index 39a9328c29..b28b70c045 100644 --- a/classpath/java/lang/reflect/Method.java +++ b/classpath/java/lang/reflect/Method.java @@ -111,8 +111,8 @@ public class Method extends AccessibleObject implements Member { } } - public static native Object invoke(Method method, Object instance, - Object ... arguments) + private static native Object invoke(Method method, Object instance, + Object ... arguments) throws InvocationTargetException, IllegalAccessException; public Class getReturnType() { diff --git a/makefile b/makefile index 946a3344cd..7ac848e6bd 100644 --- a/makefile +++ b/makefile @@ -204,6 +204,11 @@ vm-sources = \ $(src)/process.cpp \ $(src)/$(asm).cpp +ifeq ($(heapdump),true) + vm-sources += $(src)/heapdump.cpp + cflags += -DAVIAN_HEAPDUMP +endif + vm-asm-sources = $(src)/$(asm).S ifeq ($(process),compile) diff --git a/src/builtin.cpp b/src/builtin.cpp index 1cc429c691..8527eecdbb 100644 --- a/src/builtin.cpp +++ b/src/builtin.cpp @@ -585,6 +585,26 @@ Java_java_lang_Runtime_gc(Thread* t, jobject) collect(t, Heap::MajorCollection); } +#ifdef AVIAN_HEAPDUMP + +extern "C" JNIEXPORT void JNICALL +Java_java_lang_Runtime_dumpHeap(Thread* t, jclass, jstring outputFile) +{ + unsigned length = stringLength(t, *outputFile); + char n[length + 1]; + stringChars(t, *outputFile, n); + FILE* out = fopen(n, "wb"); + if (out) { + dumpHeap(t, out); + fclose(out); + } else { + object message = makeString(t, "file not found: %s", n); + t->exception = makeRuntimeException(t, message); + } +} + +#endif//AVIAN_HEAPDUMP + extern "C" JNIEXPORT void JNICALL Java_java_lang_Runtime_exit(Thread* t, jobject, jint code) { diff --git a/src/heapdump.cpp b/src/heapdump.cpp new file mode 100644 index 0000000000..ea3c1aa54a --- /dev/null +++ b/src/heapdump.cpp @@ -0,0 +1,331 @@ +/* Copyright (c) 2008, Avian Contributors + + Permission to use, copy, modify, and/or distribute this software + for any purpose with or without fee is hereby granted, provided + that the above copyright notice and this permission notice appear + in all copies. + + There is NO WARRANTY for this software. See license.txt for + details. */ + +#include "machine.h" + +using namespace vm; + +namespace { + +const uintptr_t PointerShift = log(BytesPerWord); + +class Set { + public: + class Entry { + public: + object value; + uint32_t number; + int next; + }; + + static unsigned footprint(unsigned capacity) { + return sizeof(Set) + + pad(sizeof(int) * capacity) + + pad(sizeof(Set::Entry) * capacity); + } + + Set(unsigned capacity): + size(0), + capacity(capacity), + index(reinterpret_cast + (reinterpret_cast(this) + + sizeof(Set))), + entries(reinterpret_cast + (reinterpret_cast(index) + + pad(sizeof(int) * capacity))) + { } + + unsigned size; + unsigned capacity; + int* index; + Entry* entries; +}; + +class Stack { + public: + class Entry { + public: + object value; + int offset; + }; + + static const unsigned Capacity = 4096; + + Stack(Stack* next): next(next), entryCount(0) { } + + Stack* next; + unsigned entryCount; + Entry entries[Capacity]; +}; + +class Context { + public: + Context(Thread* thread, FILE* out): + thread(thread), out(out), objects(0), stack(0), nextNumber(1) + { } + + ~Context() { + if (objects) { + thread->m->heap->free(objects, Set::footprint(objects->capacity)); + } + while (stack) { + Stack* dead = stack; + stack = dead->next; + thread->m->heap->free(stack, sizeof(Stack)); + } + } + + Thread* thread; + FILE* out; + Set* objects; + Stack* stack; + uint32_t nextNumber; +}; + +void +push(Context* c, object p, int offset) +{ + if (c->stack == 0 or c->stack->entryCount == Stack::Capacity) { + c->stack = new (c->thread->m->heap->allocate(sizeof(Stack))) + Stack(c->stack); + } + Stack::Entry* e = c->stack->entries + (c->stack->entryCount++); + e->value = p; + e->offset = offset; +} + +bool +pop(Context* c, object* p, int* offset) +{ + if (c->stack) { + if (c->stack->entryCount == 0) { + if (c->stack->next) { + Stack* dead = c->stack; + c->stack = dead->next; + c->thread->m->heap->free(dead, sizeof(Stack)); + } else { + return false; + } + } + Stack::Entry* e = c->stack->entries + (--c->stack->entryCount); + *p = e->value; + *offset = e->offset; + return true; + } else { + return false; + } +} + +unsigned +hash(object p, unsigned capacity) +{ + return (reinterpret_cast(p) >> PointerShift) + & (capacity - 1); +} + +Set::Entry* +find(Context* c, object p) +{ + if (c->objects == 0) return false; + + for (int i = c->objects->index[hash(p, c->objects->capacity)]; i >= 0;) { + Set::Entry* e = c->objects->entries + i; + if (e->value == p) { + return e; + } + i = e->next; + } + + return false; +} + +Set::Entry* +add(Context* c UNUSED, Set* set, object p) +{ + assert(c->thread, set->size < set->capacity); + + unsigned index = hash(p, set->capacity); + + int offset = set->size++; + Set::Entry* e = set->entries + offset; + e->value = p; + e->next = set->index[index]; + set->index[index] = offset; + return e; +} + +Set::Entry* +add(Context* c, object p) +{ + if (c->objects == 0 or c->objects->size == c->objects->capacity) { + unsigned capacity; + if (c->objects) { + capacity = c->objects->capacity * 2; + } else { + capacity = 4096; // must be power of two + } + + Set* set = new (c->thread->m->heap->allocate(Set::footprint(capacity))) + Set(capacity); + + memset(set->index, 0xFF, sizeof(int) * capacity); + + if (c->objects) { + for (unsigned i = 0; i < c->objects->capacity; ++i) { + for (int j = c->objects->index[i]; j >= 0;) { + Set::Entry* e = c->objects->entries + j; + add(c, set, e->value); + j = e->next; + } + } + + c->thread->m->heap->free + (c->objects, Set::footprint(c->objects->capacity)); + } + + c->objects = set; + } + + return add(c, c->objects, p); +} + +enum { + Root, + ClassName, + Push, + LastChild, + Pop, + Size +}; + +inline object +get(object o, unsigned offsetInWords) +{ + return static_cast + (mask(cast(o, offsetInWords * BytesPerWord))); +} + +void +write1(Context* c, uint8_t v) +{ + fwrite(&v, 1, 1, c->out); +} + +void +write4(Context* c, uint32_t v) +{ + uint8_t b[] = { v >> 24, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF }; + fwrite(b, 4, 1, c->out); +} + +void +writeString(Context* c, int8_t* p, unsigned size) +{ + write4(c, size); + fwrite(p, size, 1, c->out); +} + +unsigned +objectSize(Thread* t, object o) +{ + unsigned n = baseSize(t, o, objectClass(t, o)); + if (objectExtended(t, o)) { + ++ n; + } + return n; +} + + + +void +visit(Context* c, object p) +{ + Thread* t = c->thread; + int nextChildOffset; + + write1(c, Root); + + visit: { + Set::Entry* e = find(c, p); + if (e) { + write4(c, e->number); + } else { + e = add(c, p); + e->number = c->nextNumber++; + + write4(c, e->number); + + write1(c, Size); + write4(c, objectSize(t, p)); + + if (objectClass(t, p) == arrayBody(t, t->m->types, Machine::ClassType)) { + object name = className(t, p); + if (name) { + write1(c, ClassName); + writeString(c, &byteArrayBody(t, name, 0), + byteArrayLength(t, name) - 1); + } + } + + nextChildOffset = walkNext(t, p, -1); + if (nextChildOffset != -1) { + goto children; + } + } + } + + goto pop; + + children: { + int next = walkNext(t, p, nextChildOffset); + if (next >= 0) { + write1(c, Push); + push(c, p, next); + } else { + write1(c, LastChild); + } + p = get(p, nextChildOffset); + goto visit; + } + + pop: { + if (pop(c, &p, &nextChildOffset)) { + write1(c, Pop); + goto children; + } + } +} + +} // namespace + +namespace vm { + +void +dumpHeap(Thread* t, FILE* out) +{ + Context context(t, out); + + class Visitor : public Heap::Visitor { + public: + Visitor(Context* c): c(c) { } + + virtual void visit(void* p) { + ::visit(c, static_cast(mask(*static_cast(p)))); + } + + Context* c; + } v(&context); + + add(&context, 0)->number = 0; + + visitRoots(t, &v); +} + +} // namespace vm diff --git a/src/machine.cpp b/src/machine.cpp index d03a7fb1ed..efa840449b 100644 --- a/src/machine.cpp +++ b/src/machine.cpp @@ -177,34 +177,15 @@ footprint(Thread* t) return n; } -void -visitRoots(Thread* t, Heap::Visitor* v) -{ - if (t->state != Thread::ZombieState) { - v->visit(&(t->javaThread)); - v->visit(&(t->exception)); - - t->m->processor->visitObjects(t, v); - - for (Thread::Protector* p = t->protector; p; p = p->next) { - p->visit(v); - } - } - - for (Thread* c = t->child; c; c = c->peer) { - visitRoots(c, v); - } -} - void walk(Thread*, Heap::Walker* w, uint32_t* mask, unsigned fixedSize, - unsigned arrayElementSize, unsigned arrayLength) + unsigned arrayElementSize, unsigned arrayLength, unsigned start) { unsigned fixedSizeInWords = ceiling(fixedSize, BytesPerWord); unsigned arrayElementSizeInWords = ceiling(arrayElementSize, BytesPerWord); - for (unsigned i = 0; i < fixedSizeInWords; ++i) { + for (unsigned i = start; i < fixedSizeInWords; ++i) { if (mask[i / 32] & (static_cast(1) << (i % 32))) { if (not w->visit(i)) { return; @@ -222,8 +203,19 @@ walk(Thread*, Heap::Walker* w, uint32_t* mask, unsigned fixedSize, } if (arrayObjectElements) { - for (unsigned i = 0; i < arrayLength; ++i) { - for (unsigned j = 0; j < arrayElementSizeInWords; ++j) { + unsigned arrayStart; + unsigned elementStart; + if (start > fixedSizeInWords) { + unsigned s = start - fixedSizeInWords; + arrayStart = s / arrayElementSizeInWords; + elementStart = s % arrayElementSizeInWords; + } else { + arrayStart = 0; + elementStart = 0; + } + + for (unsigned i = arrayStart; i < arrayLength; ++i) { + for (unsigned j = elementStart; j < arrayElementSizeInWords; ++j) { unsigned k = fixedSizeInWords + j; if (mask[k / 32] & (static_cast(1) << (k % 32))) { if (not w->visit @@ -238,7 +230,7 @@ walk(Thread*, Heap::Walker* w, uint32_t* mask, unsigned fixedSize, } void -walk(Thread* t, Heap::Walker* w, object o) +walk(Thread* t, Heap::Walker* w, object o, unsigned start) { object class_ = static_cast(t->m->heap->follow(objectClass(t, o))); object objectMask = static_cast @@ -255,16 +247,16 @@ walk(Thread* t, Heap::Walker* w, object o) memcpy(mask, &intArrayBody(t, objectMask, 0), intArrayLength(t, objectMask) * 4); - walk(t, w, mask, fixedSize, arrayElementSize, arrayLength); + walk(t, w, mask, fixedSize, arrayElementSize, arrayLength, start); } else if (classVmFlags(t, class_) & SingletonFlag) { unsigned length = singletonLength(t, o); if (length) { walk(t, w, singletonMask(t, o), - (singletonCount(t, o) + 2) * BytesPerWord, 0, 0); - } else { + (singletonCount(t, o) + 2) * BytesPerWord, 0, 0, start); + } else if (start == 0) { w->visit(0); } - } else { + } else if (start == 0) { w->visit(0); } } @@ -1616,7 +1608,7 @@ class HeapClient: public Heap::Client { virtual void walk(void* p, Heap::Walker* w) { object o = static_cast(m->heap->follow(mask(p))); - ::walk(m->rootThread, w, o); + ::walk(m->rootThread, w, o, 0); } void dispose() { @@ -2772,6 +2764,44 @@ collect(Thread* t, Heap::CollectionType type) #endif } +int +walkNext(Thread* t, object o, int previous) +{ + class Walker: public Heap::Walker { + public: + Walker(): value(-1) { } + + bool visit(unsigned offset) { + value = offset; + return false; + } + + int value; + } walker; + + walk(t, &walker, o, previous + 1); + return walker.value; +} + +void +visitRoots(Thread* t, Heap::Visitor* v) +{ + if (t->state != Thread::ZombieState) { + v->visit(&(t->javaThread)); + v->visit(&(t->exception)); + + t->m->processor->visitObjects(t, v); + + for (Thread::Protector* p = t->protector; p; p = p->next) { + p->visit(v); + } + } + + for (Thread* c = t->child; c; c = c->peer) { + visitRoots(c, v); + } +} + void printTrace(Thread* t, object exception) { diff --git a/src/machine.h b/src/machine.h index d2adce2e33..ea2784442a 100644 --- a/src/machine.h +++ b/src/machine.h @@ -2204,6 +2204,12 @@ intern(Thread* t, object s); void exit(Thread* t); +int +walkNext(Thread* t, object o, int previous); + +void +visitRoots(Thread* t, Heap::Visitor* v); + inline jobject makeLocalReference(Thread* t, object o) { @@ -2297,6 +2303,9 @@ makeSingleton(Thread* t, unsigned count) return o; } +void +dumpHeap(Thread* t, FILE* out); + } // namespace vm void diff --git a/test/Hello.java b/test/Hello.java index d7d4f91935..51c8f0fb47 100644 --- a/test/Hello.java +++ b/test/Hello.java @@ -1,5 +1,11 @@ public class Hello { public static void main(String[] args) { System.out.println("hello, world!"); + try { + Runtime.class.getMethod("dumpHeap", String.class) + .invoke(null, "/tmp/heap.bin"); + } catch (Exception e) { + e.printStackTrace(); + } } }