Windows下JVM崩溃

XxxApp JVM 崩溃分析报告

1. 崩溃文件统计

共收集 10 个崩溃日志文件:

文件名 失败映射大小 可用物理内存 运行时间
hs_err_pid226688.log 6.4 MB 3517 MB 1h 4m 48s
hs_err_pid167320.log 558 MB 5489 MB -
hs_err_pid182888.log 644 MB 3748 MB -
hs_err_pid244348.log 1121 MB 5413 MB -
hs_err_pid323564.log 1380 MB 3678 MB 3m 5s
hs_err_pid310304.log 1225 MB 3633 MB 9m 8s
hs_err_pid276796.log 1522 MB 3736 MB 1m 31s
hs_err_pid142912.log 1760 MB 5074 MB -
hs_err_pid231664.log 2042 MB 5355 MB -
hs_err_pid295504.log 2135 MB 2720 MB 50m 44s

2. 崩溃根本原因

2.1 错误类型

原生内存分配失败(Native OOM),不是 Java 堆内存溢出。

# There is insufficient memory for the Java Runtime Environment to continue.
# Native memory allocation (mmap) failed to map XXX bytes for G1 virtual space
# Out of Memory Error (os_windows.cpp:3548)

2.2 崩溃触发时机

所有崩溃都在 VMThread 执行 G1TryInitiateConcMark 操作时发生:

  • G1 GC 尝试启动并发标记
  • 需要扩展 G1 虚拟空间
  • mmap 映射请求被操作系统拒绝

2.3 核心问题:Compressed Oops + 地址空间限制

关键配置信息:

  • Compressed Oops mode: Zero based(零基压缩)
  • Heap address: 0x0000000605000000
  • Heap Max Capacity: 8112 MB (自动计算)
  • Oop shift amount: 3

问题本质:
JVM 使用 Zero Based Compressed Oops 模式,将 Java Heap 放在前 32GB 地址空间。Java Heap 基地址成为 native heap 增长的上限。当 G1 GC 需要扩展虚拟空间时,操作系统无法在前 32GB 地址空间找到足够大的连续虚拟地址区域。

3. 环境信息

项目
JVM 版本 Java 17.0.10+11 LTS
JVM 类型 HotSpot 64-Bit Server VM
GC 算法 G1 GC
操作系统 Windows 10 Build 17763
CPU Intel i7-12700 (20 cores)
物理内存 32 GB (实际 32443 MB)
可用内存 2.7-5.5 GB (严重不足)
应用 com.xxx.app.Main
线程数 84-88 个 Java 线程

4. 内存分析

4.1 JVM 内存配置(未指定,自动计算)

MaxHeapSize (Xmx)  = 8506048512 = 8112 MB (自动)
InitialHeapSize = 532676608 = 508 MB (自动)
G1HeapRegionSize = 4 MB

4.2 实际内存使用(崩溃时)

  • Java Heap 使用: ~330-340 MB (很低,不是问题)
  • Metaspace: ~59 MB
  • Class space: ~8 MB
  • CodeCache: ~60 MB

结论:Java Heap 内存使用正常,崩溃与 Java 堆无关。

4.3 系统内存状态

物理内存 32 GB,但可用仅 2.7-5.5 GB,说明:

  • 系统运行了大量其他进程
  • 虚拟地址空间碎片化严重
  • Windows 10 的虚拟地址空间管理问题

5. 崩溃模式分析

5.1 共同特征

  1. 所有崩溃都是 G1 虚拟空间扩展失败
  2. 失败大小从 6 MB 到 2135 MB,差异巨大
  3. 可用物理内存 都在 2.7-5.5 GB 范围
  4. 系统物理内存 32 GB,但可用严重不足

5.2 特殊案例

  • hs_err_pid226688.log: 仅请求 6.4 MB 就失败,说明虚拟地址空间极度碎片化
  • hs_err_pid295504.log: 运行最长 (50分钟),请求 2135 MB 失败

6. 根因定位

6.1 直接原因

Windows 10 无法在前 32GB 地址空间找到连续虚拟内存区域,导致 mmap 失败。

6.2 技术原因

Zero Based Compressed Oops 限制:

  • 为启用压缩指针,JVM 将 Java Heap 放在低地址 (前 32GB)
  • Java Heap 基地址 (8 GB) 成为 native heap 上限
  • G1 GC 的虚拟空间预留也必须在前 32GB
  • Windows 虚拟地址空间碎片化,无法找到大块连续区域

6.3 环境因素

  1. Windows 10 虚拟地址空间碎片化

    • 长时间运行导致碎片
    • DLL 加载占用地址空间
  2. 系统内存压力

    • 32 GB 物理内存,但可用仅 3-5 GB
    • 其他进程占用大量内存
  3. JVM 自动配置过大

    • 自动计算 Xmx=8 GB
    • 导致 Heap 地址偏高,压缩 native heap 空间

7. 解决方案

7.1 上策(推荐)

使用 -XX:HeapBaseMinAddress 参数:

java -XX:HeapBaseMinAddress=8g -Xmx4g com.xxx.app.Main

原理: 将 Java Heap 基地址设为 8GB,强制 Heap 放在地址空间高位,释放前 32GB 地址空间给 native heap 使用。

7.2 中策

限制最大堆大小:

java -Xmx4g -Xms512m com.xxx.app.Main

原理: 降低 Heap 地址,为 native heap 保留更多低地址空间。

7.3 下策

禁用 Compressed Oops:

java -XX:-UseCompressedOops -Xmx4g com.xxx.app.Main

注意: 会增加内存占用(指针从 4 字节变 8 字节),性能下降约 5-10%。

7.4 系统层面

  1. 增加 Swap 空间

    • Windows 默认 swap 可能不足
    • 增加虚拟内存页面文件大小
  2. 重启系统

    • 清理地址空间碎片
    • 释放被占用的虚拟地址
  3. 关闭其他进程

    • 减少系统内存压力

8. 最佳配置建议

java \
-XX:HeapBaseMinAddress=8g \
-Xmx4g \
-Xms1g \
-XX:MaxMetaspaceSize=256m \
-XX:+UseG1GC \
-XX:G1HeapRegionSize=4m \
-Dfile.encoding=UTF-8 \
com.xxx.app.Main

参数说明:

  • HeapBaseMinAddress=8g: Heap 基地址从 8GB 开始,避开低地址空间
  • Xmx4g: 限制最大堆为 4GB,为 native heap 留空间
  • Xms1g: 初始堆 1GB,避免动态扩展开销
  • MaxMetaspaceSize=256m: 限制元空间大小

9. 风险评估

方案 风险 效果
HeapBaseMinAddress 低,仅影响地址布局 高,彻底解决
降低 Xmx 中,可能影响业务 中,缓解问题
禁用 CompressedOops 高,性能下降 低,得不偿失

10. 结论

XxxApp 应用崩溃的根本原因是 Windows 虚拟地址空间碎片化 + Zero Based Compressed Oops 地址限制,导致 G1 GC 无法扩展虚拟空间。

最有效的解决方案:使用 -XX:HeapBaseMinAddress=8g 参数。

这是 JVM 在 Windows 平台的常见问题,尤其在长时间运行的系统上更容易发生。

11. 参考