jvm技术专题「原理专题」深入剖析java对象内存分配及跨代引用分析(代码片段)

洛神灬殇 洛神灬殇     2023-03-26     397

关键词:

系列文章目录

本系列文章主要针对于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是一样的逻辑。

并发清除:

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

查看详情

jvm技术专题深入研究jvm内存逃逸原理分析「研究篇」(代码片段)

前提概要JVM的内存分配主要在是运行时数据区(RuntimeDataAreas),而运行时数据区又分为了:方法区,堆区,PC寄存器,Java虚拟机栈(就是栈区,官方文档还是叫Java虚拟机栈),本地方法区,内存逃逸主... 查看详情

jvm技术专题深入分析内存布局及gc原理分析「下卷」(代码片段)

...中卷的针对于GC虚拟机相关的文章了,详细可见【JVM技术专题】深入分析内存布局及GC原理分析「上卷」)和【JVM技术专题】深入分析内存布局及GC原理分析「中卷」,目前我相信已经会有相关的对GC的原理和虚拟机的... 查看详情

jvm技术专题深入分析内存布局及gc原理分析「上卷」(代码片段)

前提概要JVM虚拟机的整体的结构分布图,我个人觉得比较难掌握和理解的问题主要集中在“GC回收(内存管理)”和“内存布局”,这两部分属于真正的核心部分,至于执行引擎,可以理解为X86的寄存器执行方式或者... 查看详情

jvm技术专题「原理专题」深入分析java中finalize方法的作用和底层原理(代码片段)

finalize方法是什么finalize方法是Object的protected方法,Object的子类们可以覆盖该方法以实现资源清理工作,GC在首次回收对象之前调用该方法。finalize方法与C++的析构函数的区别finalize方法与C++中的析构函数不是对... 查看详情

jvm技术专题深入挖掘java对象的内存结构「原理篇」(代码片段)

📕每日一句善于利用时间的人,总会拥有充分的时间。📕基本概念在JVM虚拟机种Java对象的内存结构如图所示分为三大块:对象头(ObjectHeader)、实例数据(InstanceData)、对齐填充(Padding)... 查看详情

jvm技术专题深入分析内存布局及gc原理分析「中卷」(代码片段)

...没有看到上篇直接进入下篇,希望可以先看一下【JVM技术专题】深入分析内存布局及GC原理分析「上卷」),接下来我们会侧重点去讲解GC回收机制的运作流程以及回收期(暂时不包含最新的ZGC),小 查看详情

jvm技术专题「原理专题」全流程分析java对象的创建过程及内存布局(代码片段)

前言概要对应过程则是:对象创建、对象内存布局、对象访问定位的三个过程。对象的创建过程对象的创建方式java中对象的创建方式有很多种,常见的是通过new关键字和反射这两种方式来创建。除此之外,还有clone、... 查看详情

jvm技术专题java各种类型对象占用内存情况分析「上篇」(代码片段)

前言只有当你到了一定层次,需要了解JVM内部运行机制,或者高并发多线程下,你写的代码对内存有影响,你想做性能优化。当你想深入了解java对象在内存中,如何存储,或者每个对象占用多大空间时。... 查看详情

jvm技术专题深入学习jit编译器实现机制「原理篇」(代码片段)

前提概要解释器Java程序最初是通过解释器(Interpreter)进行解释执行的,当虚拟机发现某个方法或代码块的运行特别频繁的时候,就会把这些代码认定为“热点代码”(hotspotcode)。正因为如此,我们的hotspot的虚... 查看详情

jvm技术专题深入分析cg管理和原理查缺补漏「番外篇」(代码片段)

...用场景进行总结。自Sun发布Java语言以来,开始使用GC技术来进行内存自动管理,避免了手动管理带来的悬挂指针(DanglingPointer)问题,很大程度上提升了开发效率,从此GC技术也一举成名。GC有着非常悠久... 查看详情

jvm技术专题java各种类型对象占用内存情况分析「下篇」(代码片段)

前提回顾建议大家从【JVM技术专题】Java各种类型对象占用内存情况分析「上篇」开始学习比较好,这样子会有一个承接和过度。根据前面的学习的内存占用计算规则,可以计算出一个对象在内存中的占用空间大小情况... 查看详情

jvm技术专题全流程化分析java对象的创建过程「原理篇」(代码片段)

前言概要对应过程则是:对象创建、对象内存布局、对象访问定位的三个过程。对象的创建过程对象的创建方式java中对象的创建方式有很多种,常见的是通过new关键字和反射这两种方式来创建。除此之外,还有clone、... 查看详情

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

finalize方法是什么finalize方法是Object的protected方法,Object的子类们可以覆盖该方法以实现资源清理工作,GC在首次回收对象之前调用该方法。finalize方法与C++的析构函数的区别finalize方法与C++中的析构函数不是对应的,C++中的析构... 查看详情

jvm技术专题深入分析字节码指令重排序技术「原理篇」(代码片段)

前提概要指令重排序有两类,编译器重排序和处理器重排序。(至于内存系统指令重排较为复杂不是本章重点)重排序分为两类:编译期重排序和运行期重排序,分别对应编译时和运行时环境。编译器重排序... 查看详情

jvm技术专题深入理解g1垃圾收集器的原理和运行机制「原理分析篇」(代码片段)

本文首先简单介绍了垃圾收集的常见方式,然后再分析了G1收集器的收集原理,相比其他垃圾收集器的优势,最后给出了一些调优实践。什么是垃圾回收首先,在了解G1之前,我们需要清楚的知道,垃圾回... 查看详情

精华推荐|jvm技术专题深入学习jit编译器实现机制「核心剖析篇」(代码片段)

前提概要我们都知道开发语言整体分为两类,一类是编译型语言,一类是解释型语言。那么你知道二者有何区别吗?编译器和解释器又有什么区别?这是为了兼顾启动效率和运行效率两个方面。Java程序最初是通过... 查看详情

jvm技术专题深入研究jmm的实现原理之happens-before原则和as-if-serial语义「入门篇」(代码片段)

前提概要Happens-Before是JMM最核心的概念,所以在了解happens-before原则之前,首先需要了解java的内存模型。JMM内存模型Java内存模型是共享内存的并发模型,线程之间主要通过读-写共享变量来完成隐式通信。Java中的共享... 查看详情