Java OOM(Out of Memory)是一个常见的问题,通常意味着Java虚拟机中的可用内存不足以支持正在运行的应用程序。这可能是由于应用程序内存泄漏、内存使用过多、或者JVM参数设置不当等原因导致的。解决Java OOM问题的方法包括分析内存泄漏源,使用内存分析工具来检测内存泄漏,调整JVM参数,增加可用内存,或者优化应用程序代码以减少内存使用。
1. 现象
Java进程运行过程中,日志输出:OutOfMemoryError:Java heap space
2. 原因和解决方案
出现OOM情况,通常分两方面来看:
- 无系统Bug:JVM设置的堆太小
- 解决方案:分析dump文件,如无单个类实例特别夸张,则加大堆的设置
- 有系统Bug:内存发生泄漏
- 解决方案:分析dump文件,找出实例数特别多的类,以及引用关系,定位到源码
3. 实践案例
3.1. 导出Java堆快照
- 方式一:通过告警通知上去导heap快照(推荐方案)
- 设置告警
- 堆使用率超过70%时告警,如果一直告警说明堆一直无法回收,这个时候上去dump堆快照
- 导heap快照
- 查看堆内存占用概况
- jmap -heap 进程号
- 查看堆中对象的统计信息
- jmap -histo 进程号 | head -n 100
- 导出堆快照
- jmap -dump:format=b,file=/tmp/进程号_jmap_dump.hprof 进程号
- 导出堆快照(只包含无法回收的对象)
- jmap -dump:live,format=b,file=/tmp/进程号_live_jmap_dump.hprof 进程号
- 说明:堆的全部数据,生成的文件较大,“dump:live,这个参数表示我们需要抓取目前在生命周期内的内存对象,也就是说GC收不走的对象,一般用这个就行。拿到出现问题的快照数据,然后重启服务。
- 方式二:进程挂掉后,自动导出heap快照(不推荐)
- 设置OOM后,自动dump出堆快照
- java进程启动加jvm参数
- -XX:+HeapDumpOnOutOfMemoryError
- -XX:HeapDumpPath=/tmp/heapdump.hprof
3.2. 下载MAT并启动
- 下载
- 使用 Eclipse Memory Analyzer 工具。下载地址:www.eclipse.org/mat/download
- 启动MAT设置内存
- 建议要大于dump文件的内存
3.3. 导入dump,生成内存泄漏报告
生成的内存泄漏报告,表达的意思是MultiSpanProcessor类总共占用了94%的内存,其中引用的CurrentHashMap就占用了89%
3.4. 查看调用树
通过树形结构,很直观展现了依赖关系,MultiSpanProcessor->CurrentHashMap导致OOM
4. 关键概念
4.1. 堆里什么对象会被回收?
即从GC Root无法到达的对象,都有可能被下次gc回收
objects which would be removed at the next garbage collection.These are objects which are unreachable from the garbage collection roots.
4.2. GC Root有哪些?
GC Root是在堆外的一个对象,来指向堆内,GC root可以是:
A garbage collection root is an object that is accessible from outside the heap. The following reasons make an object a GC root:
- System Class
- Class loaded by bootstrap/system class loader. For example, everything from the rt.jar like java.util.*
- JNI Local
- Local variable in native code, such as user defined JNI code or JVM internal code.
- JNI Global
- Global variable in native code, such as user defined JNI code or JVM internal code.
- Thread Block
- Object referred to from a currently active thread block.
- Thread
- A started, but not stopped, thread.
- Busy Monitor
- Everything that has called wait() or notify() or that is synchronized. For example, by calling synchronized(Object) or by entering a synchronized method. Static method means class, non-static method means object.
- Java Local
- Local variable. For example, input parameters or locally created objects of methods that are still in the stack of a thread.
- Native Stack
- In or out parameters in native code, such as user defined JNI code or JVM internal code. This is often the case as many methods have native parts and the objects handled as method parameters become GC roots. For example, parameters used for file/network I/O methods or reflection.
- Finalizable
- An object which is in a queue awaiting its finalizer to be run.
- Unfinalized
- An object which has a finalize method, but has not been finalized and is not yet on the finalizer queue.
- Unreachable
- An object which is unreachable from any other root, but has been marked as a root by MAT to retain objects which otherwise would not be included in the analysis.
- Java Stack Frame
- A Java stack frame, holding local variables. Only generated when the dump is parsed with the preference set to treat Java stack frames as objects.
- Unknown
- An object of unknown root type. Some dumps, such as IBM Portable Heap Dump files, do not have root information. For these dumps the MAT parser marks objects which are have no inbound references or are unreachable from any other root as roots of this type. This ensures that MAT retains all the objects in the dump.
4.3. Shallow 和 Retained Heap有什么区别?
Shallow Heap和Retained Heap是Java内存分析中常用的两个概念,它们用于描述对象在堆内存中所占用的空间以及对象保留在内存中的大小。
Shallow Heap指的是对象本身在堆内存中所占用的空间大小,不包括对象引用的其他对象所占用的空间。例如,一个空的Java对象的浅堆大小为16字节(对象头大小),而一个包含一个整数字段的Java对象的浅堆大小为24字节(对象头大小+整数字段大小)。
Retained Heap指的是一个对象及其所有子对象在内存中保留的总大小。这包括直接或间接引用对象的其他对象所占用的空间大小。例如,当一个对象被垃圾回收器回收时,如果它所引用的对象仍然被其他对象引用,则这些对象的大小也会被计算在内。因此,保留堆大小可以更好地了解对象在内存中实际占用的空间大小,特别是在存在循环引用的情况下。
因此,Shallow Heap和Retained Heap的区别在于,Shallow Heap只计算对象本身在内存中所占用的空间大小,而Retained Heap则计算对象及其子对象在内存中保留的总大小。
在内存分析中,通常会使用Shallow Heap来识别内存中最大的对象,而使用Retained Heap来识别内存泄漏和优化内存使用。Retained Heap可以帮助我们找到占用大量内存的对象及其引用链,从而确定哪些对象需要进行优化或释放。