implement primitive heap dump facility for memory profiling, accessible via Runtime.dumpHeap

The proper way to do this is to implement a subset of JVMTI, but this
will do the job for now.
This commit is contained in:
Joel Dice 2008-10-21 17:38:20 -06:00
parent f38a55cbb2
commit 6a5116e7a7
8 changed files with 434 additions and 31 deletions

View File

@ -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;

View File

@ -111,8 +111,8 @@ public class Method<T> 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() {

View File

@ -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)

View File

@ -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)
{

331
src/heapdump.cpp Normal file
View File

@ -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<int*>
(reinterpret_cast<uint8_t*>(this)
+ sizeof(Set))),
entries(reinterpret_cast<Entry*>
(reinterpret_cast<uint8_t*>(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<uintptr_t>(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<object>
(mask(cast<void*>(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<object>(mask(*static_cast<void**>(p))));
}
Context* c;
} v(&context);
add(&context, 0)->number = 0;
visitRoots(t, &v);
}
} // namespace vm

View File

@ -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<uint32_t>(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<uint32_t>(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<object>(t->m->heap->follow(objectClass(t, o)));
object objectMask = static_cast<object>
@ -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<object>(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)
{

View File

@ -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

View File

@ -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();
}
}
}