JVM 收集算法 垃圾收集器 元空间 引用

勤奋不是嘴上说说而已,而是实际的行动,在勤奋的苦度中持之以恒,永不退却。业精于勤,荒于嬉;行成于思,毁于随。在人生的仕途上,我们毫不迟疑地选择勤奋,她是几乎于世界上一切成就的催产婆。只要我们拥着勤奋去思考,拥着勤奋的手去耕耘,用抱勤奋的心去对待工作,浪迹红尘而坚韧不拔,那么,我们的生命就会绽放火花,让人生的时光更加的闪亮而精彩。

导读:本篇文章讲解 JVM 收集算法 垃圾收集器 元空间 引用,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文


JVM 收集算法

前面我们了解了整个堆内存实际是以分代收集机制为主,但还是没有讲到具体是怎么实现的,那么具体的过程到底是怎么样来实现的呢?我们来了解下

标记-清除算法

在这里插入图片描述
这个回收方法就是首先需要标记出需要回收的对象,然后再依次回收掉被标记的对象,或者是标记出所有不需要回收的对象,只回收未标记的对象
虽然此方法非常简单,但是缺点也是非常明显的,首先如果内存中存在大量的对象,那么可能就会存在大量的标记,并且大规模进行清除。并且一次标记清除之后,连续的内存空间可能会出现许许多多的空隙,碎片化会导致连续内存空间利用率降低.

标记-复制算法

在这里插入图片描述
我们标记算法就是将堆的区域分成两块大小相同放入区域,然后每次只会使用其中一块区域,每当到垃圾回收的时候,将需要回收的对象标记出来,之后将没有标记的复制到另外一边区域,最后将一次清空当前区域,虽然复制浪费了一些时间,但这样能够很好的解决对象大面积回收后造成的碎片化问题
这种算法就非常适用于新生代(因为新生代的回收效率极高,一般不会留下太多的对象)的垃圾回收,而我们之前所说的新生代Survivor区其实就是这个思路,包括8:1:1的比例也正是为了对标记复制算法进行优化而采取的。

标记-整理算法

在这里插入图片描述
上述我们提到了复制算法,此算法在新生区应用完全应用完全没有问题,但如果用在老年区就显得很鸡肋,因为老年区基本都是一些钉子户,它不像新生区那样每次回收都会腾出大量空间,对象,才有机会进入到老年代,所以老年代一般都是些钉子户,可能一次GC后,仍然存留很多对象。而标记复制算法会在GC后完整复制整个区域内容,并且会折损50%的区域,显然这并不适用于老年代。
那么我们能否这样,在标记所有待回收对象之后,不急着去进行回收操作,而是将所有待回收的对象整齐排列在一段内存空间中,而需要回收的对象全部往后丢,这样,前半部分的所有对象都是无需进行回收的,而后半部分直接一次性清除即可。
虽然这样能保证内存空间充分使用,并且也没有标记复制算法那么繁杂,但是缺点也是显而易见的,它的效率比前两者都低。甚至,由于需要修改对象在内存中的位置,此时程序必须要暂停才可以,在极端情况下,可能会导致整个程序发生停顿
所以,我们可以将标记清除算法和标记整理算法混合使用,在内存空间还不是很凌乱的时候,采用标记清除算法其实是没有多大问题的,当内存空间凌乱到一定程度后,我们可以进行一次标记整理算法。


JVM垃圾收集器

Serial收集器

该收集器是比较元老的一个收集器,在较早的jdk,是虚拟机新生代区域收集器的唯一选择,这是一款单线程的垃圾收集器,也就是说,当开始进行垃圾回收的时候,需要暂停所有的线程,直到垃圾收集工作结束,他的新生代收集算法采用的是标记复制法,老年代采用的是标记整理法
在这里插入图片描述

ParNew收集器

这款垃圾收集器相当于是Serial收集器的多线程版本,它能够支持多线程垃圾收集:
在这里插入图片描述
除了多线程支持以外,其他内容基本与Seria收集器一致,并且目前某些JVM默认的服务端模式新生代收集器就是使用的ParNew收集器。

Parallel Scavenge /Parallel Old收集器

Parallel Scavenge同样是一款面向新生代的垃圾收集器,同样采用标记复制算法实现,在JDK6时也推出了其老年代收集器Parallel Old,采用标记整理算法实现:
在这里插入图片描述
与ParNew收集器不同的是,它会自动衡量一个吞吐量,并根据吞吐量来决定每次垃圾回收的时间,这种自适应机制,能够很好地权衡当前机器的性能,根据性能选择最优方案。
目前JDK8采用的就是这种 Parallel Scavenge + Barallel Old的垃圾回收方案。

CMS收集器

在JDK1.5,HotSpot推出了一款在强交互应用中几乎可认为有划时代意义的垃圾收集器:CMS (Concurrent-Mark-Sweep)收集器,这款收集器是HotSpot虚拟机中第一款真正意义上的并发(注意这里的并发和之前的并行是有区别的,并发可以理解为同时运行用户线程和GC线程,而并行可以理解为多条GC线程同时工作)收集器,它第一次实现了让垃圾收集线程与用户线程同时工作。
它主要采用标记清除算法:

在这里插入图片描述

Garbage First(G1)收集器

我们知道,我们的垃圾回收分为Minor GC 、Major GC和Full GC,它们分别对应的是新生代,老年代和整个堆内存的垃圾回收,而G1收集器巧妙地绕过了这些约定,它将整个Java堆划分成2048个大小相同的独立Region 块,每个Region块的大小根据堆空间的实际大小而定,整体被控制在1MB到32MB之间,且都为2的N次幂。所有的Region 大小相同,且在JVM的整个生命周期内不会发生改变。
那么分出这些Region有什么意义呢?每一个Region都可以根据需要,自由决定扮演哪个角色(Eden、Survivor和老年代,收集器会根据对应的角色采用不同的回收策略。此外,G1收集器还存在一个Humongous区域,它专门用于存放大对象(一般认为大小超过了Region容量一半的对象为大对象)这样,新生代、老年代在物理上,不再是一个连续的内存区域,而是到处分布的。

在这里插入图片描述
它的回收过程与CMS大体类似:
在这里插入图片描述
初始化标记:标记出对象能够关联到的对象
并发标记:通过可达性分析,递归整个堆里的对象图,找出要回收的对象
最终标记:对用户线程做一个短暂的暂停,用于处理并发标记阶段漏标的那部分对象。
筛选回收:制定回收计划


元空间

在这里插入图片描述
在JDK8之后,我们堆里面就没有之前的永久代了,随之产生的是一个叫做元空间的东西,类的元信息被存储在元空间中,元空间没有使用堆内存,理论上系统可以使用的内存有多大,元空间就有多大,不会出现永久代存在时的内存溢出问题,永久代就被完完全全的抛弃了
在这里插入图片描述


引用

强引用

在Java中如果变量是一个对象类型,那么它实际上存放的是对象的引用,类似于Object o = new Object()这样的引用类型就是强引用
我们通过前面的学习可以明确,如果方法中存在这样的强引用类型,现在需要回收强引用所指向的对象,那么要么此方法运行结束,要么引用连接断开,否则被引用的对象是无法被判定为可回收的,因为我们说不定后面还要使用它。
所以,当JVM内存空间不足时,JVM宁愿抛出OutOfMlemoryError使程序异常终止,也不会靠随意回收具有强引用的“存活”对象来解决内存不足的问题。
强引用写法: Object o = new Object();

软引用

软引用不像强引用那样不可回收,但一旦JVM内存不足的时候,它会确保抛出异常之前,清理掉软引用指向的对象
软引用写法:SoftReference reference = new SoftReference<>(new Object());

弱引用

弱引用的生命周期比软引用的还要短,在进行垃圾回收的时候,不管当前内存是否充足,都会回收它的内存
弱引用写法
WeakReference reference = new WeakReference<>(new Object());

虚引用

随时可能被回收
也就是说我们无论调用多少次get()方法得到的永远都是null,因为虚引用本身就不算是个引用,相当于这个对象不存在任何引用,并且只能使用带队列的构造方法,以便对象被回收时接到通知。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/141533.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!