如何在不使用GC日志的情况下监控Java程序中的巨型分配
我目前正在开发一个使用巨型分配(humongous allocations)和内存区域(regions)的Java应用。通常我会通过GC(垃圾回收)日志监控这些分配和区域,但现在我希望找到替代方法,能够像通过MemoryPoolMXBean查看Eden/Survivor/Old区属性那样,在JVM中打印出巨型分配信息。
GC日志示例
G1 Eden Space memory used: 0 (0.0%) G1 Old Gen memory used: 1077833728 (82.1%) G1 Survivor Space memory used: 262128 (12.5%) [0.905s] [info] [gc, start ] GC (2) Pause Young (Concurrent Start) (G1 Humongous Allocation) [0.906s] [info] [gc, task [0.907s] [info] [gc, phases 1 GC(2) Using 8 workers of 8 for evacuation 1 GC (21 [0.907s] [info] [g, phases GC (2) Merge Heap Roots: 0.1ms [0.907s] [info] [gc, phases GC (2) Evacuate Collection Set: 0.4ms [0.907s] [info] [gc, phases GC (2) Post Evacuate Collection Set: 0.3ms [0.907s] [info] [gc, phases ] GC (2) Other: 0.3ms [0.907s] [info] [gc, heap ] GC (2) Eden regions: 1->0 (32) [0.907s] [info] [gc, heap GC(2) Survivor regions: 1->1(2) [0.907s] [info] [gc, heap GC (2) Old regions: 0->0 [0.907s] [info] [gc, heap ] GC (2) ArchIve regIOnS: 2->2 [0.907s] [infol [gc, heap ] GC (2) Humongous regions: 513->513 [0.907s] [info] [g, metaspace] GC(2) Metaspace: 705K(896K)->705K (396K) NonClass: 637K(768K)->637K(768K) Class: 68K(128K) ->68K(128K) [0.907s] [infol [ac GC (2) Pause Young (Concurrent Start) (G1 Humongous Allocation) 1029M->1028M(1284M) 1.180ms
我的情况:
- 开发的Java程序存在巨型分配现象
- 熟悉通过GC日志监控这些分配和区域
请问是否存在无需使用GC日志,就能打印或监控Java程序中巨型分配及区域的方法?若有可提供该信息的API或技术,我将非常感激。
当然有几种无需依赖GC日志的方法来监控G1的巨型分配和区域:
1. 使用HotSpotDiagnosticMXBean获取堆统计
你可以通过HotSpot私有扩展的HotSpotDiagnosticMXBean触发堆信息查询,其中包含巨型区域的相关统计。示例代码如下:
import com.sun.management.HotSpotDiagnosticMXBean; import javax.management.MBeanServer; import java.lang.management.ManagementFactory; import java.util.Map; public class HumongousMonitor { public static void main(String[] args) throws Exception { MBeanServer server = ManagementFactory.getPlatformMBeanServer(); HotSpotDiagnosticMXBean bean = ManagementFactory.newPlatformMXBeanProxy( server, "com.sun.management:type=HotSpotDiagnostic", HotSpotDiagnosticMXBean.class); Map<String, Object> heapStats = bean.getHeapUsage(); System.out.println("巨型区域数量: " + heapStats.get("humongousRegionsCount")); System.out.println巨型区域使用量: " + heapStats.get("humongousRegionsUsed")); } }
注意:该API仅适用于Oracle/OpenJDK的HotSpot实现,属于非标准扩展。
2. 借助G1专属内部JMX MBean
标准GarbageCollectorMXBean不直接暴露巨型数据,但可以通过HotSpot的内部MBean(如com.sun.management:type=G1YoungGeneration)获取区域信息,这类MBean提供了getHumongousRegionCount()等方法。示例代码:
import javax.management.MBeanServer; import javax.management.ObjectName; import java.lang.management.ManagementFactory; public class G1HumongousMonitor { public static void main(String[] args) throws Exception { MBeanServer server = ManagementFactory.getPlatformMBeanServer(); ObjectName g1YoungName = new ObjectName("com.sun.management:type=G1YoungGeneration"); long humongousCount = (Long) server.getAttribute(g1YoungName, "HumongousRegionCount"); System.out.println("当前巨型区域数量: " + humongousCount); } }
内部MBean的命名和方法可能随JDK版本略有变化,需针对目标版本验证。
3. 用JFR(Java Flight Recorder)低开销监控
JFR可以在几乎不影响性能的情况下捕获巨型分配事件:
- 启动程序时添加参数:
-XX:StartFlightRecording=duration=60s,filename=humongous.jfr - 用
jfr analyze humongous.jfr生成报告,其中会包含巨型分配的次数、大小、触发的GC等信息 - 也可以通过
jdk.jfr.consumer.RecordingFileAPI编程解析JFR文件,提取自定义监控数据
4. Java Agent字节码插桩
通过Java Agent技术,在对象分配的字节码处插入判断逻辑:检测分配大小是否达到巨型阈值(通常是G1区域大小的一半),从而实时统计巨型分配情况。这种方式需要使用ASM等字节码操作库,适合自定义监控需求。
补充说明
- 上述方法均依赖HotSpot JVM,若使用OpenJ9等其他JVM实现需调整方案
- 内部MBean和
HotSpotDiagnosticMXBean属于非标准API,JDK版本升级时需注意兼容性
内容的提问来源于stack exchange,提问作者dhanush427

