本课时承接上一篇JVM内核—JVM内存模型,将详细介绍JVM GC原理。如上节所示,JVM内存被划分为5个区域,栈区、JVM程序计数器空间由JVM执行引擎负责管理,本地方法栈由操作系统负责管理,因此需要垃圾回收的区域包括堆区和方法区(永久代区,JDK8以后为MetaSpace)。
目录:
1.内存泄漏
package com.baidu;
public class MemoryLeakTest {
public static void main(String[] args) throws InterruptedException {
System.out.println(MemoryLeak.ml);//null
MemoryLeak.ml = new MemoryLeak();
System.out.println(MemoryLeak.ml);//com.baidu.MemoryLeak@15db9742
MemoryLeak.ml=null;
System.gc();//Memory leak occurs......会调用对象的finalize方法
Thread.sleep(100);
System.out.println(MemoryLeak.ml);//com.baidu.MemoryLeak@15db9742
}
}
class MemoryLeak {
public static MemoryLeak ml = null;
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("Memory leak occurs......");
ml = this;
}
}
2.堆区(Heap Space)的GC
2.1 GC过程
- 上图所示为堆区的内存结构,可以看到堆区由年轻代区(Young)和年老代区(Tenured)组成,年轻代区又包括Eden区和2个Survivor区(Survivor to和Survivor from)。
- 新new出来的对象一般存放在Eden区(当Eden区刚好满时,会直接存放于其中一个Survivor区)。
- 当Eden区满时,会进行一次Minor GC,回收掉Eden区98%以上的内存空间(因为新new出来的对象很多都是朝生夕死的)。
- 这时没有被回收的对象会被转移到其中的一块Survivor区(假设为Survivor from区),下一次Minor GC Eden区时,Eden区中仍然存活的对象会连同Survivor from区中的对象一起被转移到Survivor to区,并将由Survivor from区中转移过来的对象的age设置为1。(若Suivivor区满,则会直接复制到年老代区)。
- 下一次Minor GC时,重复第4步动作,以后这些没有被回收的对象在Survivor区中每熬过一次Minor GC,就将对象的年龄加1。(可以看到每一时刻总有一个Survivor区是空闲的,用来做复制容器,用到了复制算法,等会后面会讲到)。
- 当对象的年龄达到某个阈值(默认是15,可以通过参数-XX:MaxTenuringThreshold来设定),这些对象就会进入年老代区(对于一些较大的对象,则会直接进入年老代区)。
- 如果任意一个Survivor区满后,则会将其中的对象整个复制到年老代区。
- 当年老代区满后,触发Full GC,这时会同时对年轻代区,年老代区以及永久代区进行垃圾回收。
2.2 GC算法
2.1.1垃圾对象判定方法
2.1.1.1引用记数法
- 定义:当一个对象被创建的时候,会为该对象生成一个引用计数器,每当有新的引用时,引用计数器的值+1,每当一个引用销毁时,计数器的值-1,当计数器的值归0时,则标志着该对象为垃圾对象。此种算法在JDK1.2之前的版本中被广泛使用。
- 问题:当出现循环引用(即objA.obj=objB; objB.obj=objA),并且指向objA和objB的所有其他引用都销毁后,objA和objB还有一个相互的引用,引用计数器的值都为1,而实际上这两个对象都已经是垃圾对象了,但是由于引用计数永远无法归0,所以永远无法被回收。
2.1.1.2根搜索法
- 定义:从离散数学中的图论引入,从初始节点(GC Root)开始寻找引用节点,形成引用链树,一直到叶子节点为止,当一个对象没有被引用链连接时,说明此对象不可达,即可判定为垃圾对象。
- Java中可作为GC Root 的对象包括:
1、 虚拟机栈中引用的对象(本地变量表)
2、 方法区中静态属性引用的对象
3、 方法区中常量引用的对象
4、 本地方法栈中引用的对象(Native对象)
黄色为垃圾对象
2.1.2Java中的4种引用类型
2.1.2.1强引用
- 定义:只要引用存在,就永远不会被回收。
- 举例:
package com.baidu; public class Referrence { public static void main(String[] args) { Object obj = new Object(); obj.equals(new Object());//obj对象对参数new Object()形成强引用,只有当obj这个引用被释放之后,new Object()对象才会被释放掉,这也是我们经常所用到的编码形式 } }
2.1.2.2软引用
- 定义:内存溢出时强制回收。
- 举例:
package com.baidu; import java.lang.ref.SoftReference; public class Referrence { public static void main(String[] args) { Object obj = new Object(); SoftReference<Object> sf = new SoftReference<Object>(obj);//sf是obj的一个软引用。 obj = null; System.gc(); System.out.println(sf.get());//输出java.lang.Object@15db9742,有时候会返回null(当且仅当内存溢出时)。 } }
- 用途:软引用主要用来实现类似缓存的功能,在内存足够的情况下直接通过软引用取值,提升速度;当内存不足时,自动删除这部分缓存数据。
2.1.2.3弱引用
- 定义:一旦垃圾收集器运行并且扫描到只有该弱引用指向的对象时,就会毫不留情的删除。
- 举例:
package com.baidu; import java.lang.ref.WeakReference; public class Referrence { public static void main(String[] args) { Object obj = new Object(); WeakReference<Object> wf = new WeakReference<Object>(obj);//wf是obj的一个弱引用 obj = null; //System.gc();若加上本句,则下面方法将输出null System.out.println(wf.get());//java.lang.Object@15db9742 } }
2.1.2.4幻影引用
- 本节暂时不作讨论。
2.1.3垃圾回收算法
2.1.3.1标记清除算法
- 定义:从GC Root 开始扫描,标记存活的对象,标记完毕后再扫描整个空间,回收未被标记的对象。
- 特点:不需要进行对象的移动,并且仅对不存活的对象进行处理,在存活对象比较多的情况下极为高效,但是会造成内存碎片。
2.1.3.2复制算法
- 定义:首先将内存分为两块,利用根搜索算法把存活的对象紧凑的复制到另一块未被使用的内存,然后将前一块整块清空。
- 特点:当存活的对象比较少时极为高效,并且不会存在内存碎片,但是需要一块空的内存交换空间。回收年轻代区通常使用此算法(Minor GC)。
2.1.3.3标记整理算法
- 定义:采用标记清除算法一样的方式进行标记清除后,再将剩下的存活的对象往左端空闲空间紧凑排列。
- 特点:提高了内存利用率,并且不会产生内存碎片,但是因为要做对象移动,所以成本较高。回收年老代区通常使用此算法(Full GC)。
2.3垃圾收集器
JVM主要提供了三种垃圾收集器:
- 串行收集器:使用一个垃圾收集线程,当垃圾收集线程工作时,会中断所有用户线程(Stop the World event)。
- 并行收集器:使用多个垃圾收集线程,仍然会Stop the World。
- 并行并发收集器:使用多个垃圾收集线程,并且用户线程与垃圾收集线程并行工作。
详情请见:
深入理解JVM
3.永久代区(PermGen Space)的GC
- 当永久代区被占满时,会触发Full GC,这时可能会导致class元数据信息被卸载。
- JDK8使用本地内存来存储类的元数据信息(Metaspace),也就是说方法区由本地内存所代替,JVM参数MaxMetaspaceSize用于限制所分配的本地内存的大小,若未制定,元数据空间会动态拓展。若使用量达到MaxMetaspaceSize所设定的值时,则触发GC。
结语:本节详细介绍了JVM GC 机制,下节将带领大家一起学习JVM GC 调优工具及常用参数设置。