🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇
⭐ JVM ⭐
🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇
今日推荐歌曲: 像我这样的人 -- 毛不易 🎵🎵
目录
前言
之前讲了Java运⾏时内存的各个区域。对于程序计数器、虚拟机栈、本地⽅法栈这三部分区域⽽⾔, 其⽣命周期与相关线程有关,随线程⽽⽣,随线程⽽灭。并且这三个区域的内存分配与回收具有确定 性,因为当⽅法结束或者线程结束时,内存就⾃然跟着线程回收了。
因此我这里所讲的 有关 内存分配 和 回收关注的为Java堆与⽅法区这两个区域。
Java堆中存放着⼏乎所有的对象实例,垃圾回收器在对堆进⾏垃圾回收前,⾸先要判断这些对象哪些 还存活,哪些已经"死去"。
内存VS对象
在Java中,所有的对象都是要存在内存中的(也可以说内存中存储的是⼀个个对象),因此我们将内 存回收,也可以叫做死亡对象的回收。
回收垃圾一般分为两个步骤:
1. 识别垃圾
2. 清理垃圾
那么对于上述步骤,这里总结了以下几个垃圾回收算法:
①死亡对象的判断算法
a) 引⽤计数算法
引⽤计数描述的算法为:
给对象增加⼀个引⽤计数器,每当有⼀个地⽅引⽤它时,计数器就+1;当引⽤失效时,计数器就 -1; 任何时刻计数器为0的对象就是不能再被使⽤的,即对象已"死"。引⽤计数法实现简单,判定效率也⽐较⾼,在⼤部分情况下都是⼀个不错的算法。⽐如Python语⾔就 采⽤引⽤计数法进⾏内存管理。
但是,在主流的JVM中没有选⽤引⽤计数法来管理内存,有两个原因:
1. 对每个对象都加一个计数器,那么会消耗的额外的内存空间。
2. ⽆法解决对象 的循环引⽤问题 (主要原因)
b)可达性分析算法
上⾯我们讲了,Java并不采⽤引⽤计数法来判断对象是否已"死",⽽采⽤"可达性分析"来判断对象 是否存活(同样采⽤此法的还有C#、Lisp-最早的⼀⻔采⽤动态内存分配的语⾔)。
此算法的核⼼思想为:
通过⼀系列称为"GCRoots"的对象作为起始点,从这些节点开始向下搜索,搜 索⾛过的路径称之为"引⽤链",当⼀个对象到GCRoots没有任何的引⽤链相连时(从GCRoots到这个对 象不可达)时,证明此对象是不可⽤的。
在写代码的过程中,会定义很多的变量.
比如,栈上的局部变量/方法区中的静态类型的变量/常量池中引用的对象
就可以从这些变量作为起点,出发,尝试去进行"遍历"
所谓的遍历就是会沿着这些变量中持有的引用类型的成员,再进一步的往下进行访问.
所有能被访问到的对象,自然就不是垃圾了.剩下的遍历一圈也访问不到的对象,自然就是垃圾以下图为例:本质上是用“时间换空间”相比于引用计数,需要消耗更多的额外的时间,但是总体来说,还是可控的.不会产生类似于"循环引用"这样的问题.
对象Object5-Object7之间虽然彼此还有关联,但是它们到GCRoots是不可达的,因此他们会被判定为 可回收对象。
②垃圾回收算法
通过上⾯的学习我们可以将死亡对象标记出来了,标记出来之后我们就可以进⾏垃圾回收操作了,在那之前,先看下垃圾回收机器使⽤的⼏种算法(这些算法是垃圾收集器的指导 思想)。
a) 标记-清除算法
"标记-清除"算法是最基础的收集算法。算法分为"标记"和"清除"两个阶段:⾸先标记出所有需要回收 的对象,在标记完成后统⼀回收所有被标记的对象。后续的收集算法都是基于这种思路并对其不⾜加以改进⽽已。
原理:分为两个阶段:
标记阶段:从GC Roots开始,标记所有可达的对象。
清除阶段:回收未被标记的对象。
优点:简单直观。
"标记-清除"算法的不⾜主要有两个:
- 效率问题: 标记和清除这两个过程的效率都不⾼
- 空间问题: 标记清除后会产⽣⼤量不连续的内存碎⽚,空间碎⽚太多可能会导致以后在程序运⾏中 需要分配较⼤对象时,⽆法找到⾜够连续内存⽽不得不提前触发另⼀次垃圾收集。
b) 复制算法
"复制"算法是为了解决"标记-清理"的效率问题。它将可⽤内存按容量划分为⼤⼩相等的两块,每次只 使⽤其中的⼀块。当这块内存需要进⾏垃圾回收时,会将此区域还存活着的对象复制到另⼀块上⾯, 然后再把已经使⽤过的内存区域⼀次清理掉。这样做的好处是每次都是对整个半区进⾏内存回收,内存分配时也就不需要考虑内存碎⽚等复杂情况,只需要移动堆顶指针,按顺序分配即可。此算法实现 简单,运⾏⾼效。
原理:将存活对象从一个内存区域复制到另一个区域,然后清空原区域。
优点:避免了内存碎片问题。
缺点:需要两倍的内存空间。
适用场景:常用于新生代垃圾回收,因为新生代对象大多生命周期短。
算法的执⾏流程如下图:
现在的商⽤虚拟机(包括HotSpot都是采⽤这种收集算法来回收新⽣代)
c) 标记-整理算法
复制收集算法在对象存活率较⾼时会进⾏⽐较多的复制操作,效率会变低。因此在⽼年代⼀般不能使 ⽤复制算法。
针对⽼年代的特点,提出了⼀种称之为"标记-整理算法"。标记过程仍与"标记-清除"过程⼀致,但后续 步骤不是直接对可回收对象进⾏清理,⽽是让所有存活对象都向⼀端移动,然后直接清理掉端边界以 外的内存。
原理:结合了标记-清除和复制算法的优点。标记阶段识别存活对象,然后将这些对象移动到内存的一端,另一端被清空。
优点:解决了内存碎片问题,提高了内存利用率。
适用场景:常用于老年代垃圾回收。
流程图如下
d) 分代算法⭐⭐
分代算法和上⾯讲的3种算法不同,分代算法是通过区域划分,实现不同区域和不同的垃圾回收策 略,从⽽实现更好的垃圾回收。这就好⽐中国的⼀国两制⽅针⼀样,对于不同的情况和地域设置更符 合当地的规则,从⽽实现更好的管理,这就时分代算法的设计思想。
当前JVM垃圾收集都采⽤的是"分代收集(GenerationalCollection)"算法,这个算法并没有新思想, 只是根据对象存活周期的不同将内存划分为⼏块。⼀般是把Java堆分为新⽣代和⽼年代。
在新⽣代 中,每次垃圾回收都有⼤批对象死去,只有少量存活,因此我们采⽤复制算法;⽽⽼年代中对象存活 率⾼、没有额外空间对它进⾏分配担保,就必须采⽤"标记-清理"或者"标记-整理"算法。
原理:基于对象生命周期的观察,将堆内存划分为新生代和老年代。
新生代:存放新创建的对象,采用复制算法。
老年代:存放存活时间较长的对象,采用标记-压缩或标记-清除算法。
优点:提高了垃圾回收的效率。
哪些对象会进⼊新⽣代?哪些对象会进⼊⽼年代?
• 新⽣代:⼀般创建的对象都会进⼊新⽣代;
• ⽼年代:⼤对象和经历了N次(⼀般情况默认是15次)垃圾回收依然存活下来的对象会从新⽣代 移动到⽼年代。
⾯试题:
请问了解MinorGC和FullGC么,这两种GC有什么不⼀样吗?
1. MinorGC⼜称为新⽣代GC:指的是发⽣在新⽣代的垃圾收集。因为Java对象⼤多都具备朝⽣⼣灭 的特性,因此MinorGC(采⽤复制算法)⾮常频繁,⼀般回收速度也⽐较快。
2. Full GC⼜称为⽼年代GC或者MajorGC:指发⽣在⽼年代的垃圾收集。出现了MajorGC,经常会伴 随⾄少⼀次的MinorGC(并⾮绝对,在ParallelScavenge收集器中就有直接进⾏FullGC的策略选择 过程)。MajorGC的速度⼀般会⽐MinorGC慢10倍以上。
1)当代码中new 出一个新的对象,这个对象就是被创建在伊甸区的.伊甸区中就会有很多的对象.
—个经验规律:伊甸区中的对象,大部分是活不过第一轮GC .这些对象都是“朝生夕死”,生命周期非常短!
2)第一轮GC扫描完成之后,少数伊甸区中幸存的对象,就会通过复制算法,拷贝到生存区
后续GC的扫描线程还会持续进行扫描.不仅要扫描伊甸区,也要扫描生存区的对象.
生存区中的大部分对象也会在扫描中被标记为垃圾.少数存活的,就会继续使用复制算法,拷贝到另外一个生存区中!!
只要这个对象能够在生存区中继续存活,就会被复制算法继续拷贝到另一半的生存区中.
每次经历一轮GC的扫描,对象的年龄都会+1
3)如果这个对象在生存区中,经过了若干轮GC仍然健在~~
JVM就会认为,这个对象生命周期大概率很长,就把这个对象从生存区,拷贝到老年代~~
4)老年代的对象,当然也要被GC扫描,但是扫描的频次就会大大降低了.
老年代的对象,要G早G了~~~既然没G说明生命周期应该是很长的.频繁GC扫描意义也不大,白白浪费时间.不如放到老年代,降低扫描频率.
5)对象在老年代寿终正寝, 此时JVM就会按照标记整理的方式,释放内存.
③垃圾收集器
垃圾收集器是一种自动化的内存管理工具,用于在程序运行时识别和清理不再被程序使用的内存,从而避免内存泄漏并提高内存利用率。以下是关于垃圾收集器的总结:
定义:
- 垃圾收集器是一种用于自动管理内存的软件组件,其主要任务是在程序运行时识别和回收不再使用的内存,以避免内存泄漏和提高内存利用率。
工作原理:
- 标记-清除(Mark and Sweep):垃圾收集器通过标记不再被程序引用的对象,然后清理掉这些未被标记的对象。这个过程包括标记阶段和清理阶段。
- 复制(Copying):将存活的对象复制到另一个内存空间,然后清理原始空间中的所有对象。这种方法通常用于实现年轻代的垃圾收集。
- 标记-整理(Mark and Compact):类似于标记-清除,但在清理阶段会将存活的对象压缩到内存的一端,以便释放出连续的内存空间。
优缺点:
- 优点:
- 自动化管理:减少了程序员手动管理内存的工作量。
- 避免内存泄漏:及时回收不再使用的内存,避免了内存泄漏的发生。
- 提高内存利用率:通过回收不再使用的内存,提高了内存的利用率。
- 缺点:
- 开销:垃圾收集器本身会消耗一定的计算资源,可能会影响程序的性能。
- 暂停:在进行垃圾回收时,程序可能会出现暂停,导致性能下降或用户体验变差。
- 实现复杂性:设计和实现高效的垃圾收集器是一项复杂的工程,需要考虑多种因素。
类型:
- 串行垃圾收集器:单线程执行垃圾收集任务,适用于小型应用或测试目的。
- 并行垃圾收集器:多线程执行垃圾收集任务,提高了垃圾收集的效率,适用于多核处理器。
- 并发垃圾收集器:允许垃圾收集与程序的执行并发进行,减少了垃圾收集导致的停顿时间,适用于需要低延迟的应用。
常见实现:
- Java垃圾收集器:包括串行收集器、并行收集器、并发标记清除收集器(CMS)、G1收集器等,每种收集器都有不同的适用场景和特点。
- C/C++垃圾收集器:如Boehm垃圾收集器等,提供了在C/C++中进行自动内存管理的解决方案。
总的来说,垃圾收集器是一种重要的内存管理工具,能够帮助程序员避免内存泄漏和提高内存利用率,但需要在性能和开销之间做出权衡。
总结
以上就是关于 JVM 的最后一章了.
博客不易,希望可以帮助到大伙,动动小手点个赞作者会开心很久,感谢阅览。