美文网首页
JDK 1.8 heapdump源码

JDK 1.8 heapdump源码

作者: starskye | 来源:发表于2024-09-05 15:11 被阅读0次

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收官-.-。

相关文章

网友评论

      本文标题:JDK 1.8 heapdump源码

      本文链接:https://www.haomeiwen.com/subject/koarljtx.html