Java堆内存分配机制


在Java的内存区域中,程序计数器,虚拟机栈,本地方法栈3个区域随线程而生,随线程而灭;栈中的栈帧随着方法的进入和退出执行入栈和出栈。因此这几个内存区域的内存分配和划分都具有确定性。

而Java堆不一样。由于其不确定性,JVM关注的内存分配与回收重点都在这。

Java堆内存的分配整体可以概述为“自适应的,分代的,停止-复制,标记-清除”式的垃圾回收器。

分代指Java将堆内存划分为年轻代(Young Generation),年老代(Old Generation),永久代(Permannet Generation)三块大区域。

在了解这三块区域之前我们先了解一下“停止-复制,标记-清除”两种清理方法。

  1. 停止-复制

它依据的思想是,对任何“存活”的对象,一定能追溯到其存活在堆栈或者静态存储区的引用,因此从堆栈和静态存储区来时遍历所有引用,就能找到“活的”对象。

在进行清理时,程序将会被暂停,也就是说它不是后台清理的。

此方式会将所有活的对象复制到新堆,没有被复制的全部都是垃圾。当对象被复制到新堆时,他们是一个接一个的,所以新堆保持紧凑排列。

  1. 标记-清除

这种清理方式的思想同样是从堆栈和静态存储区开始,便利所有引用,找出所有活的对象。只是当它找到活的对象时,他会给对象设一个标记,标记过程中不会发生任何清理动作。

当标记工作完成后才会开始清理工作。由于只是清理没有发生任何复制动作,所以清理完成后,剩下的堆空间时不连续的。此时所有存活的对象向内存一端移动,以确保紧凑连续。

在了解了这两种清理方式后,我们来看看堆内存中三大区域是如何分工协作的。


  • 年轻代

对象在被创建时,内存分配首先发生在年轻代(有一部分大对象可以直接被创建在年老代)。大部分对象在被创建后很快不再被使用。于是会被年轻代的GC机制清理掉,叫做 Minor GC 或者 Young GC。

年轻代还可以划分为三个区域:Eden区,Survivor0,Survivor1.

Eden区:伊甸园,很形象的命名。对象在年轻代中会被首先创建到Eden区。

年轻代使用“停止-复制”清理方法,具体步骤如下:

  1. 对象被创建在Eden区。
  2. 当Eden区满时,则触发一次 Minor GC。会清理消亡的对象,并将存活的对象复制到 Survivor0 区。(此时 Survivor1区是空的)
  3. 在Eden区执行几次GC后,Survivor0 区也会变满。此时对Survivor0 区进行GC清理动作,然后将Eden区本次未消亡的对象和 Survivor0 区存活的对象一起复制到 Survivor1 区。(此后一段时间内 Survivor0区是空的)
  4. 当两个 Survivor 区相互切换固定次数后(HotSpot虚拟机默认是15次,可手动更改)。将仍然存活的对象复制到年老代。此时仍存货的对象很少,比如我们自定义的类。

  • 年老代

在年老代满了的时候,会触发GC机制。被称为 Major GC 或 Full GC。年老代使用的GC方式为“标记-清理”。

  1. 当创建较大的对象时,如长字符串,大数组。Young空间不足,则会直接分配至年老代。大对象会提前出发GC动作。所以应尽少使用。更应该避免使用短命的大对象。
  2. 会存在年老代引用新生代对象的情况。此时如果 Young GC时,可能要查询年老代,已确定是否可以回收,这十分低效。所以在年老代中会维护一个 512byte 的“ card table ”,存储多有对年轻代的引用记录。只需要查询此处即可。

  • 永久代

永久代经常只会发生两种清理动作:

  1. 常量池中的常量。此时只需要确定没有该常量存活的引用即可。
  2. 无用的类信息,此时需要确定以下三点,才可以回收。
       (1.):类的所有实例已经被回收;
       (2.):类的ClassLoader被回收;
       (3.):没有通过反射引用该类的地方;
Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐