2、垃圾收集器与内存分配策略

对象已死

1、引用计数算法

在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值加一;当引用失效的时候,计数器值减一;任何时刻计数器为零的对象就是不可能再被使用的。

问题:互相引用时无法回收

2、可达性分析算法

GC Roots 的跟对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程走过的路径称为“引用链”(Reference Chain)若某个对象到GC Roots 没有任何引用链项链,换句话说就是从GC Roots 到这个对象不可达时,证明此对象是不可能再被使用的。

GC Roots:

1、在虚拟机栈中引用的对象;(如:各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等)

2、在方法区中 类静态属性、常量 引用的对象;

3、在本地方法栈中引用的对象;

4、Java虚拟机内部的引用(如:基本数据类型对应的Class对象、一些常驻的异常对象、还有系统类加载器等)

5、所有被同步锁持有的对象(sychronized 关键字)

6、反映Java虚拟机内部情况的 JMXBean、JVMTI中注册的回调、本地代码缓存等

3、再谈引用

强引用(Strongly Reference):Object obj = new Object() ;只要强引用的关系还存在,垃圾收集器就永远不会回收掉被引用的对象;

软引用(Soft Reference):描述 有用但非必须的对象。在系统将要发生内存溢出异常前,会把这些对象列进回收范围内进行第二次回收,如果这次回收还没有足够的内存,才会抛出内存溢出异常;

弱引用(Weak Reference):描述非必须的对象。只能生存到下一次啊垃圾收集发生为止。

虚引用(Phantom Reference):幽灵引用/幻影引用 为一个对象设置虚引用的目的是为了能够在这个对象被会收时收到一个系统通知

4、生存还是死亡

5、回收方法区

垃圾收集算法

引用式垃圾收集算法:直接垃圾收集

追踪式垃圾收集:间接垃圾收集

1、分代收集理论(Generational Collection)

分代假说:

1、弱分代假说:绝大多数对象都是朝生夕灭的

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

对象不是孤立的,对象之间存在跨代引用

垃圾收集名词

新生代收集:Minor GC/Young GC

老年代收集:Major GC/Old GC

混合收集:Mixed GC==》目标是收集整个新生代以及部分老年代的垃圾收集==》G1收集器

整堆收集:Full GC

2、标记 - 清除算法 Mark-Sweep

首先标记出所有需要回收的对象,然后统一回收掉所有被标记的对象

标记过程:判断对象是否属于垃圾的过程

缺点:1、执行效率不稳定;2、内存空间碎片化问题

3、标记 - 复制算法

也被称为复制算法

缺点:将可用内存缩小为原来的一般,空间浪费

4、标记 - 整理算法

移动式的回收算法

移动式则内存回收时更复杂,非移动式则内存分配时更复杂。从垃圾收集停顿时间来看,不移动对象停顿时间更短,甚至可以不需要停顿。但是从整个程序的吞吐量看,移动对象才划算。

HotSpot的算法细节实现

1、根节点枚举

必须暂停用户线程==》STW Stop The Word

2、安全点 Safepoint

在特定的位置记录了 OopMap,这些位置被称为安全点

安全点位置特征:是否具有让程序长时间执行的特征(方法调用、循环跳转、异常跳转等指令序列的复用)

3、安全区域 Safe Region

指能够确保在某一段代码片段中,引用关系不会发生变化,因此,在这个安全区域中任意地方开始垃圾收集都是安全的。

4、记忆集与卡表

记忆集(Remembered Set):是一种用于记录从非收集区域指向收集区域的指针集合的抽象数据结构。

卡表(Card Table):卡表是记忆集的一种具体实现,定义了记忆集的记录精度与堆内存的映射关系等。

一个卡页的内存中通常包含不止一个对象,只要卡页内有一个或更多的对象的字段存在着跨代指针,那就将对应卡表的数组元素的值标识为1,称这个元素变脏(Dirty)

垃圾收集时,只要筛选出卡表中变脏的元素,就能轻易得出哪些卡表也内存中包含跨代指针,把他们加入GC Roots中一并扫描

5、写屏障

通过写屏障技术维护卡表,AOP切面,有写前屏障和写后屏障

6、并发的可达性分析

理论前提:该算法的全过程都需要基于一个能保障一致性的快照中才能够分析

参考博客:

https://blog.csdn.net/qq_34687559/article/details/105795213

https://www.pianshen.com/article/76122042015/

https://zhuanlan.zhihu.com/p/108706654

三色标记

并发标记可能产生“对象消失”的问题的原因:

1、添加新引用:赋值器插入了一条或多条从黑色对象到白色对象的新引用;

2、删除旧引用:赋值器删除了全部从灰色对象到白色对象的直接或间接引用

注意:当前仅当以上两个条件同时满足是,会产生“对象消失”的问题

解决思路:破坏以上两个条件的任意一个

解决方法:

1、增量更新(Incremental Update),破坏的是第一个条件,当添加新引用时,将新插入的引用记录下来,等并发扫描结束后,再将记录过的引用关系中的黑色对象为根,重新扫描一次;

2、原始快照(Snapshot At The Begining,SATB):破坏的是第二个条件,当删除旧引用时,就将要删除的引用记录下来,在并发扫描结束后,再将这些记录过的引用关系中的灰色对象为根,重新扫描一次。即,无论引用关系删除与否,都会按照刚刚开始扫描那一刻的对象图快照来进行搜索。

经典垃圾收集器

关键词解释:

并行:Parallel==》描述多条垃圾收集器线程之间的关系,同一时间有多条这样的线程在协同工作,默认用户线程等待

并发:Concurrent=》描述垃圾收集器线程与用户线程之间的关系,说明同一时间垃圾收集器和用户线程都在运行

吞吐量:处理器用于运行用户代码的时间与处理器总消耗时间的比值。即:

吞吐量=运行用户代码时间 / (运行用户代码时间+运行垃圾收集时间)

1、Serial 收集器

单线程串行、标记-复制算法、客户端模式

2、ParNew 收集器

多线程并行、标记-复制算法

3、Parallel Scavenge 收集器

多线程并行、标记-复制算法、关注吞吐量、自适应调节策略

-XX:MaxGCPauseMillis:最大垃圾收集停顿时间,大于0的毫秒数

-XX:GCTimeRatio:吞吐量值,大于0小于100的证书

-XX:+UseAdaptiveSizePolicy:垃圾收集自适应调节

4、Serial Old 收集器

单线程、标记-整理算法、客户端模式

5、Parallel Old 收集器

多线程并行、标记-整理算法

6、CMS 收集器 Concurrent Mark Sweep

并行并发、标记-清除算法、并发低停顿收集器、

目的:获取最短回收停顿时间的收集器

收集过程:

1、初始标记----STW

标记 GC Roots 能直接关联到的对象,速度很快

2、并发标记--增量更新

标记从 GC Roots 的直接关联对象开始遍历整个对象图的过程,耗时较长

3、重新标记----STW

为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象,停顿时间稍微长一点点

4、并发清除

缺点:

1、并发阶段占用部分线程导致程序变慢,降低总吞吐量。默认启动回收线程数量=(处理器核心数量+3)/ 4‘

2、无法处理“浮动垃圾”,有可能出现“Concurrent Mode Failure”失败而导致另一次完全“Stop The Word”的Full GC产生;

浮动垃圾:并发清除阶段出现的新垃圾

-XX:CMSInitianatingOccupancyFration:设置老年代使用多少空间后触发垃圾收集

设置太高容易导致CMS运行期间预留内存无法满足程序分配新对象的需要,就会出现一次“并发失败”,导致虚拟机冻结用户线程的执行,临时启用Serial Old 收集器来重新进行收集

3、空间碎片的产生

7、Garbage First 收集器

Region之间:标记-复制 ;整体来看:标记-清除

面向服务端应用的垃圾收集器

“停顿时间模型”收集器:在指定停顿时间内完成垃圾回收

参考博客:https://blog.csdn.net/qq_34687559/article/details/105979988

面向堆内存任何部分来组成回收集进行回收,衡量标准:哪块内存中存放的垃圾数量最多,回收收益最大。Mixed GC 模式

G1收集器根据回收所获得的空间大小以及回收所需时间的经验值在后台维护一个“优先级列表”,每次根据用户所设定的收集停顿时间优先处理回收价值收益最大的那些Region,这就是“Garbage First”名字由来;

使用Region划分内存空间,具有优先级的区域回收方式,保证了G1收集器在悠闲地时间内尽可能的获取最高的收集效率。

区域:Region

存储大对象:Humongous Region

收集步骤:

1、初始标记:STW-标记GC Roots 能直接关联到的对象

2、并发标记:并发---原始快照SATB

3、最终标记:STW

4、筛选回收

-XX:G1HeapRegionSize:设置每个Region的大小,取值范围为1MB~32MB,应为2的N次幂。

跨代引用问题:

记忆集:记录外部指向本Region对象的所有引用,每个Region都维护一个记忆集,通过记忆集便可以找到谁引用了本Region的对象

是一个抽象的概念

卡表:是记忆集的“实现类”

每个Region被划分为多个卡,当卡对应区域有引用本Region对象,就将其标记为脏卡

总之,G1中的记忆集实际上是一个HashTable,key是Region的起始地址,value是字节数组,字节数组的下标代表该Region中的卡门,当该Region中该卡中的对象引用本Region中的对象,则在字节数组中进行标记。

收集集合

大型对象

原始快照

触发Full GC:

1、拷贝存活对象时,无法找到可用的空闲分区;

2、为创建的大型对象分配空间时找不到足够的连续分区。

 

低延迟垃圾收集器

衡量垃圾收集器的三项重要指标:内存占用(Footprint)、吞吐量(Throughput)和延迟(Latency)

随着硬件性能的增长,对“延迟”的关注越来越多

1、Shenandoah 收集器

shenandoah继承了G1的堆内存划分,内存划分为一个个大小相等的Region,也有存放大对象的Humongous区域,但是不遵循分代理论即不再有Young GC,只剩下Mixed GC;

跨代引用:放弃了记忆集卡表,采用连接矩阵数据结构来解决;

工作过程:

1、初始标记:STW

2、并发标记:并发

3、最终标记:1、处理剩余的SATB(原始快照)扫描;2、统计出回收价值最高的Region==》STW

4、并发清理:清理回收无存活对象的Region;

5、并发回收:把回收集里存活对象先复制一份到其他未被使用的Region中。

问题:

1、复制完成后,用户线程仍然可能不停地对被移动对象进行读写访问,但是移动之后整个内存中所有指向该对象的引用还是旧对象的地址

解决思想:类似于句柄访问

转发指针“Brooks Pointer”:存放在每一个对象头前面

解决:在每个对象头前面添加新的引用字段,该引用字段指向对象,对于未移动的对象,引用指向自己,经过移动的对象,旧对象的 Brooks Pointer则指向新对象,此时访问对象流程:先找到旧对象的 Brooks Pointer,再通过旧对象的 Brooks Pointer找到新对象的 Brooks Pointer,再通过新对象的 Brooks Pointer找到新对象

2、并发写入---线程安全

垃圾收集线程复制了新的对象;用户线程更新了对象;垃圾收集线程将旧对象的 Brooks Pointer指向新对象

以上,用户的更新操作落在了旧对象,而新对象并未被操作,从而出现安全问题

解决:通过比较并交换(Compare And Swap,CAS)操作来保证并发誓对象的访问正确性的。

6、初始引用更新:设定一个线程集合点,确保并发回收阶段所有的收集线程都已完成它们的对象移动任务,STW;

7、并发引用更新:开始进行引用更新,将堆中所有指向旧对象的引用修正到复制后的新地址

8、最终引用更新:修正GC Roots中的引用===》STW

9、并发清理:将回收集中的Region回收

2、ZGC 收集器 Z Garbage Collector

ZGC收集器是一款基于 Region 内存布局的,暂时不设分代的、使用了读屏障、染色指针和内存多重映射等技术来实现可并发的标记-整理算的,以低延迟为首要目标的一款垃圾收集器

Region具有动态性:动态创建、动态销毁以及动态的区域容量大小

小型Region:Small Region:容量固定为2MB,存放小于256KB的对象

中型Region:Medium Region:容量固定为32MB, 256KB < 存放对象 < 4MB

大型Region:Large Region:容量不固定,可动态变化,必须是2MB的整数倍。存放大于4MB的大对象,一个大型Region只会存放一个大对象

ZGC运作过程:

1、并发标记

2、并发预备重分配

3、并发重分配

4、并发重映射

并发回收阶段问题解决

染色指针(Colored Pointer)+转发表

染色指针:当进行GC时,不需要访问对象具体情况,只要知道对象的引用关系。在引用更新之前,用户要访问新的对象,也不需要访问旧对象。用引用指针来存储引用信息、标记信息。

将可用的46位拿出高4位来表示信息:

第一个:Finalizable,表示对象是否被判断为死亡,只能通过 finalize() 方法才能访问并拯救它;

第二个:Remapped,表示对象是否被移动过。即在垃圾回收过程中,本对象存活,将本对象复制到空白的Region中,但引用尚未更新。此时要将Remapped打上标记,说明我已经被复制过了,我是旧对象,别访问我,访问新对象去吧!

第三个和第四个:Marked1和Marked0,用于并发可达性分析的三色标记的。

转发表:当发现对象被复制过了,就不访问旧对象,而是去查转发表,在转发表中找到新对象的内存地址,从而去访问新对象,同时,将引用更新。

选择合适的垃圾收集器

1、Epsilon 收集器

无操作的收集器、自动内存管理子系统

垃圾收集器除了垃圾收集,还要负责堆的管理与布局、对象分配、与解释器的协作、与编译器的协作、与监控子系统的协作等

2、收集器的权衡

选择合适垃圾收集器关注点:

1、应用程序的主要关注点是什么

2、运行应用程序的基础设施如何?

3、使用JDK的发行商是什么

3、虚拟机及垃圾收集器日志

1、查看GC基本信息,JDK9之前:-XX:+PrintGC,JDK9之后:-Xlog:gc

2、查看gc详细信息,JDK9之前:-XX:+PrintGCDetails,JDK9之后: -Xlog:gc*

3、查看GC前后的堆、方法区可用容量变化。 JDK9之前:-XX:+PrintHeapAtGC, JDK9之后:-Xlog:gc+heap=debug

4、查看GC过程中用户线程并发时间以及停顿的时间:

JDK9之前:-XX:+PrintGCApplicationConcurrentTime以及 -XX:+PrintGCApplicationStoppedTime

JDK9之后:-Xlog:safepoint

5、查看收集器Ergonomics机制(自动设置对空间个分代区域大小、收集目标等内容)自动调节的相关信息

JDK9之前:-XX:PrintAdaptiveSizePolicy JDK9之后:-Xlog:gc+ergo*=trace

6、查看熬过收集后剩余对象的年龄分布信息:

JDK9之前:-XX:+PrintTenuringDistribution, JDK9之后: -Xlog:gc+age=trace

4、垃圾收集器参数总结

实战:内存分配与回收策略

自动内存管理,最根本的是解决两个问题:1、自动给对象分配内存;2、自动回收分配给对象的内存

1、对象优先在Eden分配

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

-XX:PretenureSizeThreshod==>指定大于该设置值的对象直接在老年代分配内存===》只对Serial和ParNew两款新生代收集器管用

3、长期存活的对象进入老年代

-XX:MaxTenuringThreshod:设置晋升老年代的阈值,默认为15

4、动态对象年龄判定

如果在Survivor空间中低于或等于某年龄的所有对象大小总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代

5、空间分配担保

-XX:+-HandlePromotionFailure:是否允许担保失败

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值