什么是垃圾对象
JVM执行时是基于栈的字节码执行引擎,某方法执行时,虚拟机栈会创建一个栈帧,栈帧中存在局部变量表、操作数栈、方法出口等区域。方法执行过程中,基本数据类型存放在栈帧中,但引用类型在栈帧中存放的是一个引用,具体对象时存放在堆中,如Object o = new Object(),就有一个引用o指向堆中的对象。方法执行完成后,栈中的对象随着栈帧运行结束而被回收,⽽在此过程中在堆中创建的对象不会被回收,从而形成“垃圾”。
再看下JVM数据共享逻辑图,可以看出指向堆中一共有几块区域,线程私有的程序计数器、虚拟机栈、本地方法栈、方法区等。
注意下“堆指向方法区”:对象头中有个Class Point 指向对象对应的类元数据的内存地址。
为何要回收
垃圾多会导致新对象无法再分配到内存,从而导致内存溢出。
由于不同JAVA对象存活时间是不一定的,因此,在程序运行一段时间以后,如果不进行垃圾回收,整个程序会因内存耗尽导致整个程序崩溃。垃圾回收还会整理那些零散的内存碎片,碎片过多最直接的问题就是会导致无法分配大块的内存空间以及降低程序的运行效率。
如何发现垃圾
引用计数法
对于某个对象而言,只要用应用程序中持有该对象的引用,就说明该对象不是垃圾,如果一个对象没有任何指针对其引用,它就是垃圾。
优点:简单
缺点:循环引⽤问题,如果AB相互持有引⽤,导致永远不能被回收
public class ReferenceCountingGC {
public Object instance = null;
public static void testGC() {
ReferenceCountingGC objA = new ReferenceCountingGC();
ReferenceCountingGC objB = new ReferenceCountingGC();
objA.instance = objB;
objB.instance = objA;
objA = null;
objB = null;
// 假设在这行发生GC,objA和objB是否能被回收?
System.gc();
}
}
如上图,objA和objB相互引用,虽然最终将objA和objB赋值为null,但由于互相引用,使用引用计数法则任然不会回收。
可达性分析法
可达性分析算法的基本思路就是通过一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“引用链”(Reference Chain),如果某个对象到GC Roots间没有任何引用链相连,或者用图论的话来说就是从GC Roots到这个对象不可达时,则证明此对象是不可能再被使用的。
目前主流JVM都采用此方法来实现垃圾回收。
GC Root
在Java技术体系里面,固定可作为GC Root的对象包括以下几种:
1、静态变量
2、常量
3、JAVA 虚拟机栈
4、本地方法栈
5、Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象(比如NullPointExcepiton、OutOfMemoryError)等,还有系统类加载器。
6、所有被同步锁(synchronized关键字)持有的对象。
7、反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。
下面列举最常见的几种GC Root
//1、栈指向堆(本文开头说明的就是此情况)
Object obj = new Object();//对象引⽤,包括虚拟机栈、本地⽅法栈
//2、⽅法区指向堆(方法区存放类信息、类静态变量、类常量等)
private static Object obj1 = new Object(); //静态变量
private static final Object obj2 = new Object(); //常量
//3、ClassLoader
ClassLoader classLoader = new ClassLoader("url");
Class<?> aClass1 = classLoader.loadClass("Entity");
Object instance = aClass1.newInstance();
//4、synchronized持有的对象
synchronized (Object){}
不在GC Root 上的对象是否⼀定会被回收(finalize)
两次标记
第一次标记:标记对象不再GC Root 上
第二次标记:将对象移除“即将回收”的集合
判断对象是否有必要执⾏finalize()方法:
1.对象覆盖了finalize()方法
2.虚拟机未调用过finalize()方法
有必要执⾏finalize()方法时,若在此方法中将自己放到GC Root引⽤链上,则可以拯救
自己(finalize()方法是对象逃脱死亡命运的最后⼀次机会)。
注:
finalize()运行代价高昂,不确定性大,无法保证各个对象的调用顺序,如今已被官方明确声明为不推荐使用的语法。有些教材中描述它适合做“关闭外部资源”之类的清理性工作,这完全是对finalize() 方法用途的一种自我安慰。finalize()能做的所有工作,使用try-finally或者其他方式都可以做得更好、 更及时,所以建议大家完全可以忘掉Java语言的这个方法。
在GC Root 上的对象是否⼀定不会被回收
在JDK 1.2版之后,引入几种引用类型,解决“食之无味,弃之可惜”的对象问题。
1)、强引用:一定不会被回收,宁可发生OOM异常也不会回收
强引用是最传统的“ 引用”的定义,是指在程序代码之中普遍存在的引用赋值。
public void testStrongReference() {
System.out.println("验证强引⽤...");
Object a = new Object();
Object b = a; //b 是强引⽤
a = null;
System.out.printf("gc 之前的对象b=%s %n",b);
System.gc();
System.out.printf("gc 之后的对象:b=%s %n",b);
}
验证强引⽤...
gc 之前的对象b=java.lang.Object@13c78c0b
gc 之后的对象:b=java.lang.Object@13c78c0b
2)、软引用:OOM 之前会被回收
软引用是用来描述一些还有用,但非必须的对象。只被软引用关联着的对象,在系统将要发生内存溢出异常前,会把这些对象列进回收范围之中进行第二次回收,如果这次回收还没有足够的内存,才会抛出内存溢出异常。
public void testSoftReference() {
System.out.println("验证软引⽤...");
Object a = new Object();
SoftReference b = new SoftReference(a); //b是软引⽤
a = null;
System.out.println("gc 之前的对象:"+b.get());
System.gc();
System.out.println("gc 之后的对象:"+b.get());
}
验证软引⽤...
gc 之前的对象:java.lang.Object@61dc03ce
gc 之后的对象:java.lang.Object@61dc03ce
3)、弱引用:GC之前会被回收
public void testWeakReference() {
System.out.println("验证弱引⽤...");
Object a = new Object();
WeakReference b = new WeakReference(a); //b是虚引⽤
a = null;
System.out.println("gc 之前的对象:"+b.get());
System.gc();
System.out.println("gc 之后的对象:"+b.get());
}
验证弱引⽤...
gc 之前的对象:java.lang.Object@13c78c0b
gc 之后的对象:null
4)、虚引用:
虚引用也称为“ 幽灵引用”或者“ 幻影引用”,它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的只是为了能在这个对象被收集器回收时收到一个系统通知。在JDK 1.2版之后提供了PhantomReference类来实现虚引用。
如何清理垃圾
标记-清除算法
特点:标记+清除两个阶段,是最基础的收集算法
优点:简单
缺点:1.执行效率不稳定(对象越多,标记和清理效率越低)2.碎片问题
标记-复制算法
特点:将可用内存按容量划分为大小相等两块,每次只使用其中的一块,适用于新生代。
优点:1).解决标记-清除算法面的大量可回收对象时执行效率低的问题,简单高效,特别是可回收对象多时 2) 解决碎片问题
缺点:1) 空间浪费 2)在对象存活率较高时就要进行较多的复制操作,效率将会降低 3)如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法
标记-整理算法
特点:标记阶段同标记-清除算法一样,清理阶段使存活对象都向内存空间一端移动,然后清理边界外的内存
优点:
与标记-清理算法比:无空间碎片
与标记-复制算法比:1.不浪费空间 2.对象多时效率高
吞吐量高
缺点:
移动对象需要停顿时间STW(Stop the world) 比标记-清理算法长,延迟高
标记-分代收集理论
弱分代假说:绝大多数对象都是朝生夕灭的。
强分代假说:熬过越多次垃圾收集过程的对象就越难消亡。
跨分代引用假说:跨代引用相对于同代引用来说仅占少数。
分代收集分类:
- 部分收集
- 新生代收集:Minor GC/Young GC,目标新生代
- 老年代收集:Major GC/Old GC,目标老年代
- 混合收集:Mixed GC 收集新生代和部分老年代,G1收集器
- 整堆收集: Full GC 收集整个方法堆(新生代+老年代)和方法区