JVM技术专题「原理专题」深入剖析Java对象内存分配及跨代引用分析

Posted 洛神灬殇

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JVM技术专题「原理专题」深入剖析Java对象内存分配及跨代引用分析相关的知识,希望对你有一定的参考价值。

系列文章目录

本系列文章主要针对于JVM调优指南体系的文章介绍


提示:该系列文章之间都是属于相互独立的,读者可自选方式进行学习阅读。

文章目录


「每日一句」

此时成功并不意味着彼时成功,此地成功并不意味着在彼地成功,有时候就连这一次的成功也会成为下次失败的原因。


「提示介绍」

本文主要介绍的内容为:Java对象内存的分配规则和针对于JVM内存布局之间不同区域的对象引用关系(跨代引用很容易属于大家的盲点)

1、对象分配规则

本章内容主要介绍,相关Java对象分配内存的规则以及顺序,首先我们先从JVM的内存布局和结构进行认识了解

1.1、堆内存结构

总体划分为:年轻代、老年代两大部分(方法区)

1.2、分配策略

1.2.1、JVM内存宏观分配策略

  1. 对象优先分配在Eden区:

    • 如果Eden区没有足够的空间时,虚拟机执行一次Minor GC(Young GC)
  2. 大对象直接进入老年代(大对象是指需要大量连续内存空间的对象)。

    • 这样做的目的是避免在Eden区和两个Survivor区之间发生大量的内存拷贝(新生代采用复制算法收集内存)。
  3. 长期存活的对象进入老年代。

    • 虚拟机为每个对象定义了一个年龄计数器,如果对象经过了1次Minor GC那么对象会进入Survivor区,之后每经过一次Minor GC那么对象的年龄加1,知道达到阀值对象进入老年区。

-XX:MaxTenuringThreshold用来定义年龄的阈值

  1. 动态判断对象的年龄

    • 如果Survivor区中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代
  2. 空间分配担保。
    在发生Minor GC(Yong GC)之前,JVM会计算Survivor区移至老年区的对象的平均大小,虚拟机会检查老年代最大可用的连续空间是否大于需要转移的对象大小。

    • 如果大于,则此次Minor GC(Young GC)是安全的。

    • 如果小于,jdk1.6之前:则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败。

    • 如果HandlePromotionFailure=true,那么会继续检查老年代最大可用连续空间是否大于历次晋升到老年代的对象的平均大小。

    • 如果大于,则尝试进行一次Minor GC(Young GC),但这次Minor GC(Young GC)依然是有风险的,失败后会重新发起一次Major GC(Full GC);

    • 如果小于或者HandlePromotionFailure=false,则改为直接进行一次Major GC(Full GC)

jdk1.6 update 24后-XX:-HandlePromotionFailure 不起作用了,只要老年代的连续空间大于新生代对象的总大小或者历次晋升到老年代的对象的平均大小就进行Monitor GC,否则FullGC。

jdk1.8下,HandlePromotionFailure会报错,Unrecongnized VM option

Java对象分配到内存的顺序流程图:

常见GC

Minor GC/Young GC:新生代GC,指发生在新生代的垃圾收集动作,因为java对象大多都具备朝生夕死的特性,所以Minor GC非常频繁,一般回收速度也比较快。

Old GC/Major GC:收集整个Old gen的GC,只有CMS模式这么称呼。MajorGC的速度一般比Minor GC慢10倍以上。

Full GC:收集整个堆,包括Young gen、Old gen、Perm gen(如果存在的话)等所有部分的模式。

Mixed GC:收集整个young gen以及部分old gen的GC。只有G1模式这么称呼。

2、跨代引用问题

2.1、什么是跨代引用?

红色的线表示由虚拟机栈中发出的引用。显然B—>A、E—>F都是跨代引用。

2.2、跨代引用对MonitorGC的影响

JVM GC 判断对象是否可以回收使用可达性分析的方法,可达性分析首先需要找到 GC Roots 对象

常规GC-Root

  1. 虚拟机栈(栈帧中的局部变量表)中引用的对象【Stack Local】。
  2. 本地方法栈(native方法)引用的对象。
  3. 方法区中类静态属性、静态方法引用的对象。
  4. 方法区中常量引用的对象。
  5. synchronized中的monitor对象以及class实例对象

MonitorGC个性GC-Root:

  1. RememberSet(HashSet)数据结构(CardTable是具体实现类似数组)

CardTable:

  • 采用空间换时间,不需要扫描整个Heap空间,降低MonitorGC耗时。

  • 跨代引用带来的问题,采用CardTable很好的规避了遍历整个老年代的问题。

  • HotSpot JVM的卡页(Card Page)大小为512字节,卡表(Card Table)被实现为一个简单的字节数组,即卡表的每个标记项为1个字节

CardTable标记着是否存在老年代引用新生代

如上图发现:B、C所在的Card Page在CardTable中被标记上了。

MonitorGC的GC-Root中包括CardTable,如何提高MonitorGC效率?

  • 就需要降低跨代引用的对象,尽量设置稍大些的From、To区域,尽量将对象消灭在Young Gen区域。

  • 同时也表示对象不是越早进入老年代越好(老年代对象引用新生代对象就是一个很好的说明)。

2.4、CMS三阶段标记

初始标记:

CMS里有两个需要STW的阶段:initial mark,remark、这两个标记有什么不一样么?使用的GC-Root一样么?
答案:不一样。

  • 初始标记:使用的是常规的GC-Root(虚拟机栈栈帧中的局部变量表、本地方法栈native方法、方法区中类的静态属性和方法、方法区中常量等引用的对象)

  • 重新标记:常规的GC-Root。(只有MonitorGC才会使用跟可达分析)新生代所有对象。遍历老年代的DirtyCard(ModUnionTable)

MonitorGC可没有遍历整个老年代,而是采用CardTable通过时间换空间的做法,CMS-OldGC为什么采用遍历新生代所有的对象呢?

这就是YoungGen 与 OldGen存在明显的差异:

  • 老年代对象都是经历过多次GC回收存活下来了,对象存在的变数极低。所以采用空间换时间效果较好。
  • 新生代对象属于变化特别快的区域,如果采用空间换时间,既浪费了空间,也没有提升性能。

并发标记:

通过:-XX:+CMSScavengeBeforeRemark ,触发MonitorGC降低的就是YoungGen区的对象,从而达到标记源头减少,降低remark时间。

并发预清理:

阶段1

阶段2

CMS 跨代引用是图一中的B—>A 有何危害,虚拟机是如何避开的?

  • 答案:CardTable。

  • 逻辑:采用创建CardTable空间区域,来避免扫整个老年代。当B无引用的时候,cardTable会被标记,一次MonitorGC就会回收掉。B是如何做到没引用的,同E—>F是一样的逻辑。

并发清除:

以上是关于JVM技术专题「原理专题」深入剖析Java对象内存分配及跨代引用分析的主要内容,如果未能解决你的问题,请参考以下文章

JVM技术专题「源码专题」深入剖析JVM的Mutex锁的运行原理及源码实现(底层原理-防面试)

JVM技术专题深入研究JVM内存逃逸原理分析「研究篇」

Java技术专题-JVM研究系列(24)深入挖掘Java对象的内存结构

JVM技术专题深入分析内存布局及GC原理分析「上卷」

JVM技术专题「原理专题」全流程分析Java对象的创建过程及内存布局

JVM技术专题「原理专题」深入分析Java中finalize方法的作用和底层原理