堆体系结构
前面提到的虚拟机栈主管Java程序的运行,而这里的堆(Heap)则是主管程序内存。Java堆是Java虚拟机管理内存中的最大一块,是所有线程共享的一块内存管理区域。 此内存区域唯一目的就是存放对象的实例,几乎所有对象实例都在堆中分配内存。方法区(Mathod Area)其实也是Java堆的一部分,但是为了从逻辑上区分方法区才出现方法区这一很小的内存空间。因为几乎所有的对象实例都存放在堆中,所以堆是垃圾收集器管理的主要区域。Java内存(不包括方法区)从逻辑上可以分为三块区域,其中第1和2店是堆内存,如下所示:
- Young Generation Space,新生区、新生代
- Tenure Generation Space,老年区、老年代
- Permanent Space,永久区、元空间
其中新生区还可以细分为:伊甸园区(Eden)、幸存区(0区、1区,又叫from区和to区);
下面是堆内存的内存大小分布图:
对象在堆中的生命周期
新生区是对象诞生、成长、消亡的区域,一个对象在这里产生、应用,最后被垃圾回收器回收,结束生命。所有的对象在伊甸园区被new出来,如果伊甸园区的空间用完了,但程序有还需要创建对象的话,JVM的垃圾回收器就是使用 Minor GC(轻GC)来对伊甸园区中的不再被其他对象所引用的对象进行销毁,而未被轻GC销毁的对象就会进入到幸存区0区,如果幸存区0区也满了就会再使用轻GC对此区域进行清理,还存活下来的对象就进入幸存区1区。如果幸存区1区也满了,最后就会进入养老区。如果最后连养老区都满了,这时候就是使用Major GC(Full GC,也叫重GC)来清理养老区。如果使用重GC清理之后内存还是满的,就会产生OOM错误( OutOfMemoryError )。
如果出现java.lang.OutOfMemoryError:Java heap space 异常,说明虚拟机的堆内存不够,原因可能有:
- java虚拟机的堆内存设置不够,可以通过参数 -Xms、-Xmx来调整
- 代码中创建了大量大对象,并且长时间不能被垃圾回收器收集(存在被引用)
复制—>清空—>互换
- eden、From Survivor复制到 To Survivor,年龄+1
- 首先,当Eden区满的时候,会触发第一次GC,把还活着的对象拷贝到From Survivor 幸存0区,当Eden区再次触发GC的时候会扫描Eden区和From区域,对这两个区域进行垃圾回收,经过第二次回收后,还存活的对象,则直接复制到To区(如果有对象的年龄已经达到老年的标准,则赋值到养老区),同时把这些对象的年龄+1
- 清空eden、From Survivor
- 清空Eden和From Survivor中的对象,也即复制之后有交换,谁空谁是to
To Survivor和 FromSurvivor互换
最后,To Survivor和From Survivor互换,原To Survivor成为下一次的GC时的From Survivor区,部分对象会在From和To区域复制来复制去,如此交换15次(由JVM参数MaxTenuringThreshold决定,这个参数默认是15),最终如果还是存活,就存入到养老区。
堆参数调优
可以使用Java代码获取虚拟机的内存:
public class Test {public static void main(String[] args) {//返回Java虚拟机试图使用的最大内存量long maxMemory = Runtime.getRuntime().maxMemory();//返回Java虚拟机中的内存总数long totalMemory = Runtime.getRuntime().totalMemory();System.out.println("-Xmx:MAX_MEMORY =" + maxMemory + "(字节) 即约" + (maxMemory / (double) 1024 / 1024) + "MB");System.out.println("-Xms:TOTAL_MEMORY =" + totalMemory + "(字节) 即约" + (totalMemory / (double) 1024 / 1024) + "MB");}}

| 参数 | 说明 |
|---|---|
| -Xms | 设置初始内存分配大小,默认为物理内存的 1/64 |
| -Xmx | 最大分配内存,默认为物理内存的 1/4 |
| -XX:+PrintGCDetails | 输出详细的GC处理日志 |


虚拟机的堆内存是有固定大小的,当堆内存不够就会出现OOM。
