JVM-内存分配策略和垃圾回收策略

本文详细介绍了JVM内存的垃圾回收策略,包括对堆、方法区、本地方法栈中对象的回收条件。讨论了四种引用类型,如强引用、软引用等,并详细解析了JVM内存分配的五大策略。此外,文章还涵盖了多种垃圾回收算法,如标记-清除、复制、标记-整理和分代回收,并探讨了STW、安全点和安全区的概念。最后,讲解了不同类型的垃圾收集器,如Serial、Parallel Scavenge、CMS和G1的特点与工作原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

对什么区域进行垃圾回收?

  • 1.堆
  • 2.方法区中静态属性引用的对象
  • 3.方法区中常量引用的对象
  • 4.本地方法栈中JNI引用的对象
    例如: private native void sort(int[] array);

栈是线程私有的,不需要回收

什么情况下回收?

  • 1.当引用计数器为0(循环调用则会出问题)
  • 2.可达性分析遍历不到的对象进行回收.

四种引用:

  • 强引用:可达性分析不可达的情况下,会被GC
  • 软引用:内存不足时,会被GC
  • 弱引用:只能存活到下一次GC前
  • 虚引用:不会对对象的生命周期有任何影响,无法通过它获取对象实例,唯一的作用就是在对象被GC回收前收到一个通知.

JVM内存分配策略:

1.对象优先在Eden分配

大多数情况下,对象在新生代Eden区中分配。

2.大对象直接进入老年代

大对象是指,需要大量连续内存空间的java对象(字符串,数组).-XX:PretenureSizeThreshold可以令大于这个值的对象直接在老年代分配内存,可以避免在Survivor区域发生大量的内存复制.

XX:PretenureSizeThreshold 该参数只能在ParNew和Serial两款收集器使用.

3.动态对象年龄判断

如果Survivor空间中的相同年龄的所有对象大小的总和大于Survivor空间的一半,大于该年龄的对象直接进入老年代.

4.长期存活的对象将进入老年代

对象在Survivor每熬过一次GC,对象年龄就会加1,当年龄到达默认值15,则进入老年代

5.空间分配担保

检测老年代的可用连续空间是否大于即将晋升到老年代的对象的大小,如果大于,则进行一次MinorGC,如果小于,或者设置不允许冒险,那次时需进行一次FullGC.

垃圾回收算法:

1.标记-清除算法

分为标记和清除两个阶段,首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象.
在这里插入图片描述
缺点:

  • 效率不高
  • 会产生不连续的内存碎片,空间碎片太多可能会导致以后在程 序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作

2.复制算法

将可用内存分为大小相等的两个区域,每次只使用其中一片区域.当这一片区域用完了,再将还活着的对象复制到另一块区域,将之前区域的对象清理掉.
在这里插入图片描述

缺点:

  • 1.内存缩小为原来的一半,内存利用率太低
  • 2.如果对象存活率高,复制浪费时间

3.标记-整理法

分为标记和整理两个阶段,先标记出所有需要回收的对象,然后将存活对象整理到内存的一端.然后清除回收的对象
在这里插入图片描述

4.分代回收算法

  • 年轻代采用标记清除和整理算法
  • 老年代采用复制算法

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

什么是STW?

STW(Stop The World):正常执行的用户线程全部停止.

精确式GC:

HotSpot===>OopMap

准确式GC,当执行系统停顿下来后,并不需要一个不漏地检查完所有 执行上下文(例如栈帧中的本地变量表)和全局(例如常量或类静态属性)的引用位置,虚拟机应当是有办法直接得知哪些地方存放着对象引用。在 HotSpot的实现中,是使用一组称为OopMap的数据结构来达到这个目的的,在类加载完成的时候,HotSpot就把对象内什么偏移量上是什么类型的数据计算出来,在JIT编译过程中,也会在特定的位置记录下栈和寄存器中哪些位置是引用。这样,GC在扫描时就可以直接得知这些信息了。

savepoint :安全点

在OopMap的协助下,HotSpot可以快速且准确地完成GC Roots枚举,但一个很现实的问 题随之而来:可能导致引用关系变化,或者说OopMap内容变化的指令非常多,如果为每一 条指令都生成对应的OopMap,那将会需要大量的额外空间,这样GC的空间成本将会变得很 高。实际上,HotSpot也的确没有为每条指令都生成OopMap,前面已经提到,只是在“特定的 位置”记录了这些信息,这些位置称为安全点(Safepoint),即程序执行时并非在所有地方都 能停顿下来开始GC,只有在到达安全点时才能暂停。Safepoint的选定既不能太少以致于让 GC等待时间太长,也不能过于频繁以致于过分增大运行时的负荷.

对于Sefepoint,另一个需要考虑的问题是如何在GC发生时让所有线程(这里不包括执行 JNI调用的线程)都“跑”到最近的安全点上再停顿下来。有两种方式:

抢先式中断:

先将线程主动中断,没有执行到指定安全点的据需执行,直到安全点.(用得少,几乎没有)
缺点:
Thread.sleep() wait(),有可能跑不到中断点.

主动式中断:

当GC需要中断线程时,不直接对线程进行操作,而是设置一个标志,让用户线程去检查,然后中断.

安全区:

使用Safepoint似乎已经完美地解决了如何进入GC的问题,但实际情况却并不一定。 Safepoint机制保证了程序执行时,在不太长的时间内就会遇到可进入GC的Safepoint。但是, 程序“不执行”的时候呢?所谓的程序不执行就是没有分配CPU时间,典型的例子就是线程处 于Sleep状态或者Blocked状态,这时候线程无法响应JVM的中断请求,“走”到安全的地方去 中断挂起,JVM也显然不太可能等待线程重新被分配CPU时间。对于这种情况,就需要安全 区域(Safe Region)来解决。 安全区域是指在一段代码片段之中,引用关系不会发生变化。在这个区域中的任意地方 开始GC都是安全的。

垃圾收集器

如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。
连线表示可以搭配使用.
在这里插入图片描述

Serial收集器(新生代收集器)

在这里插入图片描述

开启参数:-XX:+UseSerialGC

单线程的收集器,"单线程"的意义并不仅仅说明他只会使用一个CPU或者一条收集线程去完成收集垃圾操作,最重要的是他在进行垃圾收集的时候,必须暂停其他线程的工作,直到它收集结束.新生代和老年代都会Stop The World.
就好比: “你妈妈在给你打扫房间的时候,肯定也会让你老老实实地在椅子上或者房间 外待着,如果她一边打扫,你一边乱扔纸屑,这房间还能打扫完?”

ParNew收集器(新生代收集器)

在这里插入图片描述

Serial收集器的多线程版本,除了使用多条线程进行垃圾收集之外,其余行为还包括Serial收集器的可用控制参数
(-XX:SurvivorRatio , -XX:PretenureSizeThreshold ,-XX:HandlePromotionFailure),收集算法,Stop The World,对象分配规则,回收策略等都与Serial收集器完全一样.

对新生代并行收集,对老年代还是单线程收集.

Parallel Scavenge收集器(新生代收集器)

开启参数:-XX:+UseParallelGC

Parallel Scavenge关注点: 可控的吞吐量 (与ParNew相比可控制GC时用户现场停顿时间)
吞吐量计算公式: 运行用户代码时间/(运行用户代码时间+垃圾收集时间) 例:虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%

停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能提升用户体验,而高吞吐量则可以高效的利用CPU时间,尽快完成程序的运算任务

Parallel Scavenge提供参数用于精确控制吞吐量

  • -XX:MaxGCPauseMills //最大垃圾收集停顿时间,收集器将尽可能地保证内存回收花费的时间不超过设定值
  • -XX:GCTimeRatio //吞吐量大小,也就是垃圾收集时间占总时间的比率,相当于是吞吐量的倒数
  • -XX:+UseAdaptiveSizePolicy //内存调优委托给虚拟机管理,这是一个开关参数,当这个参数打开之后,就不需要手工指定新生代的大小(-Xmn)、 Eden与Survivor区的比例(-XX:SurvivorRatio)、晋升老年代对象年龄(-XX: PretenureSizeThreshold)等细节参数了,虚拟机会根据当前系统的运行情况收集性能监控信 息,动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量,这种调节方式称为GC 自适应的调节策略。

Serial Old收集器(老年代收集器)

Serial Old是Serial收集器的老年代版本,它同样是一个单线程收集器,使用“标记-整理”算法。

图可看Serial收集器

Parallel Old (老年代的多线程版本)

在这里插入图片描述

Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法

CMS(Concurrent Mark Sweep)收集器

在这里插入图片描述
开启: -XX:+UseConcMarkSweepGC

(CMS收集器是一种以获得最短回收停顿时间为目标的收集器.CMS收集器是基于标记清除算法实现的.整个过程分为4个步骤:

  • 初始标记(CMS initial Mark) :标记一下GC Roots能直接关联到的对象(GCRoot和它的子节点),速度很快
  • 并发标记(CMS concurrent Mark) :对未标记的节点进行标记
  • 重新标记(CMS Remark) :修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录
  • 并发清除(CMS concurrent sweep) : 最后进行对象清除
    在初始标记和重新标记会STW

由于整个过程中耗时最长的并发标记和并发清除过程收集器线程都可以与用户线程一起 工作,所以,从总体上来说,CMS收集器的内存回收过程是与用户线程一起并发执行的。
CMS默认回收的线程= (CPU数量+3)/4

3个明显的缺点:

  • CMS收集器对CPU资源非常敏感
    在并发标记的时候,占用了一部分线程(CPU资源)导致应用程序变慢

  • CMS收集器无法处理浮动垃圾(Floating Garbage),可能出现“Concurrent Mode Failure”失败而导致另一次Full GC的产生。
    由于CMS并发清理阶段用户线程还在运行着,伴随程序运行自然就还会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS无法 在当次收集中处理掉它们,只好留待下一次GC时再清理掉。

  • 有大量空间碎片产生
    因为该收集器是基于标记-清除算法实现的.所以会意味着产生大量空间碎片.

    • 为了解决这个问题,CMS收集器提供了一个-XX:+UseCMSCompactAtFullCollection开 关参数(默认就是开启的),用于在CMS收集器顶不住要进行FullGC时开启内存碎片的合并整理过程,内存整理的过程是无法并发的,空间碎片问题没有了,但停顿时间不得不变长。
    • 还提供了另外一个参数-XX:CMSFullGCsBeforeCompaction,这个参数是用于设置执行多少次不压缩的Full GC后,跟着来一次带压缩的(默认值为0,表示每次进入Full GC时都进行碎片整理)。

G1收集器(Gabage First)

在这里插入图片描述

G1能充分利用多CPU、多核环境下的硬件优势,使用多个CPU(CPU或者 CPU核心)来缩短Stop-The-World停顿的时间,部分其他收集器原本需要停顿Java线程执行的 GC动作,G1收集器仍然可以通过并发的方式让Java程序继续执行。

G1收集器之所以能建立可预测的停顿时间模型,是因为它可以有计划地避免在整个Java 堆中进行全区域的垃圾收集。G1跟踪各个Region里面的垃圾堆积的价值大小(回收所获得的 空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region(这也就是Garbage-First名称的来由)。这种使用Region划分 内存空间以及有优先级的区域回收方式,保证了G1收集器在有限的时间内可以获取尽可能高 的收集效率。

仍然属于分代收集器,只不过将内存划分为多个Regin
特点: 空间整合
算法: 既属于标记整理,又属于复制

分为四个阶段:

  • 初始标记阶段:
    仅仅只是标记一下GC Roots能直接关联到的对象,并且修改 TAMS(Next Top at Mark Start)的值,让下一阶段用户程序并发运行时,能在正确可用的 Region中创建新对象,这阶段需要停顿线程,但耗时很短。
  • 并发标记阶段:
    是从GC Root开始对堆中对象进行可达性分析,找出存活的对象,这阶段耗时较长,但可与用户程序并发执行。
  • 最终标记阶段:
    为了修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机将这段时间对象变化记录在线程Remembered Set Logs里面,最终标记阶段需要把Remembered Set Logs的数据合并到Remembered Set中,这阶段需要停顿线程,但是可并行执行。
  • 筛选回收阶段:
    首先对各个Region的回收价值和成本进行排序, 根据用户所期望的GC停顿时间来制定回收计划,这个阶段其实也可以做到与用户程序一起并发执行,但是因为只回收一部分Region,时间是用户可控制的,而且停顿用户线程将大幅提高收集效率。

Young GC:
1.扫描根GCRoots
2.更新RememberSet记录回收对象的数据结构
3.检测RememberSer哪些是要从年轻代到老年代的
4.Copy对象,要么去to区,要么去老年代
5.清理工作
MixedGC

与其他GC收集器相比,G1具备如下特 点。

  • 并行与并发:G1能充分利用多CPU、多核环境下的硬件优势,使用多个CPU(CPU或者 CPU核心)来缩短Stop-The-World停顿的时间
  • 分代收集:与其他收集器一样,分代概念在G1中依然得以保留。虽然G1可以不需要其他收集器配合就能独立管理整个GC堆,但它能够采用不同的方式去处理新创建的对象和已 经存活了一段时间、熬过多次GC的旧对象以获取更好的收集效果。
  • 空间整合:与CMS的“标记—清理”算法不同,G1从整体来看是基于“标记—整理”算法实现的收集器,从局部(两个Region之间)上来看是基于“复制”算法实现的,但无论如何,这 两种算法都意味着G1运作期间不会产生内存空间碎片,收集后能提供规整的可用内存。这种 特性有利于程序长时间运行,分配大对象时不会因为无法找到连续内存空间而提前触发下一 次GC。
  • 可预测的停顿:这是G1相对于CMS的另一大优势,降低停顿时间是G1和CMS共同的关 注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一 个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值