(四)JVM的内存分配和回收策略

Java技术体系中自动内存管理可以归结为自动化地解决两个问题:

  • 给对象分配内存
  • 回收分配给对象的内存

Minor Gc和Full GC 有什么不同呢?

  • 新生代GC(Minor GC):指发生新生代的的垃圾收集动作,Minor GC非常频繁,回收速度一般也比较快。
  • 老年代GC(Major GC/Full GC):指发生在老年代的GC,出现了Major GC经常会伴随至少一次的Minor GC(并非绝对),Major GC的速度一般会比Minor GC的慢10倍以上。
一、对象的内存分配

对象的内存分配,很大程度是在堆上分配(也有可能经过JIT编译后被拆散为标量类型并间接地栈上分配),对象主要分配在新生代的Eden区,如果启动了本地线程分配缓冲,将按线程优先在TLAB上分配。少数情况下也可能是直接分配在老年代中,分配的规则并不是百分百固定,取决于当前使用的是哪一种垃圾回收器组合,还有虚拟机中与内存相关的参数的设置。

(1)对象优先在Eden分配

大多数情况下,对象在新生代Eden区中分配。当Eden区没有足够的空间进行分配时,虚拟机将会发起一次Minor GC。

(2)大对象直接进入老年代

大对象指的是需要大量连续内存空间的Java对象(例如很长的字符串和数组)。经常出现大对象容易导致内存还有不少空间就提前触发垃圾收集以获取足够的连续空间来“安置”它们。虚拟机提供了一个-XX:PretenureSizeThreshold参数,令大于这个设置值的对象直接在老年代分配。这样可以避免在Eden区及两个Survivor区之间发生大量的内存复制。

(3)长期存活对象进入老年代

虚拟机给每个对象定义了一个对象年龄(Age)计数器。如果对象在Eden出生并经过第一次Minor GC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor空间中,并且对象年龄设为1。对象在Survivor区中每“熬过”一次Minor GC,年龄就增加1岁,当他的年龄增加到一定程度(默认为15岁),就会被晋升到老年代中。对象晋升老年代的年龄阈值,可以通过参数-XX:MaxTenuringThreshold设置。

为了更好地适应不同程序的内存状况,虚拟机并不是永远地要求对象的年龄必须达到了MaxTenuringThreshold才能晋升老年代,如果Survivor空间中相同年龄所有对象大小的总和大于survivor空间的一半,年龄大于或者等于该年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄。

二、面试题
(1)说一下堆内存中对象的分配的基本策略:

堆空间的基本结构:
在这里插入图片描述
上图所示的 eden区、s0区、s1区都属于新生代,tentired 区属于老年代。大部分情况,对象都会首先在 Eden 区域分配,在一次新生代垃圾回收后,如果对象还存活,则会进入 s0 或者 s1,并且对象的年龄还会加 1(Eden区 —> Survivor 区后对象的初始年龄变为1),当它的年龄增加到一定程度(默认为15岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold来设置。

另外,大对象和长期存活的对象会直接进入老年代。
在这里插入图片描述

(2)对象的访问定位有哪两种方式?

建立对象就是为了使用对象,我们的Java程序通过栈上的 reference 数据来操作堆上的具体对象。对象的访问方式有虚拟机实现而定,目前主流的访问方式有两种:

  • 使用句柄
  • 直接指针

句柄: 如果使用句柄的话,那么Java堆中将会划分出一块内存来作为句柄池,reference 中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息;
在这里插入图片描述
直接指针: 如果使用直接指针访问,那么 Java 堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,而reference 中存储的直接就是对象的地址。
在这里插入图片描述

本文来源:

《深入理解Java虚拟机(第2版)》 周志明 著
JavaGuide面试突击版

猜你喜欢

转载自blog.csdn.net/qq_42908549/article/details/107444984