类加载过程(双亲委派模型)JVM内存模型以及垃圾回收机制算法(二)

上篇文章我们主要讲了类加载过程,双亲委派模型,和JVM内存模型,本篇文章我们主要讲垃圾回收算法,垃圾回收器,通过上篇文章,我们知道JVM内存主要包括五大块:1. 堆内存,2. 方法区(元空间),3. 本地方法栈,4. 虚拟机栈,5. 程序计数器,其中,堆内存是最大的一块,而且我们new的对象就在堆内存中开辟内存,因此,堆内存可以说是JVM中最重要的一块,因此,垃圾回收就是针对堆内存的,现在我们就来讲讲垃圾回收。
1. 堆内存模型在这里插入图片描述

JDK1.8及以前,我们的堆内存的结构是分代模型,如上图所示,即分为新生代和老年代,新生代和老年代的默认大小是1:2,其中新生代又分为eden区(伊甸园区),s0区,s1区,s0和s1区合并成为幸存区,默认情况下,eden:s0:s1 = 8:1:1,而在JDK1.8之后,堆内存的结构是分区模型,就不分为年轻代和老年代了,而是将整合内存划分成一块一块的区,这么做的目的也是为了提高我们垃圾回收的效率。
2. 垃圾回收算法
在讲垃圾回收之前,我们先讲三个算法,这三个算法也是JVM从诞生到现在的所有算法。
2.1 标记清除算法
在这里插入图片描述
如图,比如我们内存中有四个对象,A,B,C,D,现在C对象已经没有被引用了,那么GCRoots在内存中找到C对象,回先讲C进行标记,表明C已经是垃圾对象了,然后垃圾收集器就会将C回收,将C的内存地址空出来。
优点:算法简单,实现方便,效率高
缺点:造成内存空间的不连续性,如果程序后面产生的垃圾占国内高的内存大小比C大,那么就只能往D后面存放,会造成空间浪费。
2.2 标记整理算法
在这里插入图片描述
如图,相对于标记清除算法,标记整理算法是在C对象被删除后,会将C对象后面的元素全部往前移动,避免空间的碎片化,不连续化。
优点:空间连续,内存空间无浪费。
缺点:对象需要移动,会造成空间的引用内存地址发生变化,效率相对于标记清除算法低。
2.3 标记复制算法
在这里插入图片描述
如图,标记复制算法是在内存里面开辟出来两块一样大小的内存,当C对象被标记为垃圾对象之后,会将ABD三个对象复制到第二块内存中,然后将第一块内存清空,如此往复循环。
优点:避免了内存碎片化,效率高
缺点:因为需要开辟两块一样大小的内存,因此浪费内存。
3. GCRoots
通过上面的原理我们知道,算法中是分两步进行的,第一步是标记,第二步是清除垃圾,标记是有GCRoots来做的,而清除是由垃圾收集器来实现的,现在我们讲讲如何找到垃圾对象并进行标记。
3.1 可达性分析算法
如何找到垃圾是采用可达性分析算法实现的,原理非常简单,首先先确定谁是GCRoots,谁能作为GCRoots,GCRoots一般是我们栈内存中的引用,局部变量,原因是我们知道,当我们创建一个对象的时候,new的对象存储在堆内存中,而对象的引用是存在栈中的,且从栈中指向了堆内存中的实例化对象,所以使用栈中的局部变量,对象的引用作为GCRoots,而可达性分析算法就是,顺着当前的对象指向往下寻找,凡是能找到的都是非垃圾对象,否则就是垃圾对象,因此我们顺着栈中的引用对象的指向往堆内存中寻找,找到的就是非垃圾对象,如果该对象中还引用了其他对象,则一直往下寻找,知道再没有引用为止,剩下的没有被找到的就会被GCRoots标记为垃圾对象。
4. 分代算法
所谓分代算法就是根据堆内存模型,新生代和老年代,不同的代采用不同的算法,以JDK1.8为例,其中新生代采用标记复制算法,这也是新生代中s0和s1存在的原因,而老年代采用标记整理算法。
那么为什么年轻代采用标记复制算法,而老年代采用标记整理算法呢?
根据研究,发现我们生产的绝大部分的垃圾都是在年轻代的eden区就会被回收,因此说明年轻代垃圾回收的次数多,而老年代垃圾回收次数少,标记复制算法效率比标记整理算法高,因此年轻代采用标记复制算法,老年代采用标记整理算法。而标记清除算法由于会产生内存的不连续性,因此分代算法中没有用到。
当然垃圾回收机制触发的条件是当新生代的eden区满了或者s0,s1区满了,或者老年代满了的时候都会触发垃圾回收,其中新生代我们叫minor gc,只回收新生代的垃圾,而老年代叫full gc,因为老年代垃圾回收的时候会将新生代一块进行回收,所以消耗时间比较久,此时会有短暂的延迟,我们称之为stop the world(stw),这也是我们jvm内存优化的点,减少full gc。
5. 对象晋升
现在我们来讨论一下,一个对象什么时候会进入老年代?
5.1. 我们生产的对象首先是在新生代的eden区,当eden区满了的时候,就会进行垃圾回收,此时绝大部分对象因为没有被引用了而被回收,而没有被回收的对象就会进入s0区,此时对象会记录垃圾回收次数1次,而当s0区满了时候,就会进行垃圾回收,此时根据标记复制算法,未被回收的对象会进入s1区,回收次数加1变成了2,当s1区满了的时候,根据标记复制算法未被回收的垃圾会进入s0区,回收次数再次加1,依次循环,等到对象回收次数达到15次的时候(默认是15次,可以设置修改),就会晋升到老年区
5.2. 如果我们生产的对象很大,我们知道,新生代和老年代的比例是1:2,当新生代的eden区存不下该对象时,该对象会直接晋升到老年代,当然,如果老年代也存不下该对象,JVM就会报内存溢出(OOM),这也是OOM出现的原因,即老年代都存不下对象的时候。
6. 垃圾收集器
从jdk诞生以来,垃圾收集器就不断的在演化,最开始采用的是串行垃圾收集器(Serial收集器),Serial收集器在收集被标记的垃圾时候,会将所有正在运行的线程全部叫停(stop the world),但是Serial收集器是单线程的,也就是一次只有一个线程在清除垃圾,因此效率低,stw时间比较久。
后来进行优化诞生了并行垃圾收集器(Parallel Scavenge收集器)就是我们现在jdk1.8使用的,与Serial收集器相比,并行垃圾收集器是多线程的,因此效率高很多,stw的时间也减少了。
再发展就出现了cms垃圾收集器,cms垃圾收集器采用的是标记清除算法和标记整理算法,据说,cms垃圾收集可以将stw时间控制在100ms以内。
从jdk1.8以上,就采用分区模型了,因此像后面的G1垃圾收集,基本已经可以忽略stw时间了,因为是分区的,垃圾收集器可以将一部分区域收集之后就可以运行正常程序了,而jdk1.8现在用的垃圾收集器,需要将整个新生代或者老年代全部收集完才能运行,因此G1据说可以达到10ms以内,

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值