JVM基础知识-GC垃圾回收

1.GC的作用域

方法区,堆这些线程共享的运行时数据区域

2.GC ROOTS

2.1.什么是垃圾?

简单来说就是内存中已经不再被使用的空间就是垃圾

2.2.对象的可触及性
2.2.1.可触及的对象

从根节点可以触及到的对象

2.2.2.可复活的对象

一旦对象的引用被释放(暂时不可达),这个对象就是可复活状态,因为在finalize()中可能复活该对象

2.2.3.不可触及的对象

①.在finalize()之后,可能会进入不可触及状态

②.不可触及的对象不可能复活

③.可以回收

2.2.4.建议

①.谨慎使用使用finalize(),因为操作不慎可能导致错误

//由于finalize()优先级低,无法确定何时被调用,通常只有在gc时才会被调用;但是何时发生GC也不确定;

②.可以使用try-catch-finally来替代他,在finally语句块中释放资源

2.3.如何判断一个对象是否可以被回收?
2.3.1.引用计数法

1>.简介

①.java中,引用和对象是有关联的,如果要操作对象则必须使用引用进行.因此,很显然一个简单的办法就是通过引用计数来判断一个对象是否可以回收;

②.简单说,就是在对象中添加一个引用计数器.每当有一个地方引用他,计数器值加1,每当有一个对象引用失效时,计数器值减1;任何时刻计数器值为零的对象就是不可能再被使用的,那么这个对象就是可回收对象;

注意:由于每次对对象赋值时都要维护引用计数器,并且计数器本身也有一定的消耗;而且这种算法较难解决对象之间相互/循环引用的问题,常常会出现错误.因此JVM的实现一般不采用这样方式,所以该算法已经被淘汰了

2.3.2.根搜索路径算法(/枚举根节点做可达性分析)

1>.简介

在这里插入图片描述

①.为了解决引用计数法的循环引用问题,java使用了可达性分析的方法;

②.所谓的"GC Roots"或者Tracing(跟踪) GC的"根集合"就是一组必须活跃的引用;

③.基本思路就是通过一系列名为"GC Roots"的对象作为起始点,从这个被称为GC Roots的对象开始向下搜索,如果一个对象到GC Roots没有任何引用链相连时,则说明该对象不可达/不可用.也就是说从一个给定的集合的引用作为根节点出发,通过引用关系遍历对象图,能被遍历到的对象就被判定位存活/可达;没有被遍历到的就自然被判定为死亡/不可达

2>.java中哪些对象可以作为GC Roots的对象

①.虚拟机栈(帧栈中的局部变量区,也叫做局部变量表)中引用的对象
//通过引用找到对象实例,从而判断对象是否存活/可达

②.方法区中的类静态属性或者常量引用的对象(全局对象)
//这些对象在任何时候都可能被任何人使用,所以这些对象是不能随便被回收的

③.本地方法栈中JNI(native方法)引用的对象

3.GC算法

3.1.GC算法总体概述

在这里插入图片描述

说明:

①.JVM在进行GC时,并非每次都对上面三个内存区域一起回收的,大部分时候回收的都是指新生代.因此GC按照回收的区域又分了两种类型,一种是普通GC(MinorGC),一种是全局GC(MajorGC or FullGC);

②.MinorGC和FullGC的区别:
A.普通GC(MinorGC):只针对新生代区域的GC,指发生在新生代的垃圾收集动作,因为大多数Java对象的存活率都不高,所以MinorGC非常频繁,一般回收速度较快;

B.全局GC(MajorGC or FullGC):针对年老代的GC,指发生在老年代的垃圾收集动作,出现了MajorGC,经常会伴随至少一次的MinorGC(但并不是绝对的).MajorGC的速度一般要比MinorGC慢上10倍以上,因为MajorGC扫描/清理范围更大;

3.2.垃圾回收算法和垃圾回收器的关系?

①.GC算法是内存回收的方法论,而垃圾收集器则是算法的落地实现;

②.目前为止还没有完美的垃圾收集器出现,更加没有万能的垃圾收集器,只是针对具体应用最合适收集器进行分代收集;

3.3.常见的垃圾回收算法
3.3.1.标记-清除算法(Mark-Sweep)

1>.简介

①."标记-清除"算法适合用于存活对象较多的场合,如老年代;算法分为标记和清除两个阶段,先标记出要回收的对象,然后统一回收这些对象

如图:
在这里插入图片描述

例如:

一个文件夹中可以存放10M的文件,现在已经存放了10个1M的垃圾文件,已经存满了,垃圾回收的时候发现有一个文件是垃圾文件,然后把它删除,此时这个文件夹中空闲的空间为1M,此时如果要创建一个大小为2M的文件就无法办到了;如果垃圾回收一次性将10个垃圾文件全部删除,那么新建的2M大小的文件就可以存放在这里面了!!!

2>.优点:

①.可以解决循环引用的问题

②.必要时才进行回收

③.不需要额外的空间,节约空间

3>.缺点:

①.首先,他的效率比较低(递归与全堆遍历),扫描两次,非常耗时,而且在进行GC的时候,需要停止应用程序,这会导致用户体验非常差劲;

②.其次,这种方式清理出来的空闲内存是不连续的(这点不难理解,我们的死亡对象都是随机的出现在内存的各个角落的,现在把他们清除之后,内存的布局自然会乱七八糟),为了应付这一点,JVM就不得不维持一个内存的空闲列表,这又是一种开销,而且在分配数组对象的时候,寻找连续的内存空间会不太好找;

3.3.2.复制算法(Copying)

1>.简介

①."复制算法"不适合于存活对象较多的场合(如老年代),由于年轻代中的对象基本上都是朝生夕死(90%以上),所以在年轻代的垃圾回收算法使用的是复制算法;首先将新生代中一块大的内存区域分成两块大小相等的区域(A,B),然后在其中的一块区域A中进行对象创建,当垃圾收集开始的时候首先将所有的存活对象全部复制到区域B中,复制完成之后会将区域A中所有的对象(存活对象+未存活对象)全部清理掉,此时区域A就变成一块干净的区域;然后将区域A,B两块区域进行角色互换,下一次就使用区域B了在区域B中进行对象创建,垃圾回收;以此类推,不停的复制,不停的转换…

如图:
在这里插入图片描述

2>.优点:

解决空间碎片化问题,快速,清理干净,因为复制的时候是将存活对象复制到一块非常干净的内存区域中,然后将其他的内存区域全部清空;

3>.缺点:

①.空间浪费,因为对可达对象进行复制的时候当前内存区域可能只是使用了一点点,复制之后就会将他全部清除,每次最多也只能使用一半的空间;

②.如果对象的存活率非常高,我们可以极端一点,假设是100%存活,那么我们需要将所有的对象都复制一遍,并将所有对象的引用地址重置一遍.复制这一项工作所花费的时间,在对象存活率达到一定程度时,将会变得不可忽视,所以从以上描述不难看出,复制算法要想使用,最起码对象的存活率要非常低才行,而且最重要的是,我们必须要克服50%的内存浪费;

3.3.3.复制算法的优化

1>.简介

①.将新生代划分为三块区域(Eden,from,to),区域Eden空间最大,区域form,to空间较小且大小相等,每次使用区域Eden以及区域from,to其中的一块,例如区域Eden和区域from,对象的创建都在区域Eden中;区域Eden中的大对象(无法直接放在区域form/to中的对象)直接进入老年代;进行垃圾回收时,将存活的对象从Eden复制到区域from中,将他们排列好,复制完成将区域Eden清空,此时区域from中存放的就是存活对象;然后又在区域Eden中创建对象,当再次进行垃圾回收时,直接将区域Eden和区域From中那些可用/可达的对象复制到区域to中(当某个对象的存活时间/次数超过了对象存活的阈值(默认为15),那么这个对象就会进入老年代),将他们排列好,复制完成将区域Eden,from清空,此时区域from就是一块干净的区域,然后将from,to两块区域进行角色互换,谁空谁为区域to,原来的区域to就会变成下一GC时的区域from,以此类推,区域from和区域to不停复制,不停的角色互换,直到区域to被填满,区域to被填满之后,会将所有的对象移动到老年代中;

HotSpot虚拟机默认Eden和from的大小比列为8:1,也就是每次新生代中可用的内存空间为个新生代容量的90%(80%+10%),只有10%的内存会被"浪费",当然,我们没有办法保证每次回收都只有不多于10%的对象存活,当区域to空间不够用时,就需要依赖其他的内存(这里指老年代Old)进行分配担保,也就是说,如果区域to没有足够的空间存放上一次新生代收集下来的存活对象,这些对象将直接通过分配担保机制进入老年代;

如图:
在这里插入图片描述

在这里插入图片描述

3.3.4.标记-整理算法(Mark-Compact)

1>.简介

①."标记-整理"算法适合用于存活对象较多的场合,如老年代;他在"标记-清除"算法的基础上做了一些优化.和"标记-清除"算法一样,"标记-整理"首先也需要从根节点开始,对所有需要回收的对象做一次标记,但是之后他并不是直接对可回收的对象进行清理,而是先将所有的存活对象移动/压缩到内存的一端(/内存中的某一块区域),然后直接清除边界(存活对象的连续区域)以外的内存;

如图:
在这里插入图片描述
在这里插入图片描述

2>.优点:

标记-整理算法不仅可以弥补标记-清除算法当中内存区域分散的缺点,也消除了复制算法当中内存减半的高昂代价;

3>.缺点:

标记-整理算法唯一缺点就是效率也不高,不仅要标记出所有的存活对象,还要整理所有存活对象的引用地址,从效率上来说,标记-整理算法要低于复制算法;

3.3.5.以上三种算法的总结

①.内存效率(针对时间复杂度): 复制算法>标记清除算法>标记整理算法;

②.内存整齐度: 复制算法=标记整理算法>标记清除算法;

③.内存利用率: 标记整理算法=标记清除算法>复制算法;

可以看出,从效率上来说,复制算法是当之无愧的老大,但是却浪费了太多内存,为了尽量兼顾上面的三个指标,标记整理算法相对来说更加平滑一些,但是在效率上依然不尽人意,他比复制算法多了一个标记的阶段,又比标记清除算法多了一个整理内存的过程;

3.3.6.分代收集算法(标记整理算法+复制算法)

根据对象的存活周期进行分类,短命对象归为新生代,长命对象归为老年代;根据不同年代的特点,选取合适的收集算法:

在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量对象存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集;而老年代中因为对象存活率高,没有额外空间对它进行分配担保,就必须使用"标记-清理"或者"标记-整理"算法进行回收;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值