Java自动内存管理概述

Java所支持的自动内存管理针对的是对象内存的自动分配和回收,原因如下:

  • 1.在Java的内存区域中,本地方法栈、虚拟机栈、程序计数器这三块内存区域的分配和回收具有确定性,他们在编译阶段就能确定需要分配的空间大小。此外,这些内存区域属于线程私有,随线程生而生,随线程灭而灭。综上,虚拟机不需要在这部分内存区域花费太多精力用于垃圾回收。
  • 2.方法区存储的是类信息、静态变量、常量、即时编译器编译过的代码,这部分数据的回收条件较为苛刻,垃圾回收的“成绩”并不是那么令人满意,因此不是垃圾收集器需要重点关注的区域。
  • 3.Java堆存储所有线程的对象,这些对象内存空间的分配是在程序运行期间才进行的,因此具有不确定性。此外,对象的生命周期长短不一,为了提高垃圾收集的效率,需要针对不同生命周期的对象设置不同的垃圾收集算法,这也就增加了内存管理的复杂度。

对象优先在Eden分配

●对象主要分配在新生代的Eden区上,如果启动了本地线程分配缓冲(TLAB),对象将被优先分配在TLAB上,少部分情况下会直接在老年代中分配空间。

●大多数情况下,新生对象优先分配在Eden区上,当Eden区满时,虚拟机将发起一次Minor GC。经过Minor GC后存活下来的对象将被复制到一块Survivor区中,当Survivor区满时,将启动分配担保策略转移到老年代中。

大对象直接进入老年代

所谓大对象是指需要分配大量连续空间的对象,比如数组。这类对象若是分配在新生代中,一是可能导致新生代还有不少内存就提前触发一次Minor GC用于存储大对象,另外就是新生代中采用复制算法,大对象的复制代价较高。为了解决上述问题,虚拟机提供了一个参数-XX:PretenureSizeThreshold,作用是让大于这个参数值的对象直接分配在老年代。

注意:PretenureSizeThreshold只对Serial、ParNew收集器有效,Parallel Scavenge收集器不认识这个参数。

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

虚拟机为每个对象定义了一个年龄计数器,若对象在Eden区出生并经过一次Minor GC后仍然存活,那么该对象将被复制到Survivor区中,且对象年龄加1,此后每发生一次GC,对象存活下来后年龄都将加1,当该对象年龄达到一定值(默认15),则将进入老年代。对象晋升老年代的年龄阈值,可通过参数-XX:MaxTenuringThreshold设置。

动态对象年龄判定

虚拟机并不是强制要求对象年龄必须达到年龄阈值才可进入老年代,当Survivor区中年龄一样的对象大小总和占整个区大小空间一半以上时,大于或等于该年龄的对象将今日老年代。

分配担保

●在发生Minor GC之前,虚拟机会检测老年代中是否有一块连续空间能够容纳新生代中所有的对象,若有,则这次Minor GC是安全的;若无,进行下一步。

●虚拟机继续判断老年代最大可用的连续空间是否大于历次晋升老年代的对象大小的平均值,若是,则冒险进行一次Minor GC;若否,则进行一次Full GC,使老年代腾出更多的空间为新生代做担保。