垃圾回收
一:概述
垃圾收集(Garbage Collection) 简称GC
垃圾收集器关注的是java堆和方法区的内存该如何管理,因为这两个区域有着很显著的不确定性:一个接口的多个实现类需要的内存可能不一样,一个方法所执行的不同条件分支所需要的内存也可能不一样,这部分内存的分配和回收是动态的。

内存分配
Java 的自动内存管理主要是针对对象内存的回收和对象内存的分配。同时,Java 自动内存管理最核心的功能是 堆内存中对象的分配与回收。
Java 堆是垃圾收集器管理的主要区域,因此也被称作GC 堆(Garbage Collected Heap),从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以 Java 堆还可以细分为:新生代和老年代:再细致一点有:Eden 空间、From Survivor、To Survivor 空间等。进一步划分的目的是更好地回收内存,或者更快地分配内存。
堆空间基本结构

二:如何判断对象存亡
概述
垃圾回收顾名思义就是将已分配出去的,但却不再使用的内存回收回来,以便能够再次分配,在Java虚拟机的语境下,垃圾指的是死亡的对象所占据的堆空间。那么怎么来判断一个对象是死亡还是存活的是进行垃圾回收的关键。
方法
判断对象是否死亡的两种方式:引用计数法和可达性分析算法。
2.1 引用计数法
引用计数算法的思想非常简单:在对象中添加一个引用计数器,每当有一个地方引用了它时,计数器值就加一,当引用失效时,计数器值就减一,任何时刻计数器为零时说明该对象已死亡,便可以被回收了。
但是在主流的java虚拟机里面没有选用引用计数算法来管理内存,因为使用引用计数算法除了需要额外的空间存储计数器,以及繁琐的更新操作,更重要的是无法处理循环引用对象。当对象 a 与 b 互相引用,除此之外没有其他引用指向a 或者 b,实际上这两个对象已经不可能再被访问,但是它们相互引用对方,导致它们的程序计数器都不为0,那么这两个对象所占据的空间将不可回收,从而造成内存泄漏。
2.2 可达性分析算法
可达性分析算法的实质是将称为"GC roots"的根对象作为存活对象集合,从这些节点开始根据引用关系向下搜索,搜索过程走过的路径称为引用链,如果某个对象没有任何引用链相连,则此对象是不会再被使用的,将会被判定为可回收的对象。

那什么是GC Roots呢,我们可以理解为由堆外指向堆内的引用,固定可以作为GC Roots的对象包括(但不限于)以下几种:
1.在虚拟机栈中引用的对象,例如当前正在运行的方法所使用到的参数、局部变量、临时变量等。
2.已加载类的静态变量
3.在方法区中常量引用的对象,如字符串常量池里面的引用
4.本地方法栈中 JNI(通常说的 Native 方法)引用的对象
5.所有被同步锁(synchronized关键字)持有的对象
6.根据用户选用的垃圾收集器以及当前回收的内存区域不同,临时性加入的对象,共同构成完整的GC Roots集合。
可达性分析算法在多线程的环境下,其他线程可能会更新已经访问过的对象中的引用,从而造成误报(将引用设置为null)或者漏报(将引用设置为未被访问过的对象)。漏报可能会使得垃圾回收器可能回收事实上仍被引用的对象内存,可能导致Java虚拟机崩溃。
三:引用
概述
无论通过引用计数算法判断对象的引用数量,还是通过可达性分析算法判断对象是否引用链可达,判断对象是否存活都和 “引用” 离不开关系。在jdk1.2之后,java对引用的概念进行了扩充,将引用分为了强引用、软引用、弱引用、虚引用,(引用强度依次逐渐减弱)。
强引用(StrongReference):
是指程序代码之中普遍存在的引用赋值,例如(Object obj = new Object),只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象,当内存空间不足,Java 虚拟机宁愿抛出 OutOfMemoryError 错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。
软引用(SoftReference):
描述一些还有用,但非必须的对象,如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,会把这些对象列进回收范围之中进行第二次回收,如果这次回收还没有足够的内存,才会抛出内存溢出异常。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,JAVA 虚拟机就会把这个软引用加入到与之关联的引用队列中。
弱引用:
描述非必须对象,被弱引用关联的对象只能存活到下一次垃圾收集发生为止,只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java 虚拟机就会把这个弱引用加入到与之关联的引用队列中
虚引用:
虚引用是最弱的一种引用关系,一个对象是否有虚引用的存在完全不会对其生命周期构成影响,也无法通过虚引用来取得一个对象实例,任何时候都能被垃圾回收。给对象设置虚引用的唯一目的只是为了能在这个对象被收集器回收时收到一个系统通知,主要用来跟踪对象被垃圾回收的活动。
注意:
在程序设计中一般很少使用弱引用与虚引用,使用软引用的情况较多,这是因为软引用可以加速 JVM 对垃圾内存的回收速度,可以维护系统的运行安全,防止内存溢出(OutOfMemory)等问题的产生。
四:垃圾收集算法

从如何判断对象消亡的角度出发,垃圾收集算法可以分为“引用计数式垃圾收集”和“追踪式垃圾收集”,也被称为“直接垃圾收集”和间接“垃圾收集”。
新生代:每次垃圾收集都发现有大批对象死去,而每次回收后存活的少量对象,将会逐步晋升到老年代中存放。
4.1 标记-清除算法
标记清除算法可以分为"标记" 和 "清除"两个阶段,可以标记出所有需要回收的对象,标记完成后统一回收掉所有被标记的对象。也可以反过来,标记存活的对象,统一回收所有未被标记的对象,而标记过程就属于垃圾的判定过程。
缺点:
1.执行效率不稳定,当Java堆中需要对大量对象进行回收的时候,标记和清除两个过程执行效率都随着对象数量增长而降低。
2.标记、清除后产生大量不连续的内存碎片,导致以后程序运行过程中需要分配较大的对象时无法找到足够的连续内存空间而不得不提前触发另一次垃圾收集。
标记-清除算法的执行过程:

4.2 标记-复制算法
为了解决面对大量可回收对象时执行效率低的问题,“标记-复制”收集算法出现了。它可以将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对内存区间的一半进行回收。
缺点:
空间浪费,堆空间的使用效率极低,将可用的内存缩小为了原来的一半。
标记-复制算法的执行过程:

4.3 标记-整理算法
由于在老年代中可能存在所有对象都100%存活的极端情况,所以一般不能直接使用标记-复制算法。而标记-整理算法是根据老年代的特点提出的一种标记算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。
缺点:
1.在老年代这种每次回收都有大量对象存活的区域,移动存活对象并更新所有引用这些对象的地方将是极为负重的操作,而且这种操作必须全程暂停用户应用程序才能进行。这样的停顿被称为"Stop The World"。
标记-整理算法的执行过程:

4.4 分代收集算法
当前虚拟机的垃圾收集都采用分代收集算法,这种算法只是根据对象存活周期的不同将内存分为几块。一般将 java 堆空间分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。
新生代:用来存储新建的对象。被划分为 Eden 区,以及两个大小相同的 Survivor区。在Java虚拟机中可以动态的分配Eden区和Survivor区(Java虚拟机参数:-XX:+UserPSAdaptiveSurivivorSizePolicy),也可以固定其比例(Java虚拟机参数:-XX:SurvivorRatio)。
老年代:当对象存活时间够长时,将其移动到老年代。

在我们使用new指令的时候,它会在Eden区域划分一块作为存储对象的内存,由于堆空间是线程共享的,因此直接划空间是需要进行同步的。
在新生代中,每次收集都会有大量对象死去,所以可以选择”标记-复制“算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。
而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集。
五:回收方法区
方法区垃圾收集主要回收的两部分内容:废弃的常量和不再使用的类型
判断废弃的常量:
1.没有任何字符串对象引用常量池中的 "java" 常量。
2.虚拟机中也没有其他地方引用这个字面量。
判断不再使用的类:
1.该类的所有实例都已经被回收,也就是java堆中不存在任何该类以及派生子类的实例。
2.加载该类的类加载器以及被回收。
3.该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
java虚拟机被允许对满足条件的无用类进行回收,这里说的是仅仅,并非没了引用就必然会被回收。在大量使用反射、动态代理、CGLib等字节码框架,动态生成 JSP 以及OSGi 这类频繁自定义类加载器的场景中,通常都需要虚拟机具备类型卸载的能力,以保证不会对方法区造成过大的内存压力。
小结


浙公网安备 33010602011771号