JVM源码分析之perfData文件的创建

本文首发于微信公众号:javaadu

背景

看寒泉子的一篇文章: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问题排查和优化、Java面试题、个人成长和自我管理等主题,为读者提供一线开发者的工作和成长经验,期待你能在这里有所收获。
javaadu

发表评论

电子邮件地址不会被公开。 必填项已用*标注