我试图获取一些代码片段的内存消耗。经过一番搜索,我意识到可以使用 ThreadMXBean.getThreadAllocationBytes(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);
我在 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之间的巨大差异让我感到困惑。 实际上,我猜这是由类加载引起的。所以我取消了第3行代码的注释,结果如下:
0 Long: 48 bytes
1 Long: 72 bytes
10 Long: 288 bytes
20 Long: 528 bytes
30 Long: 768 bytes
看来结果证实了我的猜测。然而,在我看来,类结构的信息存储在方法区中,它不属于堆内存的一部分。正如 ThreadMXBean.getThreadAllocatedBytes(long id) 的 Javadoc 所示,它返回堆内存中分配的内存总量。我错过了什么吗?
测试的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 会加载引用的类 - java.lang.Long
。
ClassLoader.loadClass
是用Java实现的,当然可以分配Java对象。例如,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;
}
此外,当在系统字典中查找类名时,JVM 会创建一个新的 String 对象。
我用了async-profiler记录 JVM 在加载 java.lang.Long 类时执行的所有堆分配。这是可点击的交互式火焰图:
该图包括 13 个样本 - 每个分配的对象一个。分配的对象的类型未显示,但可以从上下文(堆栈跟踪)轻松猜测。
- 绿色表示 Java 堆栈跟踪;
- 黄色表示虚拟机堆栈跟踪。
请注意,每个 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_allocEP7oopDeci
是mangled name对于 SharedRuntime::dtrace_object_alloc()
函数,只要 DTraceAllocProbes
标志打开,JVM 就会为每个堆分配调用该函数。
关于java - ThreadMXBean.getThreadAllocationBytes(long id) 中包含哪些 JVM 运行时数据区域,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59310446/