1.3内存分配策略和垃圾回收器

1.3内存分配策略和垃圾回收器

1.概述

程序计数器、虚拟机栈、本地方法栈随着线程而生随着线程而亡,占用内存大小在编译期间就确定好了,因此不需要去考虑回收问题,当方法结束或者线程死亡这部分内存也随之回收掉。

堆,方法区需要进行垃圾回收

2.内存分配策略

本节验证使用HotSpot虚拟机,以客户端模式(Serial+Serial Old)收集器组合进行内存分配和垃圾回收还有一种是服务端收集器组合(Parallel Scavenge+Serial Old)

(1)对象实例分配过程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W1y2iZxV-1686273963084)(C:\Users\10059\AppData\Roaming\Typora\typora-user-images\image-20220307113235100.png)]

(2)对象实例分配策略

①优先分配到Eden区

为java对象分配内存时,首先分配到新生代的Eden区,如果此时Eden区空间不足,则进行一次MinorGC。在MinorGC之前还会进行担保策略判断(JDK8默认开启)。MinorGC后存活的对象会移动到Survivors区然后age++,如果Survivors区放不下则直接放到老年代

②大对象直接分配到老年代

大对象指的是需要大量连续内存的对象,一般指很长的字符串或者数组对象(普通对象内存占用多少就是多少,不要求连续)。分配内存遇到大对象时,这时新生代完全放不下了,会发生一次MinorGC然后放到老年代,如果老年 代还放不下,就会发生FullGC。所以说大对象的代价是很大的,特别是那种朝生夕死的大对象。要尽量避免创建大对象

③长期存在的对象分配到老年代

如果一个对象在经历了很多次GC后在Survivors区内仍然没死,当它的age到达阈值15(默认配置 -XX:MaxTenuringThreshold=15)时,就会从Survivor区移动到老年代

④动态年龄判断

指的是如果在Survivors区中,相同年龄的对象总数>=(1/2的Survivors区),那么>=这个年龄的对象直接晋升到老年代

⑤空间分配担保策略

根据不同的垃圾收集器组合,在空间分配担保策略上会有所不同。空间分配担保策略指的是,在进行MinorGC之前,Ⅰ.如果老年代最大可用连续空间大小>新生代里对象总大小,那么就直接进行MinorGC不去担保了。Ⅱ.如果开启了担保策略(JDK6之后默认开启,-XX:+HandlePromotionFailure这个参数在JDK6之后没有用了),那么JVM会进行一次判断:是否老年代最大可用连续内存空间大小>之前每一次GC后晋升到老年代的对象的平均大小,如果成立则老年代担保这次的MinorGC可以进行。如果不成立或者没开启担保策略,那么此次MinorGC改为FullGC

⑥逃逸分析和TLAB
  1. 逃逸分析(JDK8默认启用):指一个对象是否逃逸出了方法或者线程,如果没有则进行栈上分配、锁消除、标量替换
    1. 全局逃逸:指在方法或者线程中,一个对象引用传递给了类变量/成员变量、或者对象引用存储在了一个已逃逸的对象中、或者对象引用作为方法返回出去了
    2. 参数级逃逸:指一个对象引用作为参数传递到了另一个方法中
    3. 栈上分配:如果对象没有逃逸出方法或者线程,将对象直接分配到栈内存
    4. 锁消除:逃逸分析可以判断出某个对象是否始终只被一个线程访问,如果只被一个线程访问,那么对该对象的同步操作就可以转化成没有同步保护的操作
    5. 标量替换:将聚合量替换为标量
  2. TLAB:Thread-Local-Allocated-Buffer(本地线程分配缓冲区)

TLAB位于Eden区,默认开启。每一个线程都有自己的TLAB,缺省占Eden区的1%(当然可以通过选项-XX:TLABWasteTargetPercent设置TLAB空间所占用Eden空间的百分比大小),为线程私有内存空间。当分配线程独占的小对象时,优先分配到TLAB,因为在堆上分配对象需要同步操作,而这一块空间没有线程共享不存在锁开销,并且这些不共享的小对象一般朝生夕死很容易gc。

由于TLAB空间一般不会很大,因此大对象无法在TLAB上进行分配,总是会直接分配在堆上。TLAB空间由于比较小,因此很容易装满。比如,一个100K的空间,已经使用了80KB,当需要再分配一个30KB的对象时,肯定就无能为力了。这时虚拟机会有两种选择,第一,废弃当前TLAB,这样就会浪费20KB空间;第二,将这30KB的对象直接分配在堆上,保留当前的TLAB,这样可以希望将来有小于20KB的对象分配请求可以直接使用这块空间。实际上虚拟机内部会维护一个叫作refill_waste的值,当请求对象大于refill_waste时,会选择在堆中分配,若小于该值,则会废弃当前TLAB,新建TLAB来分配对象。这个阈值可以使用TLABRefillWasteFraction来调整,它表示TLAB中允许产生这种浪费的比例。默认值为64,即表示使用约为1/64的TLAB空间作为refill_waste。**默认情况下,TLAB和refill_waste都会在运行时不断调整的,使系统的运行状态达到最优。**如果想要禁用自动调整TLAB的大小,可以使用-XX:-ResizeTLAB禁用ResizeTLAB,并使用-XX:TLABSize手工指定一个TLAB的大小。
-XX:+PrintTLAB可以跟踪TLAB的使用情况。一般不建议手工修改TLAB相关参数,推荐使用虚拟机默认行为。


3.垃圾收集器

3.1判断对象已死和类卸载

(1)可达性分析
  1. 从GC Roots出发,有引用链到达的对象是存活的对象,反之标记为死亡对象。被标记的对象在GC阶段会被垃圾收集器回收,在回收之前还会经历一次再标记,指的是被标记对象是否重写了Object类的finalize()方法,如果重写了那么会去调用这个方法,重写finalize()方法可以重新将对象加入到引用链中,不被回收。但是这种“复活”只能一次即finalize()方法只会被调一次
  2. GC Roots有哪些
    1. 虚拟机栈中引用的对象
    2. 本地方法栈中引用的对象
    3. 方法区中引用的静态对象
    4. 方法区中引用的常量对象
    5. synchronized锁对象
    6. 记录当前被加载类的SystemDictionary
    7. 记录当前字符串常量的StringTable
    8. 跨代引用的对象
(2)4种引用

前提:一个对象可能存在多种引用,假设强、软、弱三者都有,那么以强引用回收条件为准

  1. 强引用:不回收
  2. 软引用:内存不足才回收
  3. 弱引用:发现即回收
  4. 虚引用:为对象设置虚引用来接收它被回收时的系统通知
(3)回收方法区

方法区要在FullGC时才会清理,允许进行类型信息的卸载。判断一个类是否能被卸载要同时满足3个条件

  1. 该类所有实例已经被回收,即java堆中已经没有这个类的实例
  2. 该类对应的java.lang.Class对象没有被任何地方引用
  3. 该类的加载器已经被回收,但这种情况很难满足,除非是大量使用反射、动态代理这类自定义类加载器的框架

3.2垃圾收集算法

(1)分代收集理论
  1. 三种假说

    • 弱分代假说——绝大多数对象都是朝生夕灭

    • 强分代假说——熬过多次垃圾收集过程的对象就越难以消亡

    • 跨代引用假说——存在两个对象分别处于不同分代,它们之间存在引用链

      • 解决:Remember Set记忆集。在新生代划分一小块空间来记录跨代引用的老年代对象,如果断开了引用,GC时就不用去老年代找了
  2. 不同收集概念

    • 部分收集
      • Minor GC:新生代的垃圾收集。当新生代空间不足时发生
      • Major GC:老年代的垃圾收集。当老年代空间不足时发生,目前仅CMS垃圾收集器支持
      • Mixed GC:整个新生代+部分老年代的垃圾收集
    • 整堆收集
      • Full GC:收集堆+方法区。当老年代空间不足时或者方法区空间不足时发生
(2)标记-清除算法(目前仅CMS收集器有用到)
  1. 根据可达性分析标记出需要清理的对象,然后遍历内存空间将死亡对象清理掉

  2. 缺点:

    • 如果有大量对象需要清理,遍历会导致执行效率低

    • 会产生内存碎片

image-20220308165921075
(3)标记-复制算法(新生代)
  1. 将内存分为两块区域,一块进行内存分配,一块闲置,当要进行垃圾回收时将未标记的存活对象从from区移动到闲置区域(to区)并按顺序排列整齐不产生内存碎片。值得一提的是,新生代比例划分默认为Eden:Survivor0:Survivors1=8:1:1。新生代和老年代的比例划分默认为Young:Old=1:2
  2. 缺点:大量对象移动时需要空间分配担保,因为有可能Survivor区放不下
image-20220308165816205
(4)标记-整理算法(老年代)
  1. 和标记-清除相比,多了一步整理的操作,这样内存空间不会产生内存碎片
  2. 缺点:整理操作会造成“Stop the world”,会暂停用户线程
image-20220308165858976

3.3垃圾收集算法详解

  1. 根节点枚举:和整理内存碎片一样,在列举GC Roots时,会造成STW。但是现在有的垃圾收集器可以做到并发枚举,不STW

  2. 安全点:用户线程运行到安全点时,才能暂停用户线程(STW)进行GC。安全点的设定标准为需要长时间执行的指令比如:调用方法,循环跳转,异常跳转

  3. 安全区域:有些线程可能处于sleep阻塞或者wait挂起状态,这时它们走不到安全点,但是在某一段代码中对象引用没有发生变化,因此在这一段区域中暂停也是安全的,称为安全区域

  4. 记忆集与卡表:记忆集用于记录非回收区域指向回收区域的跨代引用对象地址。例如老年代对象指向新生代对象的跨代引用地址。对于G1,ZGC这种垃圾收集器,会有部分区域的收集,也会用到记忆集。卡表是记忆集的一种实现方式,表现为一个字节数组,记录一个或多个对象的地址(称为卡页),当卡页内的对象存在跨代引用时,就会把卡页对应得卡表数组元素标记为1,认为是Dirty,否则为0

    image-20220310093113793
  5. 写屏障:卡表标记为Dirty的实现。

3.4垃圾收集器

前言:任何垃圾收集器GC时都会造成STW

(1)Serial+Serial Old(客户端模式)
  1. 新生代:Serials收集器,标记-复制算法
  2. 老年代:Serial Old收集器,标记-整理算法

串行垃圾收集器组合,只有一个GC线程在工作。在GC时要暂停所有用户线程,适用于客户端模式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W99Rj1zy-1686273963085)(C:\Users\10059\AppData\Roaming\Typora\typora-user-images\image-20220310102216658.png)]

(2)ParNew+Serial Old
  1. 新生代:ParNew收集器,标记-复制算法
  2. 老年代:Serial Old收集器,标记-整理算法

ParNew是Serial收集器得改良版,从单GC线程变为多GC线程并行回收

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kFa9Kfrc-1686273963086)(C:\Users\10059\AppData\Roaming\Typora\typora-user-images\image-20220310103853509.png)]

(3)Parallel Scavenge+Parallel Old(服务端模式)
  1. 新生代:Parallel Scavenge收集器,标记-复制算法
  2. 老年代:ParallelOld收集器,标记-压缩算法

Parallel Scavenge收集器是一款注重吞吐量的收集器,相对于客户端频繁与用户交互需要低延迟而言,高吞吐量能够高效利用处理器资源,适用于服务端。

  1. 高吞吐量

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Mzxh4DhI-1686273963088)(C:\Users\10059\AppData\Roaming\Typora\typora-user-images\image-20220310112818257.png)]

  2. 低延迟时间

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tIFxSyK0-1686273963088)(C:\Users\10059\AppData\Roaming\Typora\typora-user-images\image-20220310112939113.png)]

Parallel Old收集器是Serial Old收集器的改良版,从单线程GC变为多线程GC线程并行回收。下图为新生代和老年代工作示意图(新老一样的)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QbFe5wsl-1686273963089)(C:\Users\10059\AppData\Roaming\Typora\typora-user-images\image-20220310111449052.png)]

(4)CMS
  1. 新生代+老年代:并发标记-清除算法

追求低延迟的垃圾回收器,现在已经逐渐弃用。新生代和老年代的回收过程为:初始标记、并发标记、重新标记、并发清除。其中并发标记和并发清除用时较长,初始标记和重新标记时间短但是必须暂停用户线程。下图为新生代和老年代工作示意图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b548U5jX-1686273963090)(C:\Users\10059\AppData\Roaming\Typora\typora-user-images\image-20220310144517141.png)]

(5)G1(MixedGC,服务端模式)
  1. 分代收集算法
  1. 摒弃之前的分代收集概念,将堆分为一块块区域(Region)。有Eden区,Survivors区,Old区,Humongous区

  2. 每一块区域的大小取值范围为1 MB - 32 MB,当某一个大对象的大小超过Region大小的一半时,就要分配到Humongous区(本质其实是一块连续的区域)。

  3. 每一块区域都有维护一个记忆集,用于记录其它区域指向本区域的指针

  4. 垃圾回收时根据各个Region的回收价值来进行回收:初始标记(只标记GC Roots能直接关联到的对象)、并发标记、重新标记、筛选回收

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r1W03i1q-1686273963091)(C:\Users\10059\AppData\Roaming\Typora\typora-user-images\image-20220310145733271.png)]


4.垃圾收集相关参数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WjWumEir-1686273963091)(C:\Users\10059\AppData\Roaming\Typora\typora-user-images\image-20220314103106894.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SjJt3TtY-1686273963092)(C:\Users\10059\AppData\Roaming\Typora\typora-user-images\image-20220314103139710.png)]
个Region的回收价值来进行回收:初始标记(只标记GC Roots能直接关联到的对象)、并发标记、重新标记、筛选回收

[外链图片转存中…(img-r1W03i1q-1686273963091)]


4.垃圾收集相关参数

[外链图片转存中…(img-WjWumEir-1686273963091)]

[外链图片转存中…(img-SjJt3TtY-1686273963092)]

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值