当前位置 博文首页 > jiangbb8686的博客:JVM源码分析之perfData文件的创建

    jiangbb8686的博客:JVM源码分析之perfData文件的创建

    作者:[db:作者] 时间:2021-08-28 10:12

    看泉子的一篇文章:JVM源码分析之Jstat工具原理完全解读 - 你假笨 里提到了两个JVM参数,可以控制perfdata文件是否共享,引用泉子对这两个参数的解释:

    • UsePerfData:如果关闭了UsePerfData这个参数,那么jvm启动过程中perf memory都不会被创建,默认情况是是打开的
    • PerfDisableSharedMem:该参数决定了存储PerfData的内存是不是可以被共享,也就是说不管这个参数设置没设置,jvm在启动的时候都会分配一块内存来存PerfData,只是说这个PerfData是不是其他进程可见的问题,如果设置了这个参数,说明不能被共享,此时其他进程将访问不了该内存,这样一来,譬如我们jps,jstat等都无法工作。默认这个参数是关闭的,也就是默认支持共享的方式

    由于perfdata文件时通过mmap共享的,因此考虑看下perfdata文件的创建过程,看看跟mmap的MAP_SHARED和MAP_PRIVATE两个标志位是如何联系在一起的。perfdata文件底层是使用mmap接口实现的,而mmap接口的参数中有关于内存可见性的两个参数:MAP_SHARED和MAP_PRIVATE,如果JVM参数设置允许perfdata文件共享,则使用MAP_SHARED标记。

    源码分析

    perfdata文件在jvm启动的时候创,在init.cpp文件中:

    ?

    void vm_init_globals() {
      check_ThreadShadow();
      basic_types_init();
      eventlog_init();
      mutex_init();
      chunkpool_init();
      perfMemory_init();
    }
    

    在perfMemory.cpp文件中看下perfMemory_init()方法,

    ?

    void perfMemory_init() {
    
      if (!UsePerfData) return;
    
      PerfMemory::initialize();
    }
    

    可以看出,如果UsePerfData参数设置为false,则直接返回,不会创建perfdata文件;接着看PerfMemory::initialize()方法,在这个方法里会调用create_memory_region(capacity);,用于申请perfdata的内存区域;create_memory_region这个方法不同的平台有不同的实现,我们这里看linux平台下的实现;

    看下perfMemory_linux.cpp里的代码,可以看到另一熟悉的JVM参数——PerfDisableSharedMem,如果这个参数设置为false,则会创建perfdata文件,但是其他进程无法共享这块内存,会导致jps、jstat等工具无法使用;

    ?

    // create the PerfData memory region
    //
    // This method creates the memory region used to store performance
    // data for the JVM. The memory may be created in standard or
    // shared memory.
    //
    void PerfMemory::create_memory_region(size_t size) {
    
      if (PerfDisableSharedMem) {
        // do not share the memory for the performance data.
        _start = create_standard_memory(size);
      }
      else {
        _start = create_shared_memory(size);
        if (_start == NULL) {
    
          // creation of the shared memory region failed, attempt
          // to create a contiguous, non-shared memory region instead.
          //
          if (PrintMiscellaneous && Verbose) {
            warning("Reverting to non-shared PerfMemory region.\n");
          }
          PerfDisableSharedMem = true;
          _start = create_standard_memory(size);
        }
      }
    
      if (_start != NULL) _capacity = size;
    
    }
    

    首先看create_shared_memory(size)的实现:
    create_shared_memory(size) ——> mmap_create_shared(size)——>mapAddress = (char)::mmap((char)0, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);,在这里看到了MAP_SHARED标记。

    然后看create_standard_memory(size)的实现,这里并没有跟之前猜想的一样(用mmap方法建映射,传入MAP_PRIVATE标记),而是使用了os::reserve_memory(size)方法,用来分配一段堆内存。

    ?

    // Standard Memory Implementation Details
    
    // create the PerfData memory region in standard memory.
    //
    static char* create_standard_memory(size_t size) {
    
      // allocate an aligned chuck of memory
      char* mapAddress = os::reserve_memory(size);
    
      if (mapAddress == NULL) {
        return NULL;
      }
    
      // commit memory
      if (!os::commit_memory(mapAddress, size, !ExecMem)) {
        if (PrintMiscellaneous && Verbose) {
          warning("Could not commit PerfData memory\n");
        }
        os::release_memory(mapAddress, size);
        return NULL;
      }
    
      return mapAddress;
    }
    

    至此可以确认两个结论

    1. 创建shared内存,使用mmap,并传入MAP_SHARED标记
    2. 创建standard内存,使用os::reserve_memory分配一段堆内存,这点跟之前猜想的不一样

    推荐一个我最近在学的JVM课程,来自Oracle高级研究员郑宇迪在极客时间的JVM专栏,目前更新了16篇文章,我基本都跟下来了,质量值得信赖。
    整个专栏将分为四大模块。

    • 基本原理:剖析 Java 虚拟机的运行机制,逐一介绍 Java 虚拟机的设计决策以及工程实现;
    • 高效实现:探索 Java 编译器,以及内嵌于 Java 虚拟机中的即时编译器,帮助你更好地理解 Java 语言特性,继而写出简洁高效的代码;
    • 代码优化:介绍如何利用工具定位并解决代码中的问题,以及在已有工具不适用的情况下,如何打造专属轮子;
    • 虚拟机黑科技:介绍甲骨文实验室近年来的前沿工作之一 GraalVM。包括如何在 JVM 上高效运行其他语言;如何混搭这些语言,实现 Polyglot;如何将这些语言事前编译(Ahead-Of-Time,AOT)成机器指令,单独运行甚至嵌入至数据库中运行。

    cs