JVM对象分配与程序崩溃排查
一、new
对象在 JVM 中的过程
在 JVM 中通过 new
关键字创建对象时,会经历以下步骤:
-
内存分配
对象的内存分配在 堆(Heap) 中,优先在 新生代(Young Generation) 的 Eden 区 分配。分配方式取决于堆内存是否规整:- 指针碰撞(Bump the Pointer):适用于内存规整的堆(如使用
-XX:+UseSerialGC
)。 - 空闲列表(Free List):适用于内存不规整的堆(如使用
-XX:+UseCMSGC
)。
- 指针碰撞(Bump the Pointer):适用于内存规整的堆(如使用
-
初始化零值
内存分配完成后,JVM 会将对象的所有字段初始化为零值(如int
初始化为 0,引用初始化为null
)。 -
设置对象头(Object Header)
对象头包含以下信息:- Mark Word:哈希码、GC 分代年龄、锁状态标志等。
- 类型指针:指向方法区中对象所属类的元数据。
-
执行构造函数(
<init>
)
调用对象的构造函数(用户编写的new
后的代码),完成字段的显式初始化。
二、对象从新生代晋升到老生代的条件
对象从新生代进入老生代的几种情况:
-
年龄阈值(MaxTenuringThreshold)
默认情况下,对象每经历一次 Minor GC 并存活,年龄加 1。当年龄超过阈值(默认15
,通过-XX:MaxTenuringThreshold
设置)时,晋升到老年代。 -
大对象直接进入老年代
通过-XX:PretenureSizeThreshold
设置阈值(如1MB
),大于该值的对象直接在老年代分配(避免在 Eden 区复制)。 -
Survivor 区动态年龄判断
如果 Survivor 区中 相同年龄的所有对象大小总和超过 Survivor 区的一半,则年龄大于等于该值的对象直接晋升到老年代。 -
Minor GC 后 Survivor 区空间不足
当 Survivor 区无法容纳 Minor GC 后存活的对象时,会通过 分配担保机制(Handle Promotion) 直接将对象转移到老年代。
三、CPU 和内存正常,但程序崩溃的排查方法
即使 CPU 和内存正常,程序崩溃仍可能由以下原因导致:
1. 内存泄漏或 OOM(OutOfMemoryError)
- 检查 JVM 日志:搜索
OutOfMemoryError
或java.lang.StackOverflowError
。 - 堆转储分析:通过
jmap -dump:format=b,file=heap.hprof <pid>
导出堆快照,用工具(如 MAT、VisualVM)分析内存泄漏。 - 元空间泄漏:检查是否有类加载器泄漏(如频繁生成动态类)。
2. 死锁或线程阻塞
- 线程转储分析:通过
jstack <pid>
或kill -3 <pid>
获取线程快照,检查是否有BLOCKED
状态的线程或死锁。 - 示例死锁日志:
Found one Java-level deadlock: Thread 1 waiting to lock Monitor@0x00007fcdd8003e58 (Object 0x000000076ab00000), Thread 2 holding Monitor@0x00007fcdd8003e58 (Object 0x000000076ab00000)
3. JVM 崩溃(Native 层错误)
- 检查
hs_err_pid<pid>.log
:JVM 崩溃时会生成错误日志,记录 Native 层错误(如 SIGSEGV)。 - 常见原因:JNI 代码错误、操作系统资源耗尽(如文件句柄数限制)。
4. 外部依赖故障
- 数据库连接池耗尽:检查日志中是否有
Connection pool is full
。 - 外部服务超时:通过链路追踪工具(如 SkyWalking)分析调用链。
5. 程序主动退出
- 检查代码中的
System.exit()
:是否有逻辑错误调用System.exit(0)
。 - 信号处理:检查是否捕获到
SIGTERM
或SIGINT
信号(如kill -15 <pid>
)。
6. 资源泄漏
- 文件句柄泄漏:通过
lsof -p <pid>
查看进程打开的文件数。 - Socket 泄漏:通过
netstat -anp | grep <pid>
检查未关闭的连接。
四、排查工具汇总
工具/命令 | 用途 |
---|---|
jstack <pid> | 获取线程快照,分析死锁/阻塞 |
jmap -heap <pid> | 查看堆内存分配情况 |
jstat -gcutil <pid> | 监控 GC 频率和耗时 |
jcmd <pid> GC.heap_dump | 生成堆转储文件 |
vmstat 1 | 监控系统资源(CPU、内存、IO) |
strace -p <pid> | 跟踪系统调用和信号 |
五、总结
- 对象分配:优先在 Eden 区,通过 GC 年龄或大对象策略进入老年代。
- 程序崩溃排查:优先检查 JVM 日志、线程快照、堆转储和系统资源限制。