垃圾回收机制GC

当对象被创建时,就会在Java虚拟机的堆区中拥有一块内存,在Java虚拟机的生命周期中,Java程序会陆续创建无数个对象,假如所有的对象都永久占有内存,那么内存有可能很快被消耗光,最后引发内存空间不足的错误。因此必须采用一种措施来及时回收那些无用对象的内存,以保存内存可以被重复利用。

java GC 机制,是Java与C++/C的主要区别之一,作为Java开发者,一般不需要专门编写内存回收和垃圾清理代码,堆内存泄露和溢出的问题。

1.需要GC的内存区域

jvm中,程序计数器,虚拟机栈,本地方法栈都是随线程而生随线程而灭,栈帧随着方法的进入和退出做入栈和出栈操作,实现了自动的内存清理。因此,我们的内存垃圾回收主要集中于Java堆和方法区中,在程序运行期间,这部分内存的分配和使用都是动态的。

2. 判断GC对象是否存活

判断一个对象是否存活常用的有两种方法:引用计数和可达分析

2.1 引用计数:

每个对象都有一个引用计数属性,新增一个引用时计数加一,引用释放时计数减一,计数为0时可以回收。此方法简单,无法解决对象相互循环引用的问题。

2.2可达性分析:

从GC Roots开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,不可达对象。

在java语言中,GC Roots包括:

  • 虚拟机栈中引用的对象
  • 方法区中类静态属性实体引用的对象
  • 方法区中常量引用的对象
  • 本地方栈中引用的对象

在这里插入图片描述
通过可达性分析可以对需要回收的对象进行标记,那么标记的对象是否一定会回收呢?

3.标记死亡对象

要真正宣告一个对象的死亡,至少要经历两次标记的过程:

3.1 第一次标记

在可达性分析后发现到GC Roots没有任何引用链相连时,被第一次标记。并且判断此对象是否必要执行finalize()方法。

  • 如果对象没有覆盖finalize()方法或者finalize()已经被jvm调用过,则这个对象就会认为是垃圾,可以回收。
  • 对于覆盖了finalize()方法,且finalize()方法没有被jvm调用过时,对象进入一个队列中,等待着被触发调用finalize()方法

3.2 第二次标记

执行完第一次的标记后,GC将对队列中的对象进行第二次小规模标记。也就是执行对象的finalize()方法

  • 如果对象在其finalize()方法中重新与引用链上任何一个对象建立联系,第二次标记时会将其移除“即将回收”的集合。
  • 如果对象没有,也可以认为对象已死,可以回收。

4. 什么时候触发GC

  • 系统调用System.gc()时可以触发
  • 系统自身来决定GC触发的时机

5.GC分类

GC又分为minor GC 和 Full GC(也称为Major GC)

6. GC常用算法

GC收集过程中用过的算法有:

  • 标记-清除算法
  • 标记-压缩算法
  • 复制算法
  • 分代收集算法

6.1 标记-清除算法

6.1.1 标记

GC通过遍历内存区辨别那些内存在使用,那些内容没有使用,并做好标记。

在这里插入图片描述

引用的对象以蓝色显示。未引用的对象以金色显示。在标记阶段扫描所有的对象以进行此确定。如果必须扫描系统中的所有对象,这可能时一个非常耗时的过程。

6.1.2 清除

清除阶段移除掉垃圾对象,并且用一个链表维护空闲的区域。

在这里插入图片描述

内存分配器持有空间内存区的引用,以便分配给新的对象。

6.1.3优缺点

优点:

  • 每个活着的对象的引用只需要找到即可,找到一个就可以判断其为活的
  • 不移动对象的位置

缺点:

  • 效率比较低,每个活着的对象都要在标记阶段遍历一遍
  • 所有的对象都要在清除阶段扫描一遍,因此算法复杂度较高
  • 没有移动对象,导致可能踹下你很多碎片化空间无法利用的情况

6.2 标记-压缩算法

标记-压缩算法时标记-清除算法的一个改进版。在标记阶段,该算法也将所有的对象标记为活着和死亡两种状态;不同的是,在第二个阶段,该算法并没有直接对死亡的对象进行清除,而是将所有存活的对象进行整理一下,放到同一区域的另一处空间,然后把剩下的所有对象全部清除。这样就达到了标记-整理的目的。

在这里插入图片描述

通过一起移动引用的对象,这使得新的内存分配更容易和更快。

优点:该算法不会像标记-清除算法那样产生大量的碎片空间
缺点:如果存活的对象过多,整理阶段将会执行较多的复制操作,导致算法效率较低

6.3 复制算法

复制算法与标记-整理算法的区别在于:该算法不是在同一个区域复制,而是将所有存活的对象复制到另一个区域内。

该算法将内存平均分成两个部分,然后每次只使用其中的一部分,当这部分内存满的时候,将内存中所有存活的对象复制到另一个内存中,然后将之前的内存清空,只使用这部分内存,循环下去。

在这里插入图片描述

优点:实现简单,不产生内存碎片
缺点:每次运行,总有一半内存是空的,导致可使用的内存空间只有原来的一半

6.4 分代收集算法

6.4.1 标记和压缩算法存在的问题

标记和压缩算法对于Java虚拟机而言会比较耗时。当java虚拟机分配了越来越多的对象后,GC所花费的时间将会更长。然而根据经验分析,绝大多数对象存在的时间都比较短。这样我们可以把存活时间长的对象和存活时间短的对象隔离开,这样GC会更加高效。

在这里插入图片描述
现在的虚拟机垃圾收集大多采用分代收集算法,它根据对象的生存周期,将堆分为新生代和老年代。

6.4.2分代收集算法思想

  • 在新生代中,由于对象生存期较短,每次回收都会有大量对象死去,那么这时候就可以采用复制算法。
  • 老年代里的对象存活率较高,没有额外的空间进行分配担保,所以可以采用标记-清除或者标记-压缩的算法。

6.4.3 分代收集过程

1.新生代(Young)分为Eden区,From区与To区
在这里插入图片描述
2.几乎任何新的对象都会在eden空间中分配内存,两个survior空间中一开始是空的
在这里插入图片描述

3.随着对象的不断创建,eden区空间逐渐被填满。
在这里插入图片描述
4.当eden区满了,那么就会触发一次Minor GC,也就是新生代的垃圾回收,所采用的就是复制算法。
在这里插入图片描述

5.下一次minor GC发生时,eden空间的存活对象也将被复制到空闲你的survior空间(s1,年龄加一),另外在前一次minor GC s0空间的存活对象也会被复制到s1(年龄加一),垃圾对象会被清除。
在这里插入图片描述
6. 下一次minor GC发生时,还是重复第四条的内容,只是两个survivor空间对调了,这次是从s1复制到s0空间。
在这里插入图片描述
7.随着minor gc不断发生,幸存对象在两个幸存区不断交换储存,年龄也在不断递增。当幸存对象的年龄达到指定的阈值后,他们将被移动到老年代。
在这里插入图片描述
8. 当年老代的内存被填满的时候,将会触发将触发Major GC(Full GC)进行老年代的内存清理。Major GC在老年代用的是标记清除算法。同时新生代的对象将被清除。

在这里插入图片描述

6.5 对象被放到老年代的条件

  • 该对象的年龄达到阈值
  • 创建对象后,无法放置到eden区(比如eden区的大小是10m,新对象的大小是11m)
  • 如果survivor区相同年龄的所有对象大小大于survivor区空概念的一般,年龄大于或等于这些对象的可以直接进入老年代

7. 方法区垃圾回收

方法区也存在垃圾回收,方法区的垃圾回收主要回收两部分内容

  • 常量池中废弃的常量
  • 不在使用的类型

8.finalize()方法

finalize()是Object的protected方法,子类可以覆盖该方法以实现资源清理工作,GC在回收对象之前调用该方法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值