文章目录
内存泄露
- 内存泄露的原因
- 全局的容器类(如HashMap,或者自定义的容器类等),在对象不再需要时,忘记从容器中remove,这样这个对象就会仍然被HashMap等引用到,造成这个对象不满足垃圾回收的条件,从而造成内存泄漏。特别地,在抛出异常的时候,一定要确保remove被执行到。对集合对象(系统提供的或者自己实现的)只添加而不删除元素,在其它地方并保持了对集合对象的引用,是一种最常见的内存泄漏。
- 像Runnable对象等被JVM自身管理的对象,没有正确的释放渠道。Runnable对象必须交给一个Thread去run,否则该对象就永远不会消亡。因为像这种对象,尽管不被应用程序中的其它用户对象访问,但是这种对象会被JVM内部所引用
- 原子数据类型没有对象引用,因为所有的原子数据类型是放在一些对象里的,或者都是暂态临时对象。原子对象的复制,执行的是拷贝操作,而不是指向操作
- Java内存泄露的症状
- 系统越来越慢,并伴随CPU使用率过高。这主要是因为随着内存的泄漏,可用的内存越来越小,垃圾回收器频频进行垃圾回收(完全垃圾回收(FULL GC)一次接一次,每次耗时几秒,甚至几十秒),而垃圾回收一个CPU密集型操作,频繁的GC会导致CPU持续居高不下,在有内存泄漏的场合,到了最后必然是伴随着CPU使用率几乎为100
- 系统运行一段时间,系统抛OutOfMemory异常,至此整个系统完全不工作
- 虚拟机core dump
- 内存泄露的定位与分析:内存泄漏的分析过程并不复杂。但往往需要很大的耐心,因为内存泄漏的分析只能是事后分析,问题重现后才可以进行分析,常用的工具就是MAT
配置
-
配置页面
Keep unreachable objects
:分析的时候会包含dump文件中的不可达对象;Hide the getting started wizard
:隐藏分析完成后的首页,控制是否要展示一个对话框,用来展示内存泄漏分析、消耗最多内存的对象排序。Hide popup query help
:隐藏弹出查询帮助,除非用户通过F1或Help按钮查询帮助。Hide Welcome screen on launch
:隐藏启动时候的欢迎界面Bytes Display
:设置分析结果中内存大小的展示单位
-
Memory Analyzer
目前支持三种转储文件类型IBM Portable Heap Dump (PHD)
:这个专有的 IBM 格式只包含进程中每个 Java 对象的类型和大小,以及这些对象之间的关系。这个转储文件格式远远小于其他格式,并且只包含最少的信息。HPROF二进制转储文件
: HPROF 二进制转储文件在 IBM PHD 格式中包含了所有数据表现方式,以及 Java 对象和线程内部的基本数据类型,您可以查看对象中域的值,查看在转储文件产生时有哪些方法在被执行。这些数据使 HPROF 转储文件明显比 PHD 格式的转储文件要大;它们大约与所使用的 Java 堆一样大。一般sun公司系列的JVM生成的dump文件都是HPROF格式的DTFJ二进制转储文件
:IBM的JVM生成的dump文件时DTFJ格式的
基础概念入门
-
Shallow Heap(浅堆)
:Shallow Size
就是对象本身占用内存的大小,不包含其引用的对象(在32位系统中,一个对象引用会占据4个字节,一个int占4个字节,long占用8个字节,每个对象头占用8个字节)。常规对象(非数组)的Shallow Size
由其成员变量的数量和类型决定。数组的Shallow Size
由数组元素的类型(对象类型、基本类型)和数组长度决定- java的对象成员都是些引用,真正的内存都在堆上,看起来是一堆原生的byte[], char[], int[],所以我们如果只看对象本身的内存,那么数量都很小。所以我们看到Histogram图是以Shallow size进行排序的,排在第一位第二位的是byte,char 。
-
Retained Set(保留集)
:对于某个对象X来说,它的Retained Set
指的是——如果X被垃圾收集器回收了,那么这个集合中的对象都会被回收,同理,如果X没有被垃圾收集器回收,那么这个集合中的对象都不会被回收。 -
Leading Set
:对象X可能不止有一个,这些对象统一构成了Leading Set
。如果Leading Set
中的对象都不可达,那么这个Leading Set
对应的Retained Set
中的对象就会被回收 -
Retained Heap(保留堆)
:表示如果一个对象被释放掉,那会因为该对象的释放而减少引用进而被释放的所有的对象(包括被递归释放的)所占用的heap大小。于是,如果一个对象的某个成员new了一大块int数组,那这个int数组也可以计算到这个对象中。相对于Shallow Heap
,Retained Heap
可以更精确的反映一个对象实际占用的大小(因为如果该对象释放,Retained Heap
都可以被释放)
对于以上的图,看似可以得出结论每个对象的Retained Size
=Shallow Size
+直接子对象的 Retained Size
。对于以下的图,Obj2的Retained Size
不再符合刚刚总结出来的公式,这是因为Obj2的直接子对象Obj5还被Obj6所引用,造成的结果就是,如果Obj2被回收,Obj5并不会被回收,所以 Obj2 的Retained Size
就不应该包括 Obj5 的Retained Size
. 虽然 Obj2 的 Retained Size 发生了变化,但是 Obj1 的Retained Size
并没有发生变化。
针对不可达对象(下一次GC会被清理的对象),也就是可以完全被清除的对象,Retained Size 都是0
实际案例分析
-
模拟产生内存泄露的代码
/** * 运行时增加jvm参数 * -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/Volumes/P/temp/OomHeapForMat.hprof.dump -Xms20M -Xmx20M * **/ public class OomHeapForMat { public static void main(String[] args) { ArrayList