ThreadMXBean.getThreadAllocatedBytes(long id)中包含了哪些JVM运行时数据区

What JVM runtime data area are included in ThreadMXBean.getThreadAllocatedBytes(long id)

我试图获取一些代码片段的内存消耗。经过一番搜索,我意识到 ThreadMXBean.getThreadAllocatedBytes(long id) 可以用来实现这一点。所以我用下面的代码测试了这个方法:

    ThreadMXBean threadMXBean = (ThreadMXBean) ManagementFactory.getThreadMXBean();
    long id = Thread.currentThread().getId();
//      new Long(0);
    long beforeMemUsage = threadMXBean.getThreadAllocatedBytes(id);
    long afterMemUsage = 0;

    {
        // put the code you want to measure here
        for (int i = 0; i < 10; i++) {
            new Long(i);
        }
    }

    afterMemUsage = threadMXBean.getThreadAllocatedBytes(id);
    System.out.println(afterMemUsage - beforeMemUsage);

I 运行 此代码在 for 循环中具有不同的迭代次数(0、1、10、20 和 30)。结果如下:

0 Long: 48 bytes
1 Long: 456 bytes
10 Long:    672 bytes
20 Long:    912 bytes
30 Long:    1152 bytes

1和10、10和20、20和30的区别很好解释,因为Long对象的大小是24字节。但我对 0 和 1 之间的巨大差异感到困惑。 实际上,我猜这是由 class 加载引起的。所以我取消了第3行代码的注释,结果如下:

0 Long: 48 bytes
1 Long: 72 bytes
10 Long:    288 bytes
20 Long:    528 bytes
30 Long:    768 bytes

看来结果证实了我的猜测。但是,在我看来,class结构体的信息是存放在方法区的,不属于堆内存的一部分。正如 ThreadMXBean.getThreadAllocatedBytes(long id) 的 Javadoc 所示,它 returns the total amount of memory allocated in heap memory。我错过了什么吗?

测试的JVM版本为:

java version "1.8.0_131"
Java(TM) SE Runtime Environment (build 1.8.0_131-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.131-b11, mixed mode)

谢谢!

new Long(0) 的第一次调用导致 new 字节码引用的常量池条目的解析。第一次解析 CONSTANT_Class_info 时,JVM 加载引用的 class - java.lang.Long.

ClassLoader.loadClass is implemented in Java, and it can certainly allocate Java objects. For instance, getClassLoadingLock 方法在 parallelLockMap:

中创建一个新的锁对象和一个新条目
    protected Object getClassLoadingLock(String className) {
        Object lock = this;
        if (parallelLockMap != null) {
            Object newLock = new Object();
            lock = parallelLockMap.putIfAbsent(className, newLock);
            if (lock == null) {
                lock = newLock;
            }
        }
        return lock;
    }

此外,在系统字典中进行 class 名称查找时,JVM 会创建一个新的 String 对象。

我使用 async-profiler 记录 JVM 在加载 java.lang.Long class 时所做的所有堆分配。这是可点击的交互式火焰图:

该图包括 13 个样本 - 每个分配的对象一个。已分配对象的类型未显示,但可以从上下文(堆栈跟踪)中轻松猜出。

  • 绿色表示 Java 堆栈跟踪;
  • 黄色表示 VM 堆栈跟踪。

请注意,每个 java_lang_String::basic_create()(和类似的)分配两个对象:java.lang.String 的实例及其后备 char[] 数组。

该图由以下测试程序生成:

import one.profiler.AsyncProfiler;

public class ProfileHeapAlloc {

    public static void main(String[] args) throws Exception {
        AsyncProfiler profiler = AsyncProfiler.getInstance();

        // Dry run to skip allocations caused by AsyncProfiler initialization
        profiler.start("_ZN13SharedRuntime19dtrace_object_allocEP7oopDesci", 0);
        profiler.stop();

        // Real profiling session
        profiler.start("_ZN13SharedRuntime19dtrace_object_allocEP7oopDesci", 0);

        new Long(0);

        profiler.stop();
        profiler.execute("file=alloc.svg");
    }
}

如何运行:

java -Djava.library.path=/path/to/async-profiler -XX:+DTraceAllocProbes ProfileHeapAlloc

此处 _ZN13SharedRuntime19dtrace_object_allocEP7oopDescimangled name for SharedRuntime::dtrace_object_alloc() 函数,只要 DTraceAllocProbes 标志打开,JVM 就会为每个堆分配调用它。