heapdump的实现在于jmap中,jmap则是sun.tools.jmap.JMap类,没错它是java实现的。至此以该类作为入口讲述。
public static void main(String[] args) throws Exception {
....
//如果不存在参数则这是useSA
if (optionCount == 0 || paramCount != 1) {
useSA = true;
}
....
if (useSA) {
//通过反射导出当前应用的信息,导出方法依赖于VM并且使用java进行编写。
//该函数并不会STW,但是导出的内容是有限的,只会导出类相关信息。
runTool(option, params);
} else {
String pid = args[1];
//链接进程导出(本文章主要讲述该实现)
dump(pid, option);
}
}
private static void dump(String pid, String options) throws IOException {
//通过attach连接获取对应的VM对象
....
VirtualMachine vm = attach(pid);
//该函数最终调用HotSpotVirtualMachine.execute
InputStream in = ((HotSpotVirtualMachine)vm).
dumpHeap((Object)filename,
(live ? LIVE_OBJECTS_OPTION : ALL_OBJECTS_OPTION));
....
}
//VirtualMachine类实现
public static VirtualMachine attach(String id)
throws AttachNotSupportedException, IOException
{
for (AttachProvider provider: providers) {
return provider.attachVirtualMachine(id);
}
}
//LinuxAttachProvider 类实现
public VirtualMachine attachVirtualMachine(String vmid){
...
return new LinuxVirtualMachine(this, vmd.id());
}
//至此得出所谓的attach便是通过socket完成的,也就代表JVM内部一定存在accept。
public class LinuxVirtualMachine {
...
LinuxVirtualMachine(AttachProvider provider, String vmid){
path = findSocketFile(pid);
...
int s = socket();
try {
connect(s, path);
} finally {
close(s);
}
}
//dumpHeap所执行
InputStream execute(String cmd, Object ... args) throws AgentLoadException, IOException {
int s = socket();
try {
connect(s, p);
} catch (IOException x) {
close(s);
throw x;
}
...
try {
writeString(s, PROTOCOL_VERSION);
//将传入的指令和参数写入该socket
//cmd此处为:dumpheap
writeString(s, cmd);
for (int i=0; i<3; i++) {
if (i < args.length && args[i] != null) {
writeString(s, (String)args[i]);
} else {
writeString(s, "");
}
}
} catch (IOException x) {
ioe = x;
}
}
至此java入口结束,后续将会进入目标jvm中
//文件attachListener.cpp 便是监听attach的地方
void AttachListener::init() {
//其内部创建了一个线程去执行attach_listener_thread_entry函数
JavaThread* listener_thread = new JavaThread(&attach_listener_thread_entry);
...
Thread::start(listener_thread);
}
//定义命令cmd与需要执行的函数
static AttachOperationFunctionInfo funcs[] = {
{ "agentProperties", get_agent_properties },
{ "datadump", data_dump },
{ "dumpheap", dump_heap }, //内存dump核心调用
{ "load", JvmtiExport::load_agent_library },
{ "properties", get_system_properties },
{ "threaddump", thread_dump }, //jstack调用
{ "inspectheap", heap_inspection },
{ "setflag", set_flag },
{ "printflag", print_flag },
{ "jcmd", jcmd },
{ NULL, NULL }
};
static void attach_listener_thread_entry(JavaThread* thread, TRAPS) {
//其内部存在一个死循环来处理事件
for (;;) {
//内部调用LinuxAttachListener::dequeue(),内部accept(listener(), &addr, &len)读取别处写入数据
//将写入的字符信息封装为AttachOperation结构从而返回到此处,内部的accept也是死循环
AttachOperation* op = AttachListener::dequeue();
...
AttachOperationFunctionInfo* info = NULL;
//遍历上方定义的所有函数:funcs
for (int i=0; funcs[i].name != NULL; i++) {
const char* name = funcs[i].name;
//当匹配到相同名字时则赋值给info
if (strcmp(op->name(), name) == 0) {
info = &(funcs[i]);
break;
}
}
if (info != NULL) {
//根据函数指针进行调用
res = (info->func)(op, &st);
}
}
}
jint dump_heap(AttachOperation* op, outputStream* out) {
...
//核心在此处,live_objects_only 是是否在dump前进行gc,从而减少导出对象数目
HeapDumper dumper(live_objects_only /* request GC */);
int res = dumper.dump(op->arg(0));
...
}
int HeapDumper::dump(const char* path) {
//其函数核心是调用VM事件
VM_HeapDumper dumper(&writer, _gc_before_heap_dump, _oome);
//需要注意的是dump谁都可以调用可以是java线程也可以是VM线程
//java线程则会调用VMThread进行执行,而VM线程则必须是在线程安全点,也就是所有java线程STW后才能执行
//如果不这样设计那么导出的同时还在产生新的数据,这样会给排错人员新增很多无所用的内存数据
if (Thread::current()->is_VM_thread()) {
assert(SafepointSynchronize::is_at_safepoint(), "Expected to be called at a safepoint");
dumper.doit();
} else {
VMThread::execute(&dumper);
}
}
void VMThread::execute(VM_Operation* op) {
//如果不是线程安全点则进入后再执行evaluate,否则直接执行
if (op->evaluate_at_safepoint() && !SafepointSynchronize::is_at_safepoint()) {
SafepointSynchronize::begin();
op->evaluate();
SafepointSynchronize::end();
} else {
//evaluate函数内部调用了doit(),此处不在展开
op->evaluate();
}
}
//最终执行此处的doit
void VM_HeapDumper::doit() {
...
//这里所有的xxx_do,均是遍历的含义,它内部将会遍历每一个对象,然后调用传入的函数比如do_load_class
SymbolTable::symbols_do(&sym_dumper);
ClassLoaderDataGraph::classes_do(&do_load_class);
Universe::basic_type_classes_do(&do_load_class);
dump_stack_traces();
...
}
//此处为do所执行的函数,可以看出非常简单,就是将所有的Klass按照HPROF文件的要求写入而已
//其他函数也一样,此处不在一一赘述。
void VM_HeapDumper::do_load_class(Klass* k) {
do {
DumperSupport::write_header(writer(), HPROF_LOAD_CLASS, remaining);
writer()->write_u4(++class_serial_num);
Klass* klass = k;
writer()->write_classID(klass);
dumper()->add_class_serial_number(klass, class_serial_num);
writer()->write_u4(STACK_TRACE_ID);
Symbol* name = klass->name();
writer()->write_symbolID(name);
k = klass->array_klass_or_null();
} while (k != NULL);
}
至此heapdump收官-.-。
网友评论