Java堆内存分配机制
Java堆内存分配机制在Java的内存区域中,程序计数器,虚拟机栈,本地方法栈3个区域随线程而生,随线程而灭;栈中的栈帧随着方法的进入和退出执行入栈和出栈。因此这几个内存区域的内存分配和划分都具有确定性。而Java堆不一样。由于其不确定性,JVM关注的内存分配与回收重点都在这。Java堆内存的分配整体可以概述为“自适应的,分代的,停止-复制,标记-清除”式的垃圾回收器。分代指Java将堆内存划分为
Java堆内存分配机制
在Java的内存区域中,程序计数器,虚拟机栈,本地方法栈3个区域随线程而生,随线程而灭;栈中的栈帧随着方法的进入和退出执行入栈和出栈。因此这几个内存区域的内存分配和划分都具有确定性。
而Java堆不一样。由于其不确定性,JVM关注的内存分配与回收重点都在这。
Java堆内存的分配整体可以概述为“自适应的,分代的,停止-复制,标记-清除”式的垃圾回收器。
分代指Java将堆内存划分为年轻代(Young Generation),年老代(Old Generation),永久代(Permannet Generation)三块大区域。
在了解这三块区域之前我们先了解一下“停止-复制,标记-清除”两种清理方法。
- 停止-复制
它依据的思想是,对任何“存活”的对象,一定能追溯到其存活在堆栈或者静态存储区的引用,因此从堆栈和静态存储区来时遍历所有引用,就能找到“活的”对象。
在进行清理时,程序将会被暂停,也就是说它不是后台清理的。
此方式会将所有活的对象复制到新堆,没有被复制的全部都是垃圾。当对象被复制到新堆时,他们是一个接一个的,所以新堆保持紧凑排列。
- 标记-清除
这种清理方式的思想同样是从堆栈和静态存储区开始,便利所有引用,找出所有活的对象。只是当它找到活的对象时,他会给对象设一个标记,标记过程中不会发生任何清理动作。
当标记工作完成后才会开始清理工作。由于只是清理没有发生任何复制动作,所以清理完成后,剩下的堆空间时不连续的。此时所有存活的对象向内存一端移动,以确保紧凑连续。
在了解了这两种清理方式后,我们来看看堆内存中三大区域是如何分工协作的。
- 年轻代
对象在被创建时,内存分配首先发生在年轻代(有一部分大对象可以直接被创建在年老代)。大部分对象在被创建后很快不再被使用。于是会被年轻代的GC机制清理掉,叫做 Minor GC 或者 Young GC。
年轻代还可以划分为三个区域:Eden区,Survivor0,Survivor1.
Eden区:伊甸园,很形象的命名。对象在年轻代中会被首先创建到Eden区。
年轻代使用“停止-复制”清理方法,具体步骤如下:
- 对象被创建在Eden区。
- 当Eden区满时,则触发一次 Minor GC。会清理消亡的对象,并将存活的对象复制到 Survivor0 区。(此时 Survivor1区是空的)
- 在Eden区执行几次GC后,Survivor0 区也会变满。此时对Survivor0 区进行GC清理动作,然后将Eden区本次未消亡的对象和 Survivor0 区存活的对象一起复制到 Survivor1 区。(此后一段时间内 Survivor0区是空的)
- 当两个 Survivor 区相互切换固定次数后(HotSpot虚拟机默认是15次,可手动更改)。将仍然存活的对象复制到年老代。此时仍存货的对象很少,比如我们自定义的类。
- 年老代
在年老代满了的时候,会触发GC机制。被称为 Major GC 或 Full GC。年老代使用的GC方式为“标记-清理”。
- 当创建较大的对象时,如长字符串,大数组。Young空间不足,则会直接分配至年老代。大对象会提前出发GC动作。所以应尽少使用。更应该避免使用短命的大对象。
- 会存在年老代引用新生代对象的情况。此时如果 Young GC时,可能要查询年老代,已确定是否可以回收,这十分低效。所以在年老代中会维护一个 512byte 的“ card table ”,存储多有对年轻代的引用记录。只需要查询此处即可。
- 永久代
永久代经常只会发生两种清理动作:
- 常量池中的常量。此时只需要确定没有该常量存活的引用即可。
- 无用的类信息,此时需要确定以下三点,才可以回收。
(1.):类的所有实例已经被回收;
(2.):类的ClassLoader被回收;
(3.):没有通过反射引用该类的地方;
更多推荐



所有评论(0)