mirror of
https://github.com/corda/corda.git
synced 2025-01-04 04:04:27 +00:00
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:
parent
f38a55cbb2
commit
6a5116e7a7
@ -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;
|
||||
|
@ -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() {
|
||||
|
5
makefile
5
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)
|
||||
|
@ -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
331
src/heapdump.cpp
Normal 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
|
@ -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)
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user