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
- 逃逸分析(JDK8默认启用):指一个对象是否逃逸出了方法或者线程,如果没有则进行栈上分配、锁消除、标量替换
- 全局逃逸:指在方法或者线程中,一个对象引用传递给了类变量/成员变量、或者对象引用存储在了一个已逃逸的对象中、或者对象引用作为方法返回出去了
- 参数级逃逸:指一个对象引用作为参数传递到了另一个方法中
- 栈上分配:如果对象没有逃逸出方法或者线程,将对象直接分配到栈内存
- 锁消除:逃逸分析可以判断出某个对象是否始终只被一个线程访问,如果只被一个线程访问,那么对该对象的同步操作就可以转化成没有同步保护的操作
- 标量替换:将聚合量替换为标量
- 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)可达性分析
- 从GC Roots出发,有引用链到达的对象是存活的对象,反之标记为死亡对象。被标记的对象在GC阶段会被垃圾收集器回收,在回收之前还会经历一次再标记,指的是被标记对象是否重写了Object类的finalize()方法,如果重写了那么会去调用这个方法,重写finalize()方法可以重新将对象加入到引用链中,不被回收。但是这种“复活”只能一次即finalize()方法只会被调一次
- GC Roots有哪些
- 虚拟机栈中引用的对象
- 本地方法栈中引用的对象
- 方法区中引用的静态对象
- 方法区中引用的常量对象
- synchronized锁对象
- 记录当前被加载类的SystemDictionary
- 记录当前字符串常量的StringTable
- 跨代引用的对象
(2)4种引用
前提:一个对象可能存在多种引用,假设强、软、弱三者都有,那么以强引用回收条件为准
- 强引用:不回收
- 软引用:内存不足才回收
- 弱引用:发现即回收
- 虚引用:为对象设置虚引用来接收它被回收时的系统通知
(3)回收方法区
方法区要在FullGC时才会清理,允许进行类型信息的卸载。判断一个类是否能被卸载要同时满足3个条件
- 该类所有实例已经被回收,即java堆中已经没有这个类的实例
- 该类对应的java.lang.Class对象没有被任何地方引用
- 该类的加载器已经被回收,但这种情况很难满足,除非是大量使用反射、动态代理这类自定义类加载器的框架
3.2垃圾收集算法
(1)分代收集理论
-
三种假说
-
弱分代假说——绝大多数对象都是朝生夕灭
-
强分代假说——熬过多次垃圾收集过程的对象就越难以消亡
-
跨代引用假说——存在两个对象分别处于不同分代,它们之间存在引用链
- 解决:Remember Set记忆集。在新生代划分一小块空间来记录跨代引用的老年代对象,如果断开了引用,GC时就不用去老年代找了
-
-
不同收集概念
- 部分收集
- Minor GC:新生代的垃圾收集。当新生代空间不足时发生
- Major GC:老年代的垃圾收集。当老年代空间不足时发生,目前仅CMS垃圾收集器支持
- Mixed GC:整个新生代+部分老年代的垃圾收集
- 整堆收集
- Full GC:收集堆+方法区。当老年代空间不足时或者方法区空间不足时发生
- 部分收集
(2)标记-清除算法(目前仅CMS收集器有用到)
-
根据可达性分析标记出需要清理的对象,然后遍历内存空间将死亡对象清理掉
-
缺点:
-
如果有大量对象需要清理,遍历会导致执行效率低
-
会产生内存碎片
-
(3)标记-复制算法(新生代)
- 将内存分为两块区域,一块进行内存分配,一块闲置,当要进行垃圾回收时将未标记的存活对象从from区移动到闲置区域(to区)并按顺序排列整齐不产生内存碎片。值得一提的是,新生代比例划分默认为Eden:Survivor0:Survivors1=8:1:1。新生代和老年代的比例划分默认为Young:Old=1:2
- 缺点:大量对象移动时需要空间分配担保,因为有可能Survivor区放不下
(4)标记-整理算法(老年代)
- 和标记-清除相比,多了一步整理的操作,这样内存空间不会产生内存碎片
- 缺点:整理操作会造成“Stop the world”,会暂停用户线程
3.3垃圾收集算法详解
-
根节点枚举:和整理内存碎片一样,在列举GC Roots时,会造成STW。但是现在有的垃圾收集器可以做到并发枚举,不STW
-
安全点:用户线程运行到安全点时,才能暂停用户线程(STW)进行GC。安全点的设定标准为需要长时间执行的指令比如:调用方法,循环跳转,异常跳转
-
安全区域:有些线程可能处于sleep阻塞或者wait挂起状态,这时它们走不到安全点,但是在某一段代码中对象引用没有发生变化,因此在这一段区域中暂停也是安全的,称为安全区域
-
记忆集与卡表:记忆集用于记录非回收区域指向回收区域的跨代引用对象地址。例如老年代对象指向新生代对象的跨代引用地址。对于G1,ZGC这种垃圾收集器,会有部分区域的收集,也会用到记忆集。卡表是记忆集的一种实现方式,表现为一个字节数组,记录一个或多个对象的地址(称为卡页),当卡页内的对象存在跨代引用时,就会把卡页对应得卡表数组元素标记为1,认为是Dirty,否则为0
-
写屏障:卡表标记为Dirty的实现。
3.4垃圾收集器
前言:任何垃圾收集器GC时都会造成STW
(1)Serial+Serial Old(客户端模式)
- 新生代:Serials收集器,标记-复制算法
- 老年代:Serial Old收集器,标记-整理算法
串行垃圾收集器组合,只有一个GC线程在工作。在GC时要暂停所有用户线程,适用于客户端模式
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W99Rj1zy-1686273963085)(C:\Users\10059\AppData\Roaming\Typora\typora-user-images\image-20220310102216658.png)]
(2)ParNew+Serial Old
- 新生代:ParNew收集器,标记-复制算法
- 老年代: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(服务端模式)
- 新生代:Parallel Scavenge收集器,标记-复制算法
- 老年代:ParallelOld收集器,标记-压缩算法
Parallel Scavenge收集器是一款注重吞吐量的收集器,相对于客户端频繁与用户交互需要低延迟而言,高吞吐量能够高效利用处理器资源,适用于服务端。
高吞吐量
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Mzxh4DhI-1686273963088)(C:\Users\10059\AppData\Roaming\Typora\typora-user-images\image-20220310112818257.png)]
低延迟时间
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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
- 新生代+老年代:并发标记-清除算法
追求低延迟的垃圾回收器,现在已经逐渐弃用。新生代和老年代的回收过程为:初始标记、并发标记、重新标记、并发清除。其中并发标记和并发清除用时较长,初始标记和重新标记时间短但是必须暂停用户线程。下图为新生代和老年代工作示意图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b548U5jX-1686273963090)(C:\Users\10059\AppData\Roaming\Typora\typora-user-images\image-20220310144517141.png)]
(5)G1(MixedGC,服务端模式)
- 分代收集算法
摒弃之前的分代收集概念,将堆分为一块块区域(Region)。有Eden区,Survivors区,Old区,Humongous区
每一块区域的大小取值范围为1 MB - 32 MB,当某一个大对象的大小超过Region大小的一半时,就要分配到Humongous区(本质其实是一块连续的区域)。
每一块区域都有维护一个记忆集,用于记录其它区域指向本区域的指针
垃圾回收时根据各个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)]