JVM内存分配与回收简述

本文是《深入理解Java虚拟机》一书3.6节的学习笔记,因此有些地方直接摘抄书中段落。在此仅描述内存分配与回收的一般过程,不深入特殊情况。理解有误的地方欢迎指正。

在开始之前先介绍相关概念,在《深》书第二章中介绍了Java运行时数据区的划分情况。运行时数据区被划分为5个部分:Java堆、方法区、Java虚拟机栈、本地方法栈、程序计数器。其中Java堆就是专门用来存放对象实例的区域,更具体的,Java堆被划分为新生代与老年代两个区域。新生代又被分为三块,一块较大的Eden区和两块较小的Survivor区,使用复制(Coping)算法进行垃圾收集。


ScreenClip.png


当一个对象被创建,将在Java堆中为其分配内存,一般是Java堆中新生代的Eden区中。


当Eden区没有足够的内存进行分配时,将发起一次Minor GC,以期在Eden区中腾出足够的可供分配的内存空间。不过进行Minor GC存在一定的问题。


按照复制算法,Minor GC后,会将Eden区和From Survivor区中存活下来的对象复制到 To Survivor区中,然后清空Eden区和From Survivor区。那么有可能存活下来的对象的总大小大于To Survivor区的大小,此时To Survivor区是装不下的。怎么办呢?最简单的想法就是将这些装不下的对象直接放到老年代中去。那么要确保老年代中有足够的连续内存空间。因此在Minor GC之前,JVM会去检查老年代,查看其最大可用连续内存空间的大小是否大于新生代中对象的总空间大小。如果大于,则这次Minor GC可以确保是安全的(因为就算这些对象全都存活下来,老年代中也装得下),接下来可以进行Minor GC。


如果小于,虚拟机会先去查看HandlePromotionFailure设置值是否允许担保失败。如果不允许担保失败,那么进行Full GC,让老年代腾出足够空间。如果允许担保失败,那么JVM将会基于老年代的历史经验进行判断,即检查老年代最大可用连续内存空间是否大于历次晋升到老年代的对象的平均大小。若大于,则进行Minor GC,即使是有风险的(实际需要晋升到老年代的对象大小大于老年代最大可用连续内存空间,依据历史经验判断失败)。若小于,那么进行Full GC。


前面讲了新生代的内存分配与回收,接下来介绍老年代。前面已经介绍了一种对象由新生代晋升老年代的方式,下面还有几种。

  • 当对象够大的时候(虚拟机提供了-XX:PretenureSizeThreshold参数,令大于这个设置值的对象直接在老年代中分配),为了避免在Eden区和Survivor区之间发生大量的内存复制,该对象直接在老年代中分配内存空间。

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

  • 如果在Survivor空间中相同年龄所有对象的总大小大于Survivor空间的一半,年龄大于等于该年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄。

原文地址:https://www.cnblogs.com/read-the-spring-and-autumn-annals-in-night/p/12041952.html