【java虚拟机】之java虚拟机划分
一、java虚拟机划分脑图
-XX:NewRatio=1 |
表示新生代占
|
-XX:SurvivorRatio=8 |
表示Eden:Survivor=8:1 |
-Xms20m | 表示堆空间初始大小为 20 M |
-Xmx20m | 表示堆空间最大大小为 20 M |
-Xmn10m | 表示新生代大小为 10M |
-XX:+UseParNewGC | 表示年轻代使用serial的多线程版本,它是server模式下首选的新生代收集器,因为能与cms配合(我们生产也是这么配的) |
-XX:+UseConcMarkSweepGC |
表示老年代用cms收集器(我们生产也是这么配置的)
|
-XX:+PrintGC |
简单打印gc日志 |
-XX:+PrintGCDetails | 详细打印gc日志 |
二、对象的创建
1、对象生成的步骤
(1)new关键字+类构造器
(2)根据类名,去常量池查询类是否被加载,如加载则从堆上找一块空闲内存,生成对象。
(3)不存在,则进行类加载(同步),一个类只会被加载一次,生成类对象,存放至方法区。然后在堆上找一块空闲内存,生成对象。
2、对象分配内存的方式(取决于堆内存是否规整,堆内存是否规整取决于垃圾收集器,其收集器是否存在内存压缩功能)
(1)指针碰撞:空闲内存和使用内存整齐分布,中间有一个指针,需要生成对象,将指针向空闲内存移动一段距离,足够分配对象。
(2)空闲列表:空闲内存和使用内存交错分布,jvm维护内存的列表指针,当需要分配对象时,从列表的指针里找一块足够对象生成的内存进行生成对象。
3、对象分配比较频繁,如果对内存做同步。
(1)一种方式:cas操作,通过循环重试,进行对象分配。
(2)一种方式:每个线程预先分配一个块内存,称为本地线程分配缓冲,线程产生的对象,就在该区域内存进行分配,无需进行同步操作。只有当本地线程缓冲使用完,会进行同步,再次分配一块本地线程缓冲,进行对象分配。用-XX:+/-UseTLAB
三、垃圾收集器
(1)那些对象可以做GCRoot
==>虚拟机栈(栈帧中的本地变量表)中引用的对象。
==>方法区中静态属性引用的对象
==>方法区中常量引用的对象
==>本地方法栈中JNI(一般说的是native方法)引用的对象。
==>Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象(比如NullPointExcepiton、OutOfMemoryError)等,还有系统类加载器。
==>所有被同步锁(synchronized关键字)持有的对象。
==>反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等
(2)类卸载(被回收)的前提条件
==》该类所有的实例都已经被回收,也就是java堆中不存在该类的任何实例
==》加载该类的classLoader已经被回收
==》该类对应的java.lang.class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
虚拟机给每个对象定义了一个对象年龄(Age)计数器。如果对象在Eden出生并经过第一次Minor GC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor区,并将对象年龄设为 1。对象在Survivor区中每熬过一次Minor GC,年龄就增加1,当它的年龄增加到一定程度(默认为15)时,就会被晋升到老年代中。对象晋升老年代的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold 来设置。
(3)虚拟机垃圾收集器
强弱分代假说
- 弱分代假说(Weak Generational Hypothesis):绝大多数对象都是朝生夕灭的。
- 强分代假说(Strong Generational Hypothesis):熬过越多次垃圾收集过程的对象就越难以消亡。
- 跨代引用假说(Intergenerational Reference Hypothesis):跨代引用相对于同代引用来说仅占极少数。
垃圾回收类型
-
Minor GC/Young GC:只局限于新生代区域内的收集
-
Major GC/Old GC:目标只是老年代的垃圾收集。目前只有CMS收集器会有单独收集老年代的行为。另外请注意“Major GC”这个说法现在有点混淆,在不同资料上常有不同所指,读者需按上下文区分到底是指老年代的收集还是整堆收集。
-
Full GC:收集整个Java堆和方法区的垃圾收集
-
Mixed GC:指目标是收集整个新生代以及部分老年代的垃圾收集。目前只有G1收集器会有这种行为。
算法实现细节
【STEP1】根节点枚举(停顿):寻找GC-ROOT , Stop The World必须发生。(保证一致性)即使是号称停顿时间可控,或者(几乎)不会发生停顿的CMS、G1、ZGC等收集器,枚举根节点时也是必须要停顿的。
【STEP2】存活对象标记(GC线程和用户线程可并发):可达性分析,三色标记法进行存活对象标记(寻找GC-ROOT标记为黑色,从GC-ROOT开始遍历)
- 黑色:标识存活的对象。表示对象已经被垃圾收集器访问过,且这个对象的所有引用都已经扫描过。黑色的对象代表已经扫描过,它是安全存活的,如果有其他对象引用指向了黑色对象,无须重新扫描一遍。黑色对象不可能直接(不经过灰色对象)指向某个白色对象。
- 灰色:标识扫描过程为彻底完成标记的对象。表示对象已经被垃圾收集器访问过,但这个对象上至少存在一个引用还没有被扫描过。
- 白色:表示对象尚未被垃圾收集器访问过。显然在可达性分析刚刚开始的阶段,所有的对象都是白色的,若在分析结束的阶段,仍然是白色的对象,即代表不可达。
解决并发标记过程中,对象引用关系发生变化的方案:
- 增量更新(Incremental Update)【被GC收集器采用】:当黑色对象插入新的指向白色对象的引用关系时,就将这个新插入的引用记录下来,等并发扫描结束之后,再将这些记录过的引用关系中的黑色对象为根,重新扫描一次。这可以简化理解为,黑色对象一旦新插入了指向白色对象的引用之后,它就变回灰色对象了。
- 原始快照(Snapshot At The Beginning,SATB):当灰色对象要删除指向白色对象的引用关系时,就将这个要删除的引用记录下来,在并发扫描结束之后,再将这些记录过的引用关系中的灰色对象为根,重新扫描一次。这也可以简化理解为,无论引用关系删除与否,都会按照刚刚开始扫描那一刻的对象图快照来进行搜索。
垃圾收集器:
Serial收集器(单线程收集器【标记清除法+标记复制法-新生代】)
ParNew收集器(并行收集器【标记清除法+标记复制法-新生代】)
-XX:SurvivorRatio
-XX:PretenureSizeThreshold :线程数
-XX:HandlePromotionFailure
-XX:+UseConcMarkSweepGC 开启CMS收集器
-XX:+/-UseParNewGC 开启和禁用ParNew收集器
Parallel Scavenge收集器(追求吞吐量的垃圾收集器,并行收集器【标记清除法+标记复制法-新生代】)
-XX:MaxGCPauseMillis:每次GC的最长时间
-XX:GCTimeRatio : GC时间占比(1-吞吐量)
Serial Old收集器(【标记整理法-老年代】)
Parallel Old收集器(【标记整理法-老年代】)
CMS收集器(追求停顿时间,并发垃圾收集器【标记清除法-老年代】)
-XX:CMSInitiatingOccupancyFraction:当老年代内存占用达到多少时,触发。百分比。
过程:
1)初始标记(CMS initial mark) => 标记GC-root(Stop the world)
2)并发标记(CMS concurrent mark) => 用户线程和GC线程一起运行,GC线程基于GC-root基于三色法标记垃圾对象
3)重新标记(CMS remark)=>修正并发标记的内容(Stop the world)
4)并发清除(CMS concurrent sweep)
Garbage First收集器G1 (年轻代region标记复制算法,老年代并发标记+整理算法(无分代概念,但多个region可以在逻辑上组成年轻代和老年代))
JDK 9及以上版本的HotSpot虚拟机使用参数-XX:+UseConcMarkSweepGC来开启CMS收集器的话,用户会收到一个警告信息,提示CMS未来将会被废弃
G1不再坚持固定大小以及固定数量的分代区域划分,而是把连续的Java堆划分为多个大小相等的独立区域(Region),每一个Region都可以根据需要,扮演新生代的Eden空间、Survivor空间,或者老年代空间。收集器能够对扮演不同角色的Region采用不同的策略去处理,
Region中还有一类特殊的Humongous区域,专门用来存储大对象。G1认为只要大小超过了一个Region容量一半的对象即可判定为大对象。每个Region的大小可以通过参数-XX:G1HeapRegionSize设定,取值范围为1MB~32MB,且应为2的N次幂。
虽然G1仍然保留新生代和老年代的概念,但新生代和老年代不再是固定的了,它们都是一系列区域(不需要连续)的动态集合。G1收集器之所以能建立可预测的停顿时间模型,是因为它将Region作为单次回收的最小单元,即每次收集到的内存空间都是Region大小的整数倍,这样可以有计划地避免在整个Java堆中进行全区域的垃圾收集。更具体的处理思路是让G1收集器去跟踪各个Region里面的垃圾堆积的“价值”大小,价值即回收所获得的空间大小以及回收所需时间的经验值,然后在后台维护一个优先级列表,每次根据用户设定允许的收集停顿时间(使用参数-XX:MaxGCPauseMillis指定,默认值是200毫秒),优先处理回收价值收益最大的那些Region,这也就是“Garbage First”名字的由来。这种使用Region划分内存空间,以及具有优先级的区域回收方式,保证了G1收集器在有限的时间内获取尽可能高的收集效率。
-XX:G1HeapRegionSize: 每个region的大小
-XX:MaxGCPauseMillis:每次GC,允许最大的停顿时间长度(单位毫秒)
-XX:G1HeapRegionSize : 设置固定区域存储大对象的阈值。
过程:
- 初始标记(Initial Marking)【stop the world】:仅仅只是标记一下GC Roots能直接关联到的对象。
- 并发标记(Concurrent Marking):从GC Root开始对堆中对象进行可达性分析,递归扫描整个堆里的对象图,找出要回收的对象
- 最终标记(Final Marking)【stop the world】:对用户线程做另一个短暂的暂停,用于处理并发阶段结束后仍遗留下来的最后那少量的SATB记录。
- 筛选回收(Live Data Counting and Evacuation)【stop the world】:负责更新Region的统计数据,对各个Region的回收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个Region构成回收集,然后把决定回收的那一部分Region的存活对象复制到空的Region中,再清理掉整个旧Region的全部空间。
(3)内存分配和回收策略
对象分配规则以及对象如何从年轻代晋升至老年代
1、优先在Eden区进行内存分配。(少数情况下,会直接在老年代分配内存,分配规则不是百分百固定的,其细节取决使用哪一种垃圾收集器和jvm中与内存相关的参数)
2、当Eden区没有足够的内存时,会触发一次Minor GC(Yong GC)。
3、当设置了-XX:PretenureSizeThreshold=xx(单位为K)(对象大于xxk时,直接在老年代分配内存)
4、当设置了-XX:MaxTenuringThreshold=m (单位为次数)(对象经历Minor GC的次数大于等于该值,对象从年轻代迁移至老年代)
5、当Survivor空间中相同年龄大小的对象之和>Survivor空间一半大小(from或者to空间),则大于或等于该年龄的对象,从年轻代迁移至老年代。
6、(Minor GC会转换成FullGC)在进行Minor GC前,会进行老年代连续空间大小检查。
【1】jdk1.6之前的规则
==>如果老年代连续空间>年轻代对象之和,则正常进行Minor GC
==>如果老年代连续空间<年轻代对象之和,并且允许内存担保失败(-XX:HandlePromotionFailure=true)>>>并且老年代连续空间>历届晋升老年代对象之和的平均数,则正常进行Minor GC.
>>>老年代连续空间<历届晋升老年代对象之和的平均数,则老年代要先进行一次FullGC (腾出更多空间),再进行Minor GC,让年轻代对象可以平稳晋升到老年代。
==>如果老年代连续空间<年轻代对象之和,并且允许内存担保失败(-XX:HandlePromotionFailure=false):老年代要先进行一次FullGC (腾出更多空间),再进行Minor GC,让年轻代对象可以平稳晋升到老年代。
【2】jdk1.6之后的规则
==>在进行Minor GC前,检查老年代连续空间。如果(连续空间>年轻代对象之和 或者 连续空间>历届晋升老年代对象之和的平均数)其中一个成立,则进行Minor GC
==>在进行Minor GC前,检查老年代连续空间。如果(连续空间>年轻代对象之和 或者 连续空间>历届晋升老年代对象之和的平均数)两个都不成立,则老年代要先进行一次FullGC (腾出更多空间),再进行Minor GC,让年轻代对象可以平稳晋升到老年代。
对象分配规则以及对象如何从年轻代晋升至老年代(新总结)
【1】新对象创建的规则
1、eden内存充足:优先在eden区分配内存。
2、eden区内存不足时:触发yongGC,采用复制算法,将eden区存活的对象和from或to中使用中的存活对象汇总,copy至to或from中。清理无用对象腾出空间,存活对象年龄+1;
3、eden区内存不足时:触发完yongGC后,依然无充足内存,eden区的对象也无法复制到from或to中,则直接将eden区中的对象,从年轻代迁移至老年代。
4、大对象规则,如果设置了对象大小大于某个阀值(-XX:PretenureSizeThreshold=xxK)时,大于阀值的对象,直接在old区分配内存。
【2】对象从年轻代晋升至老年代的规则
1、在发生yongGC时,from或to区中的对象年龄值>晋升阀值(-XX:MaxTenuringThreshold=m),该类对象从年轻代迁移至老年代。
2、在发生yongGC时,from或to区中的相同年龄的对象之和的大小>from或to区的一半大小时,大于或等于该年龄的一类对象,从年轻代迁移至老年代。
3、在发生yongGC前,检查老年代的连续空间大小>年轻代eden和(from或to)中对象之和 或者 老年代的连续空间大小>历届晋升对象之和的平均数,正常触发yonggc。伴随对象迁移。
4、在发生yongGC前,检查老年代的连续空间大小<=年轻代eden和(from或to)中对象之和 并且 老年代的连续空间大小<=历届晋升对象之和的平均数,先触发fullGC对老年代回收,再触发yongGC对年轻代回收。
四、汇总
五、G1垃圾收集器
参考:https://www.cnblogs.com/ASPNET2008/p/6496481.html