这个 native 方法对应的 JVM 源码是:
ThreadImpl.c
management.cpp
JVM_ENTRY(jlong, jmm_GetOneThreadAllocatedMemory(JNIEnv *env, jlong thread_id)) if (thread_id < 0) { THROW_MSG_(vmSymbols::java_lang_IllegalArgumentException(), "Invalid thread ID", -1); } //获取当前线程 if (thread_id == 0) { // current thread //调用 cooked_allocated_bytes,和采集 jdk.ThreadAllocationStatistics 调用的底层方法一样 return thread->cooked_allocated_bytes(); } //根据线程号获取线程 ThreadsListHandle tlh; JavaThread* java_thread = tlh.list()->find_JavaThread_from_java_tid(thread_id); if (java_thread != NULL) { //调用 cooked_allocated_bytes,和采集 jdk.ThreadAllocationStatistics 调用的底层方法一样 return java_thread->cooked_allocated_bytes(); } return -1; JVM_END可以看出,其实底层调用的和 JFR 采集 jdk.ThreadAllocationStatistics 事件一样,都是调用 Thread 的 cooked_allocated_bytes() 方法。
针对这个 JFR 事件的一些思考首先,提出一个观点,jdk.ThreadAllocationStatistics 这个事件并不太消耗性能。原因有二:
统计线程分配大小是近似统计,并不需要进入全局安全点统计。同时也代表,jdk.ThreadAllocationStatistics 同一时间的不同线程事件的分配大小实际并不是同一时间点的,因为没有进入安全点暂停所有线程。
统计仅仅是原子读取每个线程的分配对象大小这个变量,之后加上每个线程当前 TLAB 分配对象大小(这个大小是通过读取两个指针地址获取的),可以看出操作是很轻量级的。即使有很多线程,也不会增加多少性能负担。
然后,默认配置的采集周期,并不能满足我们的需求。默认的采集周期是 everyChunk,默认的 chunk 大小(maxchunksize)是 12M,也就是每采集 12M 的 JFR 事件之后,采集一次 jdk.ThreadAllocationStatistics。这是不太可控的,我一般配置为每过 5s 采集一次。这样对于我们上面提到的那两个需要这个事件的场景也是很适合的。
微信搜索“我的编程喵”关注公众号,加作者微信,每日一刷,轻松提升技术,斩获各种offer: