java虚拟机之垃圾回收概念与算法

时间:2022-06-17 00:03:22

一:垃圾回收概念
GC中的垃圾:指的是,存在于内存中的不会再被使用的对象。
为什么要进行垃圾回收:如果不及时对内存中的垃圾进行清理,那么,这些垃圾对象所占用的内存空间会一直保留到应用程序结束,被保留的空间无法被其他对象使用。如果大量不会被使用的对象一直占用内存空间不放,需要内存空间时,就无法使用这些被垃圾对象占用的内存,从而有可能导致内存溢出。因此,堆内存空间的管理来说,识别和清理垃圾对象是至关重要的。
在早期的c、c++,垃圾回收基本上是手工进行清理,现在java,c#,python等语言都是用了垃圾回收的思想。开发人员只需要关注内存的申请,而内存的释放可以由系统自动识别和完成。

二:讨论常用的垃圾回收算法
常用算法:引用计数法,标记清除法,复制算法,标记压缩法,分代算法和分区算法。
1:引用计数法:是最经典也是最古老的一种垃圾收集方法,在微软的COM组件计数,Adobe的ActionScript3中,都可以找到引用计数器的身影。引用计数器的实现很简单,对于一个对象A,只要有任何一个对象引用了A,则A的引用计数器就加1,当引用失败时,引用计数器就减1。只要对象A的引用计数器的值为0,则对象A就不可能再被使用。
实现也非常简单,只需要为每个对象配备一个整型的计数器即可,但是,引用计数器有两个非常严重的问题

→无法处理循环引用的情况 ,因此,在java的垃圾回收器中,没有使用这种算法 (循环问题简述如下:有对象A和对象B,对象A中含有对象B的引用,对象B中含有对象A的引用。此时对象A和B的引用计数器都不为0。但是,在在系统中,却不存在任何第三个对象引用了A或者B,也就是说,A和B是应该被回收的垃圾对象,但由于垃圾对象间的相互引用,从而使垃圾回收器无法识别,引起内存泄漏。)

→ 引用计数器要求在每次因引用产生和消除的时候,需要伴随一个加法操作和减法操作,对系统性能会有一定得影响。

2:标记清除法:标记清除算法是现代垃圾回收算法的思想基础。标记清楚算法将垃圾回收分为两个阶段:标记阶段和清除阶段,说白了就是对不是垃圾对象做标记,之后清除未
被标记的对象。

标记清除法的缺点:会造成内存空间的不连续性。在对象的堆空间分配过程中,尤其是大对象的内存分配,不连续内存空间的工作效率要远低于连续的内存空间。

3:复制算法:复制算法的核心思想:将原有的内存空间分为两块,每次只是用其中一块,在垃圾回收时,将正在使用的内存中的存活对象复制到未使用的内存块中,之后,
清除正在使用的内存块中的所有对象,交换两个内存的角色(交换后的对象,存储空间是连续的),完成垃圾回收。

复制算法的适用场景:垃圾对象较多的区域,垃圾对象的回收的效率相对较高(因为复制的对象相对较少)。因为垃圾对象较少的话,复制的对象也就较多,垃圾回收的效率
相对较低

复制算法的缺点:将系统内存折半。

在java的新生代串行垃圾回收器中,使用了复制算法的思想。(这里补充下堆的空间结构:堆的空间结构由 新生代和老年代组成。新生代包括 eden 区、from区和 to区,老年代
包括tenured区)新生代分为eden空间,form空间和to空间。其中from 和to空间可以被视为用于复制的两块大小相同、地位相等、且可进行角色互换的空间块。from 和to空间也成为
survivor(幸存者)空间,用于存放未被回收的对象。

新生代:存放年轻对象的堆空间。年轻对象指刚刚创建的,或者经历垃圾回收次数不多的对象。
老年代:存放老年对象的堆空间。老年对象指经历过多次垃圾回收依然存活的对象。

在垃圾回收时,eden空间中的存活对象会被复制到未使用的幸存者空间(假设是to),正在使用的survivor空间(假设是from)中的年轻对象也会被复制到to空间中(大对象,或者老年对象会直接进入老年代,如果to空间已满,则对象也会直接进入老年代)。此时,eden空间和from空间中剩余对象就是垃圾对象,可以直接清除,to空间则存放此次回收后的存活对象。这种改进的复制算法,既保证了空间的连续性,又避免了大量的内存空间浪费。

4:标记压缩法:标记压缩法是一种老年代的回收算法,它在标记清除法的基础上做了一些优化。标记压缩法并不只是简单地清理未标记的对象,而是将所有的存活对象压缩到内
存的一端(压缩的对象是连续的排列在内存空间中),之后清理标记边间外所有的空间,这种方法既避免了内存空间的不连续性,又不需要折半内存,因此性价比高。

5:分代算法:说白了就时根据垃圾回收对象的特性,使用合适的算法回收。这里的分代指的是新生代和老年代,新生代存活的对象相对较少可以用复制算法,老年代存活的对
相对较多,可以使用标记压缩算法回收。

对于新生代和老年代来说,通常,新生代回收的频率很高,但是每次回收的耗时都很短,而老年代回收的频率比较低,但是会消耗更多的时间,为了支持高频率的新生代回收,
虚拟机可能使用一种叫做卡表的数据结构,卡表为一个比特位集合,每一个比特位可以用来表示老年代的某一区域中的所有对象是否持有新生代对象的引用。这样在新生代gc时,
可以不用花大量时间扫描所有老年代对象,来确定每一个对象的引用关系,而可以先扫描卡表,只有当卡表的标记位为1时,才需要扫描给定区域的老年代对象,而卡表位为0的
所在区域的老年代对象,一定不会含有新生代对象的引用。

6:分区算法:分代算将对象的生命周期长短划分为两个部分,分区算法将整个堆空间划分成连续的不同小区间,每一个小区间都独立使用,独立回收。这种算法的好处是可以
控制一次回收多少个小区间。

一般来说,在相同条件下,堆空间越大,一次gc时所需要的时间就越长,从而产生的停顿也越长。为了更好的控制gc产生的停顿时间,将一块大的内存区域分割成多个小块,
根据目标的停顿时间,每次合理的回收若干个小区间,而不是整个堆空间,从而减少一次gc所产生的停顿

总结 掌握 gc每个算法的优缺点,适用场景。理解记忆。
文章内容如有错误 欢迎加 QQ :82479297 探讨