runtime/heap-profiler.cpp (565 lines of code) (raw):
// Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com)
#include "heap-profiler.h"
#include <cerrno>
#include "file.h"
#include "handles.h"
#include "os.h"
#include "runtime.h"
#include "thread.h"
namespace py {
const char HeapProfiler::kBytearrayClassName[] = "byte[]";
const char HeapProfiler::kDoubleArrayClassName[] = "double[]";
const char HeapProfiler::kInvalid[] = "<INVALID>";
const char HeapProfiler::kOverflow[] = "<OVERFLOW>";
const char HeapProfiler::kJavaLangClass[] = "java.lang.Class";
const char HeapProfiler::kJavaLangClassLoader[] = "java.lang.ClassLoader";
const char HeapProfiler::kJavaLangObject[] = "java.lang.Object";
const char HeapProfiler::kJavaLangString[] = "java.lang.String";
const char HeapProfiler::kLongArrayClassName[] = "long[]";
const char HeapProfiler::kObjectArrayClassName[] = "java.lang.Object[]";
HeapProfiler::HeapProfiler(Thread* thread, HeapProfilerWriteCallback callback,
void* stream)
: thread_(thread), output_stream_(stream), write_callback_(callback) {}
void HeapProfiler::write(const void* data, word size) {
(*write_callback_)(data, size, output_stream_);
}
// LOAD CLASS - 0x02
//
// Format:
// u4 - class serial number (always > 0)
// ID - class object ID
// u4 - stack trace serial number
// ID - class name string ID
void HeapProfiler::writeFakeLoadClass(FakeClass fake_class,
const char* class_name) {
Record record(kLoadClass, this);
// class serial number (always > 0)
record.write32(1);
// class object ID
record.writeObjectId(static_cast<uword>(fake_class));
// stack trace serial number
record.write32(0);
// TODO(T61807224): Dump type names discriminated by layout ID
// class name string ID
record.writeObjectId(cStringId(class_name));
}
// CLASS DUMP - 0x20
//
// Format:
// u4 - class serial number (always > 0)
// ID - class object ID
// u4 - stack trace serial number
// ID - class name string ID
void HeapProfiler::writeFakeClassDump(FakeClass fake_class,
const char* class_name,
FakeClass fake_super_class) {
writeFakeLoadClass(fake_class, class_name);
CHECK(!class_dump_table_.add(static_cast<uword>(fake_class)),
"cannot dump object twice");
SubRecord sub(Subtag::kClassDump, current_record_);
// class object ID
sub.writeObjectId(static_cast<uword>(fake_class));
// stack trace serial number
sub.write32(0);
// super class object ID
sub.writeObjectId(static_cast<uword>(fake_super_class));
// class loader object ID
sub.writeObjectId(0);
// signers object ID
sub.writeObjectId(0);
// protection domain object ID
sub.writeObjectId(0);
// reserved
sub.writeObjectId(0);
// reserved
sub.writeObjectId(0);
// instance size (in bytes)
sub.write32(0);
// size of constant pool and number of records that follow
sub.write16(0);
// Number of static fields
sub.write16(0);
// Number of instance fields (not include super class's)
sub.write16(0);
}
// STACK TRACE - 0x05
//
// u4 - stack trace serial number
// u4 - thread serial number
// u4 - number of frames
// [ID]* - series of stack frame ID's
void HeapProfiler::writeFakeStackTrace() {
Record record(kStackTrace, this);
// stack trace serial number
record.write32(0);
// thread serial number
// TODO(T70833159): Support multiple threads in heap dumper.
record.write32(0);
// number of frames
record.write32(0);
}
void HeapProfiler::writeHeader() {
const char magic[] = "JAVA PROFILE 1.0.2";
write(magic, sizeof(magic));
write32(kPointerSize);
double seconds_double = OS::currentTime();
uint64_t seconds = static_cast<uint64_t>(seconds_double);
seconds += (seconds_double - seconds);
uint64_t milliseconds = seconds * kMillisecondsPerSecond;
uint32_t hi =
static_cast<uint32_t>((milliseconds >> 32) & 0x00000000FFFFFFFF);
write32(hi);
uint32_t lo = static_cast<uint32_t>(milliseconds & 0x00000000FFFFFFFF);
write32(lo);
}
// LOAD CLASS - 0x02
//
// Format:
// u4 - class serial number (always > 0)
// ID - class object ID
// u4 - stack trace serial number
// ID - class name string ID
void HeapProfiler::writeLoadClass(RawLayout layout) {
CHECK(!load_class_table_.add(layout.raw()), "cannot dump object twice");
Record record(kLoadClass, this);
// class serial number (always > 0)
record.write32(1);
// class object ID
record.writeObjectId(objectId(layout));
// stack trace serial number
record.write32(0);
// class name string ID
HandleScope scope(thread_);
Type type(&scope, thread_->runtime()->concreteTypeAt(layout.id()));
Str name(&scope, type.name());
record.writeObjectId(stringId(*name));
}
// CLASS DUMP - 0x20
//
// Format:
// ID - class object ID
// u4 - stack trace serial number
// ID - super class object ID
// ID - class loader object ID
// ID - signers object ID
// ID - protection domain object ID
// ID - reserved
// ID - reserved
// u4 - instance size (in bytes)
// u2 - size of constant pool and number of records that follow
// u2 - constant pool index
// u1 - type of entry: (See Basic Type)
// value - value of entry (u1, u2, u4, or u8 based on type of entry)
// u2 - Number of static fields:
// ID - static field name string ID
// u1 - type of field: (See Basic Type)
// value - value of entry (u1, u2, u4, or u8 based on type of field)
// u2 - Number of instance fields (not including super class's)
// ID - field name string ID
// u1 - type of field: (See Basic Type)
void HeapProfiler::writeClassDump(RawLayout layout) {
CHECK(!class_dump_table_.add(layout.raw()), "cannot dump object twice");
SubRecord sub(kClassDump, current_record_);
// class object ID
sub.writeObjectId(classId(layout));
// stack trace serial number
sub.write32(0);
// super class object ID
if (layout.id() == LayoutId::kObject) {
// Superclass == 0 => object is java.lang.Object
sub.writeObjectId(0);
} else {
// Since there is not much of a concept of inheritance in the Layout
// system, pretend all Layouts' super is "object". This allows much easier
// dumping of attributes.
// TODO(emacs): Figure out how to dump class hierarchies
RawLayout super_layout =
Layout::cast(thread_->runtime()->layoutAt(LayoutId::kObject));
sub.writeObjectId(classId(super_layout));
}
// class loader object ID
sub.writeObjectId(0);
// signers object ID
sub.writeObjectId(0);
// protection domain object ID
sub.writeObjectId(0);
// reserved
sub.writeObjectId(0);
// reserved
sub.writeObjectId(0);
// instance size (in bytes)
sub.write32(layout.instanceSize());
// size of constant pool and number of records that follow
// Constant pool is variable-length and empty here
sub.write16(0);
// number of static fields
// Static fields are variable-length and empty here
sub.write16(0);
Runtime* runtime = thread_->runtime();
if (layout.id() == LayoutId::kComplex) {
// Two instance fields: "real", "imag"
sub.write16(2);
sub.writeObjectId(stringId(Str::cast(runtime->symbols()->at(ID(real)))));
sub.write8(BasicType::kDouble);
sub.writeObjectId(stringId(Str::cast(runtime->symbols()->at(ID(imag)))));
sub.write8(BasicType::kDouble);
return;
}
if (layout.id() == LayoutId::kFloat) {
// One instance field: "value"
sub.write16(1);
sub.writeObjectId(stringId(Str::cast(runtime->symbols()->at(ID(value)))));
sub.write8(BasicType::kDouble);
return;
}
// number of instance fields (not include super class's)
RawTuple in_object = Tuple::cast(layout.inObjectAttributes());
word num_in_object = in_object.length();
bool has_tuple_overflow = layout.hasTupleOverflow();
word num_overflow = has_tuple_overflow ? 1 : 0;
word num_attributes = num_in_object + num_overflow;
sub.write16(num_attributes);
// instance fields
for (word i = 0; i < num_in_object; i++) {
// allocated on the layout for an attribute
RawObject name = Tuple::cast(in_object.at(i)).at(0);
if (name == SmallInt::fromWord(0)) {
sub.writeObjectId(cStringId(kInvalid));
} else {
sub.writeObjectId(stringId(Str::cast(name)));
}
sub.write8(BasicType::kObject);
}
// TODO(emacs): Remove this special case once tuple overflow fits neatly into
// the allocated in-object attributes
if (has_tuple_overflow) {
sub.writeObjectId(cStringId(kOverflow));
sub.write8(BasicType::kObject);
}
}
void HeapProfiler::writeInstanceDump(RawInstance obj) {
CHECK(!heap_object_table_.add(obj.raw()), "cannot dump object twice");
SubRecord sub(kInstanceDump, current_record_);
RawLayout layout = Layout::cast(Thread::current()->runtime()->layoutOf(obj));
RawTuple in_object = Tuple::cast(layout.inObjectAttributes());
word num_in_object = in_object.length();
bool has_tuple_overflow = layout.hasTupleOverflow();
word num_overflow = has_tuple_overflow ? 1 : 0;
word num_attributes = num_in_object + num_overflow;
sub.beginInstanceDump(obj, /*stack_trace=*/0, num_attributes * kPointerSize,
classId(layout));
// write in-object attributes
for (word i = 0; i < num_in_object; i++) {
RawTuple elt = Tuple::cast(in_object.at(i));
AttributeInfo info(elt.at(1));
sub.writeObjectId(
objectId(Instance::cast(obj).instanceVariableAt(info.offset())));
}
// write tuple overflow (dict overflow is in-object)
if (has_tuple_overflow) {
sub.writeObjectId(objectId(
Instance::cast(obj).instanceVariableAt(layout.overflowOffset())));
}
}
void HeapProfiler::writeImmediate(RawObject obj) {
DCHECK(!obj.isHeapObject(), "obj must be an immediate");
SubRecord sub(kInstanceDump, current_record_);
RawLayout layout = Layout::cast(Thread::current()->runtime()->layoutOf(obj));
sub.beginInstanceDump(obj, /*stack_trace=*/0, /*num_bytes=*/0,
classId(layout));
}
class ImmediateVisitor : public WordVisitor {
public:
ImmediateVisitor(HeapProfiler* profiler) : profiler_(profiler) {}
void visit(uword element) { profiler_->writeImmediate(RawObject{element}); }
private:
HeapProfiler* profiler_;
};
void HeapProfiler::writeImmediates() {
ImmediateVisitor visitor(this);
immediate_table_.visitElements(&visitor);
}
// OBJECT ARRAY DUMP - 0x22
//
// Format:
// ID - array object ID
// u4 - stack trace serial number
// u4 - number of elements
// ID - array class object id
// [ID]* - elements
void HeapProfiler::writeObjectArray(RawTuple tuple) {
SubRecord sub(kObjectArrayDump, current_record_);
// array object id
sub.writeObjectId(objectId(tuple));
// stack trace serial number
sub.write32(0);
// number of elements
word length = tuple.length();
CHECK(length < kMaxUint32, "length %ld too big for Java length field",
length);
sub.write32(static_cast<uint32_t>(length));
// array class object id
sub.writeObjectId(static_cast<uword>(FakeClass::kObjectArray));
// elements
for (word i = 0; i < length; i++) {
sub.writeObjectId(objectId(tuple.at(i)));
}
}
void HeapProfiler::writeBytes(RawBytes bytes) {
CHECK(!heap_object_table_.add(bytes.raw()), "cannot dump object twice");
SubRecord sub(kPrimitiveArrayDump, current_record_);
sub.beginPrimitiveArrayDump(objectId(bytes), /*stack_trace=*/0,
bytes.length(), BasicType::kByte);
for (word i = 0; i < bytes.length(); i++) {
sub.write8(bytes.byteAt(i));
}
}
void HeapProfiler::writeLargeStr(RawLargeStr str) {
CHECK(!heap_object_table_.add(str.raw()), "cannot dump object twice");
SubRecord sub(kPrimitiveArrayDump, current_record_);
word length = str.length();
sub.beginPrimitiveArrayDump(objectId(str), /*stack_trace=*/0, length,
BasicType::kByte);
for (word i = 0; i < length; i++) {
sub.write8(str.byteAt(i));
}
}
void HeapProfiler::writeComplex(RawComplex obj) {
CHECK(!heap_object_table_.add(obj.raw()), "cannot dump object twice");
SubRecord sub(kInstanceDump, current_record_);
RawLayout layout = Layout::cast(Thread::current()->runtime()->layoutOf(obj));
sub.beginInstanceDump(obj, /*stack_trace=*/0, 2 * kDoubleSize,
classId(layout));
sub.write64(bit_cast<uint64_t>(obj.real()));
sub.write64(bit_cast<uint64_t>(obj.imag()));
}
void HeapProfiler::writeEllipsis(RawEllipsis obj) {
CHECK(!heap_object_table_.add(obj.raw()), "cannot dump object twice");
SubRecord sub(kInstanceDump, current_record_);
RawLayout layout =
Layout::cast(thread_->runtime()->layoutAt(LayoutId::kEllipsis));
sub.beginInstanceDump(obj, /*stack_trace=*/0, layout.instanceSize(),
classId(layout));
for (word i = 0; i < layout.instanceSize(); i += kPointerSize) {
sub.writeObjectId(objectId(Unbound::object()));
}
}
void HeapProfiler::writeFloat(RawFloat obj) {
CHECK(!heap_object_table_.add(obj.raw()), "cannot dump object twice");
SubRecord sub(kInstanceDump, current_record_);
RawLayout layout = Layout::cast(Thread::current()->runtime()->layoutOf(obj));
sub.beginInstanceDump(obj, /*stack_trace=*/0, kDoubleSize, classId(layout));
sub.write64(bit_cast<uint64_t>(obj.value()));
}
void HeapProfiler::writeLargeInt(RawLargeInt obj) {
CHECK(!heap_object_table_.add(obj.raw()), "cannot dump object twice");
SubRecord sub(kPrimitiveArrayDump, current_record_);
sub.beginPrimitiveArrayDump(objectId(obj), /*stack_trace=*/0, obj.numDigits(),
BasicType::kLong);
for (word i = 0; i < obj.numDigits(); i++) {
sub.write64(obj.digitAt(i));
}
}
// HEAP DUMP SEGMENT - 0x1C
void HeapProfiler::setRecord(Record* current_record) {
DCHECK(current_record != nullptr, "record should be non-null");
DCHECK(current_record_ == nullptr, "current record already exists");
current_record_ = current_record;
}
void HeapProfiler::clearRecord() {
DCHECK(current_record_ != nullptr, "current record does not exist");
current_record_ = nullptr;
}
// HEAP DUMP END - 0x2C
void HeapProfiler::writeHeapDumpEnd() { Record record(kHeapDumpEnd, this); }
uword HeapProfiler::objectId(RawObject obj) {
uword id = obj.raw();
if (obj.isHeader()) {
std::fprintf(stderr,
"objectId called on header @ 0x%lx, indicating misalignment\n",
id);
}
if (!obj.isError() && !obj.isHeapObject()) {
immediate_table_.add(id);
}
return id;
}
uword HeapProfiler::classId(RawLayout layout) {
uword id = layout.raw();
if (!layout_table_.add(id)) {
writeLoadClass(layout);
}
return id;
}
uword HeapProfiler::cStringId(const char* c_str) {
uword id = reinterpret_cast<uword>(c_str);
if (!string_table_.add(id)) {
writeCStringInUTF8(c_str);
}
return id;
}
uword HeapProfiler::stringId(RawStr str) {
uword id = objectId(str);
if (!string_table_.add(id)) {
writeStringInUTF8(str);
}
return id;
}
// ROOT UNKNOWN - 0xFF
//
// Describes a root of unknown provenance.
//
// Format:
// ID - object ID
void HeapProfiler::writeRuntimeRoot(RawObject obj) {
SubRecord sub(kRootUnknown, current_record_);
// object ID
sub.writeObjectId(objectId(obj));
}
// ROOT STICKY CLASS - 0x05
//
// Describes a built-in Layout.
//
// Format:
// ID - object ID
void HeapProfiler::writeStickyClassRoot(RawObject obj) {
SubRecord sub(kRootStickyClass, current_record_);
// object ID
sub.writeObjectId(objectId(obj));
}
// ROOT JAVA FRAME - 0x03
//
// Describes a value found in a Frame in the Python stack.
//
// Format:
// ID - object ID
// u4 - thread serial number
// u4 - frame number in stack trace (-1 for empty)
void HeapProfiler::writeStackRoot(RawObject obj) {
SubRecord sub(kRootJavaFrame, current_record_);
// object ID
sub.writeObjectId(objectId(obj));
// thread serial number
// TODO(T70833159): Support multiple threads in heap dumper.
sub.write32(0);
// frame number in stack trace
sub.write32(-1);
}
// ROOT THREAD OBJECT - 0x08
//
// Describes a Thread object.
//
// Format:
// ID - object ID
// u4 - thread serial number
// u4 - stack trace serial number
void HeapProfiler::writeThreadRoot(Thread* thread) {
SubRecord sub(kRootThreadObject, current_record_);
// object ID
sub.writeObjectId(reinterpret_cast<uword>(thread));
// thread serial number
// TODO(T70833159): Support multiple threads in heap dumper.
sub.write32(0);
// stack trace serial number
sub.write32(0);
}
// ROOT JNI GLOBAL - 0x01
//
// Describes an object wrapped in an ApiHandle.
//
// Format:
// ID - object ID
// ID - ApiHandle address
void HeapProfiler::writeApiHandleRoot(void* handle, RawObject obj) {
SubRecord sub(kRootJniGlobal, current_record_);
// object ID
sub.writeObjectId(objectId(obj));
// ApiHandle address
sub.writeObjectId(reinterpret_cast<uword>(handle));
}
// ROOT UNKNOWN - 0xFF
//
// Describes an object of unknown provenance (typically Runtime or Thread root).
//
// Format:
// ID - object ID
void HeapProfiler::writeUnknownRoot(RawObject obj) {
SubRecord sub(kRootUnknown, current_record_);
// object ID
sub.writeObjectId(objectId(obj));
}
// ROOT NATIVE STACK - 0x04
//
// Describes an object inside a native frame (in a Handle).
//
// Format:
// ID - object ID
// u4 - thread serial number
void HeapProfiler::writeHandleRoot(RawObject obj) {
SubRecord sub(kRootNativeStack, current_record_);
// object ID
sub.writeObjectId(objectId(obj));
// thread serial number
// TODO(T70833159): Support multiple threads in heap dumper.
sub.write32(0);
}
// STRING IN UTF8 - 0x01
//
// Format:
// ID - ID for this string
// [u1]* - UTF8 characters for string (NOT NULL terminated)
void HeapProfiler::writeStringInUTF8(RawStr str) {
Record record(kStringInUtf8, this);
record.writeObjectId(str.raw());
for (word i = 0, length = str.length(); i < length; i++) {
record.write8(str.byteAt(i));
}
}
void HeapProfiler::writeCStringInUTF8(const char* c_str) {
Record record(kStringInUtf8, this);
record.writeObjectId(reinterpret_cast<uword>(c_str));
for (; *c_str != '\0'; ++c_str) {
record.write8(*c_str);
}
}
HeapProfiler::Buffer::Buffer() : data_(Vector<uint8_t>()) {}
void HeapProfiler::Buffer::write(const uint8_t* data, word size) {
for (word i = 0; i < size; i++) {
data_.push_back(data[i]);
}
}
void HeapProfiler::write8(uint8_t value) { write(&value, sizeof(value)); }
void HeapProfiler::write16(uint16_t value) {
for (word i = 1; i >= 0; i--) {
write8((value >> (i * kBitsPerByte)) & 0xff);
}
}
void HeapProfiler::write32(uint32_t value) {
for (word i = 3; i >= 0; i--) {
write8((value >> (i * kBitsPerByte)) & 0xff);
}
}
void HeapProfiler::write64(uint64_t value) {
for (word i = 7; i >= 0; i--) {
write8((value >> (i * kBitsPerByte)) & 0xff);
}
}
HeapProfiler::Record::Record(Tag tag, HeapProfiler* profiler)
: tag_(tag), profiler_(profiler) {}
// Record
//
// Format:
// u1 - TAG: denoting the type of the record
// u4 - TIME: number of microseconds since the time stamp in the header
// u4 - LENGTH: number of bytes that follow this u4 field and belong
// to this record
// [u1]* - BODY: as many bytes as specified in the above u4 field
HeapProfiler::Record::~Record() {
if (profiler_) {
profiler_->write8(tag());
profiler_->write32(time());
profiler_->write32(length());
if (length() > 0) {
profiler_->write(body(), length());
}
}
}
void HeapProfiler::Record::write(const byte* value, word size) {
body_.write(value, size);
}
void HeapProfiler::Record::write8(uint8_t value) {
body_.write(&value, sizeof(value));
}
void HeapProfiler::Record::write16(uint16_t value) {
for (word i = 1; i >= 0; i--) {
write8((value >> (i * kBitsPerByte)) & 0xff);
}
}
void HeapProfiler::Record::write32(uint32_t value) {
for (word i = 3; i >= 0; i--) {
write8((value >> (i * kBitsPerByte)) & 0xff);
}
}
void HeapProfiler::Record::write64(uint64_t value) {
for (word i = 7; i >= 0; i--) {
write8((value >> (i * kBitsPerByte)) & 0xff);
}
}
void HeapProfiler::Record::writeObjectId(uword value) { write64(value); }
HeapProfiler::SubRecord::SubRecord(Subtag sub_tag, Record* record)
: record_(record) {
DCHECK(record_ != nullptr, "heap dump segment does not exist");
record_->write8(sub_tag);
}
void HeapProfiler::SubRecord::beginInstanceDump(RawObject obj,
uword stack_trace,
uword num_bytes,
uword layout_id) {
// TODO(emacs): This is a hack that works around MAT expecting ClassLoader at
// 0. Once we have modified MAT to dump ClassLoader at a different location
// than 0, we should just dump SmallInt 0 normally.
word id = obj.raw() == 0 ? 73 : obj.raw();
writeObjectId(id);
write32(stack_trace);
writeObjectId(layout_id);
write32(num_bytes);
}
void HeapProfiler::SubRecord::beginPrimitiveArrayDump(uword object_id,
uword stack_trace,
uword length,
BasicType type) {
writeObjectId(object_id);
write32(stack_trace);
CHECK(length < kMaxUint32, "length %ld too big for Java length field",
length);
write32(static_cast<uint32_t>(length));
write8(type);
}
void HeapProfiler::SubRecord::write(const uint8_t* value, intptr_t size) {
record_->write(value, size);
}
void HeapProfiler::SubRecord::write8(uint8_t value) { record_->write8(value); }
void HeapProfiler::SubRecord::write16(uint16_t value) {
record_->write16(value);
}
void HeapProfiler::SubRecord::write32(uint32_t value) {
record_->write32(value);
}
void HeapProfiler::SubRecord::write64(uint64_t value) {
record_->write64(value);
}
void HeapProfiler::SubRecord::writeObjectId(uword value) {
record_->writeObjectId(value);
}
static void writeToFileStream(const void* data, word length, void* stream) {
DCHECK(data != nullptr, "data must not be null");
DCHECK(length > 0, "length must be positive");
word fd = static_cast<int>(reinterpret_cast<word>(stream));
int result = File::write(fd, data, length);
CHECK(result == length, "could not write the whole chunk to disk");
}
class HeapProfilerHandleVisitor : public HandleVisitor {
public:
HeapProfilerHandleVisitor(HeapProfiler* profiler) : profiler_(profiler) {}
void visitHandle(void* handle, RawObject obj) {
return profiler_->writeApiHandleRoot(handle, obj);
}
protected:
HeapProfiler* profiler_;
DISALLOW_COPY_AND_ASSIGN(HeapProfilerHandleVisitor);
};
class HeapProfilerRootVisitor : public PointerVisitor {
public:
HeapProfilerRootVisitor(HeapProfiler* profiler) : profiler_(profiler) {}
void visitPointer(RawObject* pointer, PointerKind kind) {
// TODO(emacs): This is a hack that works around MAT expecting ClassLoader
// at 0. Once we have modified MAT to dump ClassLoader at a different
// location than 0, we should just dump SmallInt 0 normally.
RawObject obj = RawObject{pointer->raw() == 0 ? 73 : pointer->raw()};
switch (kind) {
case PointerKind::kRuntime:
case PointerKind::kThread:
case PointerKind::kUnknown:
return profiler_->writeUnknownRoot(obj);
case PointerKind::kHandle:
return profiler_->writeHandleRoot(obj);
case PointerKind::kStack:
return profiler_->writeStackRoot(obj);
case PointerKind::kApiHandle:
// Should only see handles in `HeapProfilerHandleVisitor`.
UNREACHABLE("should not be used");
case PointerKind::kLayout:
return profiler_->writeStickyClassRoot(obj);
}
}
protected:
HeapProfiler* profiler_;
DISALLOW_COPY_AND_ASSIGN(HeapProfilerRootVisitor);
};
class HeapProfilerObjectVisitor : public HeapObjectVisitor {
public:
HeapProfilerObjectVisitor(HeapProfiler* profiler) : profiler_(profiler) {}
void visitHeapObject(RawHeapObject obj) {
switch (obj.layoutId()) {
case LayoutId::kLayout:
return profiler_->writeClassDump(Layout::cast(obj));
case LayoutId::kLargeInt:
return profiler_->writeLargeInt(LargeInt::cast(obj));
case LayoutId::kLargeBytes:
case LayoutId::kMutableBytes:
return profiler_->writeBytes(Bytes::cast(obj));
case LayoutId::kFloat:
return profiler_->writeFloat(Float::cast(obj));
case LayoutId::kComplex:
return profiler_->writeComplex(Complex::cast(obj));
case LayoutId::kTuple:
case LayoutId::kMutableTuple:
return profiler_->writeObjectArray(Tuple::cast(obj));
case LayoutId::kLargeStr:
return profiler_->writeLargeStr(LargeStr::cast(obj));
case LayoutId::kEllipsis:
return profiler_->writeEllipsis(Ellipsis::cast(obj));
default:
CHECK(obj.isInstance(), "obj should be instance, but is %ld",
obj.layoutId());
return profiler_->writeInstanceDump(Instance::cast(obj));
}
}
protected:
HeapProfiler* profiler_;
DISALLOW_COPY_AND_ASSIGN(HeapProfilerObjectVisitor);
};
RawObject heapDump(Thread* thread, const char* filename) {
int fd = File::open(
filename,
File::kBinaryFlag | File::kCreate | File::kTruncate | File::kWriteOnly,
0644);
if (fd < 0) {
int saved_errno = errno;
return thread->raiseOSErrorFromErrno(saved_errno);
}
HeapProfiler profiler(thread, writeToFileStream, reinterpret_cast<void*>(fd));
profiler.writeHeader();
profiler.writeFakeStackTrace();
{
HeapProfiler::Record record(HeapProfiler::kHeapDumpSegment, &profiler);
profiler.setRecord(&record);
profiler.writeThreadRoot(thread);
// java.lang.Class
profiler.writeFakeClassDump(HeapProfiler::FakeClass::kJavaLangClass,
HeapProfiler::kJavaLangClass,
HeapProfiler::FakeClass::kJavaLangObject);
// java.lang.ClassLoader
profiler.writeFakeClassDump(HeapProfiler::FakeClass::kJavaLangClassLoader,
HeapProfiler::kJavaLangClassLoader,
HeapProfiler::FakeClass::kJavaLangObject);
// java.lang.Object
profiler.writeFakeClassDump(HeapProfiler::FakeClass::kJavaLangObject,
HeapProfiler::kJavaLangObject,
static_cast<HeapProfiler::FakeClass>(0x0));
// java.lang.String
profiler.writeFakeClassDump(HeapProfiler::FakeClass::kJavaLangString,
HeapProfiler::kJavaLangString,
HeapProfiler::FakeClass::kJavaLangObject);
// byte[]
profiler.writeFakeClassDump(HeapProfiler::FakeClass::kBytearray,
HeapProfiler::kBytearrayClassName,
HeapProfiler::FakeClass::kJavaLangObject);
// double[]
profiler.writeFakeClassDump(HeapProfiler::FakeClass::kDoubleArray,
HeapProfiler::kDoubleArrayClassName,
HeapProfiler::FakeClass::kJavaLangObject);
// long[]
profiler.writeFakeClassDump(HeapProfiler::FakeClass::kLongArray,
HeapProfiler::kLongArrayClassName,
HeapProfiler::FakeClass::kJavaLangObject);
// java.lang.Object[]
profiler.writeFakeClassDump(HeapProfiler::FakeClass::kObjectArray,
HeapProfiler::kObjectArrayClassName,
HeapProfiler::FakeClass::kJavaLangObject);
Runtime* runtime = thread->runtime();
HeapProfilerRootVisitor root_visitor(&profiler);
runtime->visitRootsWithoutApiHandles(&root_visitor);
HeapProfilerHandleVisitor handle_visitor(&profiler);
visitApiHandles(runtime, &handle_visitor);
HeapProfilerObjectVisitor object_visitor(&profiler);
runtime->heap()->visitAllObjects(&object_visitor);
profiler.writeImmediates();
profiler.clearRecord();
}
profiler.writeHeapDumpEnd();
int result = File::close(fd);
CHECK(result == 0, "could not close file '%s'", filename);
return NoneType::object();
}
} // namespace py