详解Native Memory Tracking之追踪区域分析

Compiler

Compiler 就是 JIT 编译器线程在编译 code 时本身所使用的内存。查看 NMT 详情:

[0x0000ffff93e3acc0] Thread::allocate(unsigned long, bool, MemoryType)+0x348
[0x0000ffff9377a498] CompileBroker::make_compiler_thread(char const*, CompileQueue*, CompilerCounters*, AbstractCompiler*, Thread*)+0x120
[0x0000ffff9377ce98] CompileBroker::init_compiler_threads(int, int)+0x148
[0x0000ffff9377d400] CompileBroker::compilation_init()+0xc8
                             (malloc=37KB type=Thread #12)

跟踪调用链路:InitializeJVM ->
Threads::create_vm ->
CompileBroker::compilation_init ->
CompileBroker::init_compiler_threads ->
CompileBroker::make_compiler_thread

发现最后 make_compiler_thread 的线程的个数是在 compilation_init() 中计算的:

# hotspot/src/share/vm/compiler/CompileBroker.cpp
void CompileBroker::compilation_init() {
  ......
 // No need to initialize compilation system if we do not use it.
 if (!UseCompiler) {
 return;
  }
#ifndef SHARK
 // Set the interface to the current compiler(s).
  int c1_count = CompilationPolicy::policy()->compiler_count(CompLevel_simple);
  int c2_count = CompilationPolicy::policy()->compiler_count(CompLevel_full_optimization);
  ......
 // Start the CompilerThreads
 init_compiler_threads(c1_count, c2_count);
  ......
}

追溯 c1_count、c2_count 的计算逻辑,首先在 JVM 初始化的时候(Threads::create_vm -> init_globals -> compilationPolicy_init)要设置编译的策略 CompilationPolicy:

# hotspot/src/share/vm/runtime/arguments.cpp
void Arguments::set_tiered_flags() {
 // With tiered, set default policy to AdvancedThresholdPolicy, which is 3.
 if (FLAG_IS_DEFAULT(CompilationPolicyChoice)) {
    FLAG_SET_DEFAULT(CompilationPolicyChoice, 3);
  }
  ......
}
# hotspot/src/share/vm/runtime/compilationPolicy.cpp
// Determine compilation policy based on command line argument
void compilationPolicy_init() {
 CompilationPolicy::set_in_vm_startup(DelayCompilationDuringStartup);
 switch(CompilationPolicyChoice) {
  ......
 case 3:
#ifdef TIERED
 CompilationPolicy::set_policy(new AdvancedThresholdPolicy());
#else
 Unimplemented();
#endif
 break;
  ......
 CompilationPolicy::policy()->initialize();
}

此时我们默认开启了分层编译,所以 CompilationPolicyChoice 为 3 ,编译策略选用的是 AdvancedThresholdPolicy,查看相关源码(compilationPolicy_init -> AdvancedThresholdPolicy::initialize):

# hotspot/src/share/vm/runtime/advancedThresholdPolicy.cpp
void AdvancedThresholdPolicy::initialize() {
 // Turn on ergonomic compiler count selection
 if (FLAG_IS_DEFAULT(CICompilerCountPerCPU) && FLAG_IS_DEFAULT(CICompilerCount)) {
    FLAG_SET_DEFAULT(CICompilerCountPerCPU, true);
  }
 int count = CICompilerCount;
 if (CICompilerCountPerCPU) {
 // Simple log n seems to grow too slowly for tiered, try something faster: log n * log log n
 int log_cpu = log2_int(os::active_processor_count());
 int loglog_cpu = log2_int(MAX2(log_cpu, 1));
    count = MAX2(log_cpu * loglog_cpu, 1) * 3 / 2;
  }
  set_c1_count(MAX2(count / 3, 1));
  set_c2_count(MAX2(count - c1_count(), 1));
  ......
}

我们可以发现,在未手动设置 -XX:CICompilerCountPerCPU 和 -XX:CICompilerCount 这两个参数的时候,JVM 会启动 CICompilerCountPerCPU ,启动编译线程的数目会根据 CPU 数重新计算而不再使用默认的 CICompilerCount 的值(3),计算公式通常情况下为 log n * log log n * 1.5(log 以 2 为底),此时笔者使用的机器有 64 个 CPU,经过计算得出编译线程的数目为 18。计算出编译线程的总数目之后,再按 1:2 的比例分别分配给 C1、C2,即我们上文所求的 c1_count、c2_count。

使用 jinfo -flag CICompilerCount 来验证此时 JVM 进程的编译线程数目:

jinfo -flag CICompilerCount 
-XX:CICompilerCount=18

所以我们可以通过显式的设置 -XX:CICompilerCount 来控制 JVM 开启编译线程的数目,从而限制 Compiler 部分所使用的内存(当然这部分内存比较小)。

我们还可以通过 -XX:-TieredCompilation 关闭分层编译来降低内存使用,当然是否关闭分层编译取决于实际的业务需求,节省的这点内存实在微乎其微。

编译线程也是线程,所以我们还可以通过 -XX:VMThreadStackSize 设置一个更小的值来节省此部分内存,但是削减虚拟机线程的堆栈大小是危险的操作,并不建议去因为此设置这个参数。

Internal

Internal 包含命令行解析器使用的内存、JVMTI、PerfData 以及 Unsafe 分配的内存等等。

其中命令行解释器就是在初始化创建虚拟机时对 JVM 的命令行参数加以解析并执行相应的操作,如对参数 -XX:NativeMemoryTracking=detail 进行解析。

JVMTI(JVM Tool Interface)是开发和监视 JVM 所使用的编程接口。它提供了一些方法去检查 JVM 状态和控制 JVM 的运行,详情可以查看 JVMTI官方文档 [1]。

PerfData 是 JVM 中用来记录一些指标数据的文件,如果开启 -XX:+UsePerfData(默认开启),JVM 会通过 mmap 的方式(即使用上文中提到的 os::reserve_memory 和 os::commit_memory)去映射到 {tmpdir}/hsperfdata_/pid 文件中,jstat 通过读取 PerfData 中的数据来展示 JVM 进程中的各种指标信息.

需要注意的是,  {tmpdir}/hsperfdata_/pid  与{tmpdir}/.java_pid  并不是一个东西,
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jh035

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值