美文网首页
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