二、对象在JVM堆中的运动轨迹介绍

128 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第5天,点击查看活动详情

二、对象在JVM堆中的运动轨迹介绍

  1. 下图为对象分配简单示意图,描述java对象的主要活动区域

image-20220603145937421.png

  1. 对象的分配------运动轨迹介绍

    👇一般普通对象情况描述;

    • 对于一般新生对象而言,一般会在年轻代的Eden区上进行内存分配,在eden内存足够的情况下直接分配,但Eden区域内存撑满没有足够的内存在为新到来的对象分配时,此时会触发一次MinorGC

    • 大多是情况,Eden区域的对像会在这次GC中被回收掉,剩余少部分还在被引用着的对象进入survivor区(S区);

    • s区默认大概占到整个年轻代的2/10左右,里面的内存布局时两块大小一致的内存块,分别是s1和s0,但是在jvm运行期间实际上在同一时间只有一块内存用来被存放从上次MinorGC中存活下来的对象;

    • MinorGC到来时,Eden区域存活着的对象会被转移到S区的一块;

    • S区存放着对象的那块内存满了的情况下,在GC清理时使用标记复制的方式对此时存放对象的那块区域进行清理;

    • 复制清除:两块大小一致的内存块,把一块存活的对象复制另一块去,然后把原来使用的内存块清理掉,这种回收效率比较高,就是浪费内存,毕竟每次只能用其中的人一块;

    • 在s区存活下来的的对象会升级到老年代;

      上面是一般情况下对象的运动轨迹;

    👇长期存活着的对象终将进入老年代

    • 上文中说到对于一般的对象首先会经过Eden区,在经过几轮的MinorGC在s区来回倒腾,最终还被引用着将会进入老年代;
    • s区的两块内存,不断重复的复制清除操作是可以配置次数的,这个倒腾的次数,一般称之为对象的分带年龄,通过 -XX:MaxTenuringThreshold=15 参数。可以指定对像在年轻代的最大年龄,每经历一次MinorGC,对象的分带年龄就会+1;直至超过设置的值,直接进入老年代;

    👇关于大对象直接进入老年代

    • 在开发过程中,难免因为各种因素会创造出一些比较大的对像,像这种对象在被创建的时候,需要在堆中需要一块非常大的连续的内存空间为他分配;
    • 这样可能就会导致在Eden经过一轮GC之后,到达S区,S区的空间相对较小,如果一个大对象直接丢过来,那么势必有可能导致S区存放不下许多从Eden来的对象,而导致频繁的GC,严重影响程序的性能,况且大对象在两个S区之间来回的复制,也是很大胆额开销;
    • 因此,对于这种大对象。与其让他在年轻代熬过自己的分带年龄,好不如直接把他分配到GC活动更加稳定的老年代;
    • -XX:PretenureSizeThreshold 可以用来配置大对象的上限大小,只是这个参数只对Serial和ParNew两款新生代收集器有效;

    👇对象的动态对象年龄判断机制

    • 上述的对象的达到分代年龄后进入老年代也不是一成不变的,HotSpot虚拟机会根据内存运行时的状况,来判断哪些对像可以年进入老年代,以此来平衡整个内存区域;
    • 先通过-XX:TargetSurvivorRatio 指定一个百分比的值 a,当S区的一部分对象所占目前正在使用的这块S区的百分之a,那么在s区内大于等于这部分对象中的分带年龄最大的那个对象的,都会被升级到老年代;

    👇空间分配担保机制

    • 一般而言对象在到达老年代之前要先经过MinorGC,但是这个时候老年代剩余可用空间的大小是否能支撑MinorGC后的对象所占的空间是个问题,如果不够则会发生FULLgc;
    • 因此,在MinorGC之前就需要计算老年代剩余可用空间,由于是MinorGC之前,因此就引入了老年代空间分配担保机制;
    • 开启 XX:HandlePromotionFailure参数,代表允许jvm空间分配担保,否则一旦年轻代的对象占用内存大小大于老年代剩余空间则触发FullGC,如果还不够则OOM
    • 开启了老年代空间担保,当年轻代的对像大小大于老年代剩余的空间,首先会去查找历史每次Minorgc后进入老年代的对象平均大小,小于老年代剩余大小则直接进入老年代;
    • 如果说大于老年代剩余空间,则会在老年代进行一次FULLGC,然后再去年轻代MinorGC,进入老年代;