第5章-垃圾回收
一、垃圾回收概述
1、什么是垃圾
- 垃圾就是指在运行程序中没有任何指针指向的对象,这个对象就是需要被回收的垃圾
2、为什么需要GC
-
如果不及时对内存的垃圾进行清理,那么,这些垃圾对象所占的内存空间会一直保留到应用程序结束,被保留的空间无法被其他对象使用,甚至可能导致内存溢出。
-
对于高级语言来说,一个基本认知是如果不进行垃圾回收,内存迟早都会被消耗完,因为不断的分配内存空间而不进行回收,就好像不停的生产生活垃圾而从来不打扫一样。
-
除了释放没用的对象,垃圾也可以清理内存里的记录碎片,碎片整理将所占用的堆内存移到对的端,以便JVM将整理出来的内存分配给新的对象。
-
随着应用陈旭所应付的业务越来越大,复杂,用户越来越多,没有GC就不能保证应用程序的正常运行。而经常造成STW的GC又跟不上实际的需求,所以才会不断地尝试对GC进行优化
3、Java垃圾回收机制
- 自动内存管理,无需开发人员手动参与内存的分配与回收,这样降低内存泄漏和内存溢出的风险。
二、垃圾回收的相关算法
1、标记阶段:引用计数算法
-
引用计数算法比较简单,对每个对象保存一个整型的引用计数器属性,用于记录对象被引用的情况
-
对于一个对象A,只要有任何一个对象引用了A,这A的引用计数器就+1;当引用失效时,引用计数器-1。只要对象A的引用计数器的值为0,即代表对象A不可能再被使用,可以进行回收。
-
优点:实现简单,垃圾对象便于辨别;判定效率高,回收没有延迟性
-
缺点:
- 它需要单独的字段储存计数器,这样的作法增加了储存空间的开销
- 每次赋值都需要更显计数器,伴随着加法和减法的操作,这增加了时间的开销
- 引用计数器有一个严重的问题,既无法处理循环引用的情况。这是一条致命的缺陷,导致再Java的垃圾回收器中没有使用这类算法
2、标记阶段:可达性分析算法
- 跟搜索算法/追踪性垃圾收集
- 相对于引用计数算法而言,可达性分析算法不仅同样具备实现简单和执行高效等特点,更重要的是该算法可以有效地解决在引用计数算法中循环引用无法解决的问题,防止发生内存泄漏
- 相较于引用计数算法,这里的可达性分析就是Java、c#选择的。这种类型的垃圾收集通常也叫作追踪性垃圾收集。
- 所谓“GC ROOts”跟根集合就是一组必须活跃的引用。
- 基本思路:
- 可达性分析算法是以根对象集合为起始点,按照从上至下的方式搜索被跟对象集合所连接的目标对象是否可达
- 使用可达性分析算法后,内存中的存活对象都会被根对象集合直接或间接的连接着,搜索所有走过的路径称为引用链
- 如果目标对象没有任何引用链相连接,则是不可达的,就意味着该对象已经死亡,可以标记为垃圾对象。
- 在可达性分析算法中,**只有能够被根对象直接上传](htots 集合中多包涵的元素类型:
- 虚拟机栈中引用的对象
- 比如:各个线程被调用的方法中使用到的参数、局部变量
- 本地方法内JNI(通常说的是本地方法)引用对象
- 方法区中类静态属性的对象
- 比如:Java类的引用雷响静态变量
- 方法区中常量引用的对象
- 比如:字符串常量池里的引用
- 所有被同步锁synchronized持有的对象
- Java虚拟机内部的引用
- 基本数据类型对应的Class对象,一些常驻的异常对象(比如:NullPointerException),系统类加载器。
- 反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等
- 如果要使用可达性分析算法来判断内存是否可回收,那么分析工作必须再一个能保障一致性的快照中进行。这一点不满足,分析结果的准确性就无法保证。
- 这一点也是导致GCimgs%5Cimaion机制


4、清除阶段:标记-清除算法
- 执行过程:
- 当堆中的有效内存空间被耗尽的时候,就会停止整个程序,然后进行两项工作,第一项则是标记,第二项则是清除。
- 标记:collector从引用根节点开始遍历,标记所有被引用的对象。一般是在对象的Header中记录为可达对象。
- 清除:collector对堆内存从头到尾进行线性的遍历,如果发现某个对象在Header中没有标记为可达对象,则将其回收。
- 缺点:
- 效率不高
- 在进行GC时,需要停止整个应用程序,导致用户体验差。
- 这种方法清理出来的空闲内存是不连续的,产生内存碎片。需要维护一个空闲列表
- 注意:何为清除
- 这里所谓的清除并不是真的置空,而是把需要清除的对象地址保存在空闲的地址列表中。下次有新对象需要加载时,判断垃圾的位置空间是否够,如果够,就存放。
5、清除阶段:复制算法
- 背景:
- 为了解决标记清除算法在垃圾回收效率方面的缺陷,M.LMinsky于1963年发表了著名的论文,“使用双存储区的垃圾回器”
- 核心思想
- 将活着的内存空间分为两块,每次只使用其中一块,在垃圾回收时,将正在使用的内存中的存活对象复制到未被使用的内存块中,之后清除正在使用的内存块中的所有的对象,交换两个内存的角色,最后完成垃圾回收。
- 优点
- 没有标记清除过程,实现简单,运行效率高
- 复制过去后保证空间的连续性,不会出现碎片化问题。
- 缺点:
- 需要两倍空间
- 对于G1这种拆分成为大量region的GC,复制而不是移动,意味着GC需要维护region之间对象引用关系,不管是内存占用或者时间开销也不小。
- 特别的
- 如果系统中的垃圾对象过少,复制算法需要复制的存活对象数量并不会太大,或者说非常低才行。
6、清除阶段:标记-压缩算法
- 标记-清除算法的确可以应用在老年代中,但是该算法不仅执行效率低,而且在执行完内存回收后还会产生内存碎片,所以JVM的设计者需要在此基础上进行改进,标记-压缩算法由此诞生。
- 执行过程:
- 第一阶段:和标记清除算法一样,从根节点开始标记所有被引用对象
- 第二阶段:将所有存活对·象压缩到内存的一端,按顺序排放。
- 第三阶段:清理边界外所有的空间
- 优点
- 消除了标记-清除算法中,内存区域分散的缺点,我们需要给新对象分配内存时,JVM只需要持有一个内存的起始地址即可。
- 消除了复制算法当中,内存减半的高额代价。
- 缺点
- 从效率上来说,标记-整理算法要低于复制算法
- 移动对象的同时,如果对象被其他对象引用,则还需要调整引用的地址。
- 移动过程中,需要全程暂停用户应用程序。即:STW
8、分代收集算法
- 目前几乎所有的GC都是采用分代收集算法执行垃圾回收
- 年轻代:
- 特点:区域相对老年代小,对象生命周期短,回收频繁。
- 这种情况刚好使用复制算法
- 老年代:
- 特点:区域大,对象生命周期长,存活率高,回收不及年轻代频繁。
- 一般使用,标记-清除和标记-压缩的混合实现


被折叠的 条评论
为什么被折叠?



