6、垃圾回收概述、垃圾回收算法(可达性分析算法、标记清除算法、复制算法、标记压缩算法)

更新时间:2023-07-12 22:36:41 阅读: 评论:0

6、垃圾回收概述、垃圾回收算法(可达性分析算法、标记清除算法、复制算
法、标记压缩算法)
⽂章⽬录
垃圾回收概述
关于垃圾收集的三个经典问题:
哪些内存需要回收?
什么时候回收?
如何回收?
什么是垃圾?
垃圾是指在运⾏程序中没有任何指针指向的对象,这个对象就是需要被回收的垃圾。
如果不及时对内存中的垃圾进⾏清理,那么,这些垃圾对象所占⽤的内存空间会⼀直保留到应⽤程序结束,被保留的空间⽆法被其他对象使⽤,甚⾄可能导致内存溢出。
在早期的C、C++时代,垃圾回收基本上是⼿⼯进⾏的。开发⼈员可以使⽤new关键字进⾏内存申请,并使⽤delete关键字进⾏内存释放。
现在,除了Java以外,C#、Python等语⾔都使⽤了⾃动垃圾回收的思想,也是未来发展趋势。
Java垃圾回收机制
⾃动内存管理,⽆需开发⼈员⼿动参与内存的分配与回收,降低了内存泄漏和内存溢出的风险;
没有垃圾回收期,Java也会和cpp⼀样,各种野指针,泄漏问题;
⾃动内存管理机制,将程序员从繁重的内存管理中释放出来,专注业务开发。
当需要排查各种内存溢出、内存泄漏问题时,我们必须对这些⾃动化技术实施必要的监控和调节;
GC的作⽤区域:堆和⽅法区。
垃圾回收相关算法
标记阶段:引⽤计数法
在GC执⾏垃圾回收之前,⾸先需要区分出内存中哪些是存活对象,哪些是死亡对象。只有被标记为死亡的对象,GC才会在执⾏垃圾回收时,释放掉其所占⽤的空间。
判断对象存活⼀般有两种⽅式:引⽤计数算法和可达性分析算法。
引⽤计数算法⽐较简单,对每个对象保存⼀个整型的引⽤计数器属性,⽤于记录对象被引⽤的情况。
对于⼀个对象A,只有有⼀个对象引⽤了A,则A的引⽤计数器就加1,;当引⽤失效时,引⽤计数器就减⼀。只要对象A的引⽤计数器的值为0,即表⽰对象A不可能再被使⽤,可进⾏回收。
优点:实现简单,垃圾对象便于辨识,判定效率⾼,回收没有延迟性。
缺点:
需要单独的字段存储计数器,增加了存储空间的开销;
引⽤计数器有⼀个严重的问题,⽆法处理循环引⽤的问题。这是⼀条致命缺陷,导致在Java的垃圾回收器中没有采⽤这类算法。
publicclassMyObject {
publicObject ref =null;
publicstaticvoidmain(String[] args){
MyObject myObject1 =newMyObject();
MyObject myObject2 =newMyObject();
myObject1 =null;
南华门myObject2 =null;
}
}
即使myObject1和myObject2设置为null,但是堆中的对象引⽤计数器不为0。
标记阶段:可达性分析算法
毒品有哪些
相对于引⽤计数算法⽽⾔,可达性分析算法不仅具备实现简单和执⾏⾼效等特点,更重要的是该算法可以有效地解决在引⽤计数算法中循环引⽤的问题,防⽌内存泄漏的发⽣。
所谓"GC Roots"根集合就是⼀组必须活跃的引⽤。
基本思路:
可达性分析算法是以根对象集合(GC Roots)为起始点,按照从上到下的⽅式搜索被根对象集合所连接的⽬标对象是否可达;
使⽤可达性分析算法后,内存中的存活对象都会被根对象集合直接或间接连接着,搜索所⾛过的路劲称为引⽤链(Reference Chain);
如果⽬标对象没有任何引⽤链相连,则是不可达的,就意味着该对象已经死亡,可以标记为垃圾对象;
在可达性分析算法中,只有能够被根对象集合直接或间接连接的对象才是存活对象。
在Java语⾔中,GC Roots包括以下⼏类元素:
虚拟机栈中引⽤的对象
⽐如各个线程被调⽤的⽅法中使⽤到的参数、局部变量等;
本地⽅法栈内引⽤的对象
⽅法区中类静态属性所引⽤的对象
Java类的引⽤类型静态变量
⽅法区中常量引⽤的对象
字符串常量池中的引⽤
所有被同步锁Synchronized持有的对象
Java虚拟机内部的引⽤
基本数据类型对象的Class对象、⼀些常驻的异常对象(如,NullPointerException、OutOfMemoryError)、系统类加载器;
除了这些固定的GC Roots集合以外,根据⽤户所选⽤的垃圾收集器以及当前回收的内存区域不同,还可以有其他对象"临时性"地加⼊,共同构成完整的GC Roots集合。⽐如:分代收集和局部回收。
注意:如果要使⽤可达性分析算法来判断内存是否可回收,那么分析⼯作必须在⼀个能保障⼀致性的快照中进⾏。这点也是导致GC进⾏时,必须"Stop The World"的⼀个重要原因。
即使是号称⼏乎不会发⽣停顿的CMS收集器中,枚举根节点时也是必须要停顿的。
对象的finalization机制
Java语⾔提供了对象终⽌机制来允许开发⼈员提供对象被销毁之前的⾃定义处理逻辑;
当垃圾回收器发现没有引⽤指向⼀个对象,即:垃圾收集此对象之前,总会先调⽤这个对象的finalize()⽅法;
灯谜大全及答案超难
finalize()⽅法允许在⼦类中被重写,⽤于在对象被回收时进⾏资源释放。通常在这个⽅法中进⾏⼀些资源释放的⼯作,⽐如关闭⽂件、套接字和数据库连接。
永远不要主动调⽤某个对象的finalize()⽅法,应该交给垃圾回收机制调⽤,
在finalize()时可能会导致对象复活;
finalize()⽅法的执⾏时间是没有保障的,它完全由GC线程决定,极端情况下,若不发⽣GC,则finalize()⽅法将没有执⾏机会;
⼀个糟糕的finalize()会严重影响GC的性能;
由于finalize()⽅法的存在,虚拟机中的对象⼀般处于三种可能的状态:
可触及的:从根节点开始,可以到达这个对象;
可复活的:对象的所有引⽤都被释放,但是对象有可能在finalize()中复活;
不可触及的:对象的finalize()被调⽤,并且没有复活,那么就进⼊不可触及状态。不可触及的对象是不可能被复活,因为
finalize()⽅法只会调⽤⼀次。
具体过程:
MAT的GC Roots溯源
MAT:The Eclip Memory Analyzer is a fast and feature-rich Java heap analyzer that helps you find memory leaks and reduce memory consumption.
快速且功能丰富的Java堆分析仪,帮助你找到内存泄漏,减少内存消耗。
package ;
import java.util.ArrayList;最高限额
import java.util.Date;
import java.util.List;
import java.util.Scanner;
/**
* @author lwj
* @date 2020/9/16 17:31
*/
public class GcRootsTest {
public static void main(String[] args){
List<Object> numList =new ArrayList<>();
Date date =new Date();
for(int i =0; i <100; i++){
numList.add(String.valueOf(i));
try{
Thread.sleep(10);
}catch(InterruptedException e){
e.printStackTrace();
}
}
System.out.println("数据添加完毕,请操作。");
new Scanner(System.in).next();
numList = null;
date = null;
System.out.println("numList、date已置空,请操作。");
new Scanner(System.in).next();
System.out.println("结束");拌海蜇
}
}
使⽤jvisualvm完成堆dump。
保存为⽂件。怎么制作泡泡水
使⽤MAT打开⽂件查看。
清除阶段:标记-清除算法
当成功区分出内存中存活对象和死亡对象后,GC接下来的任务就是执⾏垃圾回收,释放掉⽆⽤对象所占⽤的内存空间,以便有⾜够的可⽤内存空间为新对象分配内存。
执⾏过程:
当堆中的有效内存空间被耗尽的时候,就会停⽌整个程序(Stop The World),然后进⾏两项⼯作,第⼀项是标记,第⼆项是清除。
标记:Collector从引⽤根节点开始遍历,标记所有被引⽤的对象,⼀般是在对象的Header中记录为可达对象;
清除:Collectot对堆内存从头到尾进⾏线性的遍历,如果发现某个对象在其Header中没有标记可达对象,则将其回收。
缺点:
效率不算⾼;
在进⾏GC时候,需要停⽌整个应⽤程序;
这种⽅式清理出来的空闲内存是不连续的,产⽣内存碎⽚,需要维护⼀个空闲列表;
注意:
这⾥所谓的清除并不是真的置空,⽽是把需要清除的对象地址保存在空闲的地址列表中。下次有新对象需要加载时,判断垃圾的位置空间是否够,如果够,就存放。
清除阶段:复制算法
核⼼思想:
将活着的内存空间分为两块,每次只使⽤其中⼀块,在垃圾回收时将正在使⽤的内存中的存活对象复
制到未被使⽤的内存块中,之后清除正在使⽤的内存块中的所有对象,交换两个内存的⾓⾊,最后完成垃圾收集。
优点:
没有标记-清除的过程,实现简单,运⾏⾼效;
复制过去以后保证空间的连续性,不会出现碎⽚问题;
缺点:
需要两倍的内存空间;
需要维护对象的引⽤关系;
清除阶段:标记-压缩算法
背景:
复制算法的⾼效性是建⽴在存活对象少、垃圾对象多的前提下。这种情况在新⽣代经常发⽣,但是在⽼年代,更常见的情况是⼤部分对象都是存活对象,如果依然使⽤复制算法,由于存活对象多,复制的成本也将很⾼。因此,基于⽼年代垃圾回收的特性,需要使⽤其他的算法。
摩羯男和天蝎女
标记-清除算法的确可以应⽤在⽼年代,但是该算法不仅执⾏效率低下,⽽且在执⾏完内存回收后,还会产⽣内存碎⽚,所以JVM的设计者需要在此基础上进程改进,标记-压缩算法由此诞⽣。
执⾏过程:
第⼀阶段和标记-清除算法⼀致,从根节点开始标记所有被引⽤对象。
第⼆阶段将所有的存活对象压缩到内存的另⼀端,按顺序排放。
之后,清理边界外所有空间。
⼆者的本质差异在于标记清除算法是⼀种⾮移动式的回收算法,标记压缩是移动式的。
可以看到,标记的存活对象将会被整理,按照内存地址依次排列,⽽未被标记的内存会被清理掉。如此⼀来,当我们需要给新对象分配内存时,jvm只需要持有⼀个内存的起始地址即可,这⽐维护⼀个空闲列表显然少了许多开销。
⼩结
麻油
标记清除标记压缩复制
速度中等最慢最快
空间开销少(但会堆积碎⽚)少(不堆积碎⽚)通常需要活对象的2倍⼤⼩(不堆积碎⽚)
移动对象否是是
分代收集算法
⽬前⼏乎所有的GC都是采⽤分代收集算法执⾏垃圾回收的。
在HotSpot中,基于分代的概念,GC所使⽤的内存回收算法必须结合年轻代和⽼年代各⾃的特点。
年轻代:
特点:区域相对⽼年代较⼩,对象声明周期短、存活率低,回收频繁。
这种情况复制算法的回收整理,速度是最快的。复制算法的效率只和当前存活对象的⼤⼩有关。因此
很适合于年轻代的回收。⽽复制算法内存利⽤率不⾼,通过HotSpot中的两个Survivor的设计得以缓解。
⽼年代:
特点:区域较⼤,对象⽣命周期长、存活率⾼,回收不及年轻代频繁。
这种情况存在⼤量存活率⾼的对象,复制算法明显变得不合适。⼀般是由标记-清除或者是标记-清除与标记-整理的混合实现。
增量收集算法
基本思想:
如果⼀次性将所有的垃圾进⾏处理,需要造成系统长时间的停顿,那么就可以让垃圾收集线程和应⽤程序线程交替执⾏。每次,垃圾收集线程只收集⼀⼩⽚区域的内存空间,接着切换到应⽤程序线程。依次反复,直到垃圾收集完成。
总的来说,增量收集算法的基础仍是传统的标记-清除和复制算法、增量收集算法通过对线程间冲突的妥善处理,允许垃圾收集线程以分阶段的⽅式完成标记、清理或复制⼯作。
缺点:
由于在垃圾回收过程中,间断性的还执⾏了应⽤程序代码,所以能减少系统的停顿时间,但是因为线程切换和上下⽂转换的消耗,会使得垃圾回收的总体成本上升,造成系统吞吐量的下降。
分区算法

本文发布于:2023-07-12 22:36:41,感谢您对本站的认可!

本文链接:https://www.wtabcd.cn/fanwen/fan/89/1079006.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:对象   垃圾   算法   内存   回收   标记   收集
相关文章
留言与评论(共有 0 条评论)
   
验证码:
推荐文章
排行榜
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图