Java堆
Java堆的结构
还是那句话“栈管运行,堆管数据”,Java堆中存储的主要就是对象实例数据。一个JVM实例只存在一个堆,其大小默认情况下是内存的1/4(HotSpot)。堆从GC回收的角度看物理上分为两个部分:新生区、老年区;而逻辑上又可以分为三个部分:
- 新生区 PSYoungGen
- 老年区 ParOldGen
- 元空间 MetaSpace
新生区
新生区是所有对象产生的地方,也是大部分对象消亡的地方。新生区又可以细分为3块
- Eden区,对象产生的地方。
- 幸存区
- From区,上一次轻GC清理后幸存的对象存放的地方。
- To区,本次GC清理完后幸存的对象存放到这。
注意From区和To区是动态变化的,每一次GC清理完,幸存区中空的部分为To区,幸存的对象复制到To区,然后当前的From区和Eden区被GC完全清空。接着在下一次GC时,原本的From区由于被上一次的GC清空了,而原本的To区存储着上一次幸存的对象,所以对于当前GC来说From区和To区的位置恰好和之前相反。
养老区
养老区存储的也是对象,但存储在养老区的对象是经过了多次新生区的轻GC后幸存下来的对象(15次)。在养老区也满的情况下就会触发FullGC进行垃圾回收,如果仍然爆满,则会爆出OOM(OutOfMemory)错误。
元空间(仅仅逻辑上属于堆!)
元空间其实是对方法区的实现。之前提过方法区存储的是类的模板信息,这就类似一个接口,只告诉你有什么而不是具体的,所以将方法区的模板实现后这些数据包括类、常量池、方法、编译器编译后的代码就存到了元空间中。(Java8之前元空间就叫做永久区)
GC垃圾回收
堆之所以这么划分就是因为GC回收时方便管理。由对象在堆中的转移顺序可知,GC回收必然是先发生在新生区接着才可能是养老区。
MinorGC
新生区的垃圾回收,就是轻GC,也就说GC。它的过程简而言之三步:复制、清空、交换。
- Eden区新生对象,当产生的对象数量第一次挤满Eden区,就会触发轻GC,还有引用的对象就复制到To区,接着清空整个Eden区,GC完后原本的To区(before)由于存放了对象,所以To区(before)变成了From区(now),From区(before)成了To区(now),就是所谓的交换。接着Eden区还在不断new对象,又一次挤满Eden后,又触发GC,还有引用的对象复制到To区,然后清空整个Eden区和From区,再发生交换……
清空之后有交换,谁空谁是To
- 部分对象由于每次都存活,在From区和To区来回复制,每次复制年龄
,15岁后就转移到养老区。(具体岁数可以由JVM参数MaxTenuringThreshold来调整)
FullGC
从新生区幸存多次的对象不断存入养老区,当养老区也满了,就会触发FullGC。养老区仍然爆满后就抛出OOM错误。
堆参数
实际的应用中,我们可能会需要调整一下JVM默认的堆参数来满足实际的开发需求,下面看看如何进行堆参数的调整,同时通过OOM验证堆的结构。
首先通过如下代码,我们可以查看我们的CPU核数,和当前堆的一些默认设定。 -Xmx
参数就是堆的最大内存限制, -Xms
就是堆的初始内存限制。
public static void main(String[] args) {
System.out.println("CPU核数:" + Runtime.getRuntime().availableProcessors());
// 获取当前实例的运行时数据区对象
// 运行时数据区就是我们所说的JVM的运行时数据区,只不过封装成了一个类
long maxMemory = Runtime.getRuntime().maxMemory();
long totalMemory = Runtime.getRuntime().totalMemory();
System.out.println("-Xmx: MAX_MEMMORY = " + maxMemory + "bytes, " + (maxMemory / (double) 1024 / 1024) + "MB");
System.out.println("-Xms: TOTAL_MEMORY = " + totalMemory + "bytes, " + (totalMemory / (double) 1024 / 1024) + "MB");
}
然后我查看本机的内存大小,发现JVM堆的默认最大值大概就是本机内存的1/4.
在Eclipse下,找到JVM参数的配置,可以看到每一个运行程序对应一个JVM分配的堆
然后为了能够快速观察到OOM错误,我将堆的大小改为了10m,然后 while(true)
使劲 new
对象,之后通过控制台就可以观察到垃圾回收的过程:先是多个GC,之后出现FullGC,FullGC搞不定最后就出现了OOM错误。