堆的概述
堆空间主要用于java中数组和实例对象的存储,其可能出现OOM,并且是GC主要关注的区域。 一般的,堆被分为以下几个部分:
看不懂记不住?没关系,接下来我们一点一点的来进行了解。
异常与参数设置
异常的产生
前面的简介中我们说到,堆上会发生OOM异常,OOM的产生情况也是很容易想到的: 我们的数组和实例对象都存放在堆上,当内存不足时发生OOM异常。
public class TestOOM {
public static void main(String[] args) {
ArrayList<char[]> arrayList = new ArrayList<>();
while(true){
arrayList.add(new char[1024*1024]);
}
}
}
结果:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at com.linmu.heap.TestOOM.main(TestOOM.java:16)
产生OOM异常正是因为堆需要的内存超过了我们限定的大小,导致堆上没有空间用于容纳我们的对象。
堆大小的配置和查看
我们可以使用-Xms
设置堆的初始大小, -Xmx
设置堆的最大大小:
一般的,如果我们不明确的指定这些参数,堆的默认初始大小为当前电脑内存的1/64, 堆的最大大小为电脑内存的1/4。
如何判断我们设置的参数是否真的起作用了呢? 可以通过以下几种方式查看堆中内存大小分配情况:
- 使用jps查看java运行进程,再通过jstat查看gc详情:
- 使用
-XX:+PrintGCDetails
配置展示GC的具体情况信息:
- 使用java自带的jvisualVM可视化工具进行查看:
通过上面的方法,我们查看到了我们的Eden区大小为5632k, 两个Survivor区大小都为512k, 老年代大小为13824k。5632 + 512 + 512 + 13824 = 20480
正好是我们设置的-Xms的值。
新生代与老年代
在堆区中,有两个主要的内容,新生代和老年代, 他们都是用于存储数组和实例。 从名字就能看出,新生代用于存储新产生的对象,老年代用于存储年龄较老的对象。 这句话大部分情况下都是正确的,特殊情况我们之后遇到了再进行讨论。
新生代与老年代的内存大小分配
默认情况下,新生代与老年代的内存大小分配为1 : 2
我们也可以使用-XX:NewRatio=
来进行比例的设置,例如-XX:NewRatio=4
代表新生代与老年代比例为1:4,下图也能看出,新生代占6m, 老年代占24m
新生代
所谓新生代,是总是存储最新的对象的一个地方,新生代中通常又被分为三个区域: Eden区、Survivor1区和Survivor2区。
三个区域解析
Eden区是新对象创建的主要区域,当对象被创建时,他总是出现在Eden区中(内存过大的对象可能直接进入老年代)
当创建的对象达到一定的程度,Eden区可能被装满了,此时会发生一次Minor GC。 Minor GC判断Eden区中哪些对象此时已经死了,哪些没有死。 如果一个对象没有死,则将其放入 Survivor区中,并为其设置一个计数器。
对于Survivor区,一般将GC前装有对象的那一个叫做from区,没有对象的叫做to区。 当MinorGC发生时,from区中的对象会伴随着Eden区的存活对象一起迁移到to区中,然后from和to身份互换。 也就是说,每次GC后都最多只有一个Survivor区被使用。
也就是说,Eden区存储新创建的对象,Survivor区存储存活的对象, 同一时间最多有一个Survivor区被使用。
大小分配设置
和新生代和老年代相似的,Eden区和Survivor也可以通过-XX:SurvivorRatio=
进行比例设置, 默认比例为8:1:1
Minor GC详解
前面我们简单的介绍了Minor GC的过程,此处将其细化再进行分析:
- 一般的情况下, 创建的新对象总是位于Eden区,他们或被一直持有,或被用完直接抛弃,总之他们都存活在Eden区中。
- 如果一个对象内存过于庞大,大到Eden区无法容纳时,会直接将其存放到老年代中。
- 当我们的对象数量到达了一定程度,使得Eden的内存被占用完了,这时会触发一次Minor GC,并且STW(Stop The World)
- Minor GC一般情况下作用于Eden区,他会抛弃Eden中已经死亡的对象,将存活的对象放置到Survivor的to区。
- from区中的对象也会伴随着Eden的脚步,纷纷的转移到to区中
- 如果此时发现to区无法容纳所有的对象,Minor GC则会同时对Survivor区进行一次死亡对象回收。虽然Minor GC此时作用于Survivor,但是并不意味着Survivor能够触发Minor GC
- 但是一昧的将存活对象放置在Survivor区也不是个办法,因此对于所有存活的对象,都拥有一个计数器,当计数器到达一定数(默认为15,可以通过
-XX:InitialTenuringThreshol
和-XX:MaxTenuringThreshold
设置最小的晋升年龄和最大的晋升年龄) - 但是并不是所有情况下都能保证对象晋升老年代之前不会发生Surivor区满, 因此如果Minor GC发现Surivor区中有一半大小的对象年龄相同,则会将该年龄以上的对象直接晋升。