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 并不是一个东西,