JDK 1.8 heapdump源码

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

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 给一个系统定位问题的时候,知识、经验是关键基础,数据是依据,工具是运用知识处理数据的手段。这里的数据是指:运行日志...
    云芈山人阅读 347评论 0 1
  • JDK为我们提供了不少的工具,如下图所示 这些可执行文件都是不错的工具,掌握他们就是掌握JVM分析的武器库。 在w...
    IT三明治阅读 216评论 0 0
  • GC的基础知识 1.什么是垃圾 C语言申请内存:malloc freeC++: new deletec/C++ 手...
    小川川哥哥哈阅读 469评论 0 0
  • GC和GC Tuning GC的基础知识 1.什么是垃圾 C语言申请内存:malloc free C++: new...
    WhaleFall_0db7阅读 407评论 0 0
  • JVM 1:JVM基础知识 什么是JVM 常见的JVM 2:ClassFileFormat 3:类编译-加载-初始...
    皮皮魏阅读 247评论 0 0