Java堆

Java堆的结构

还是那句话“栈管运行,堆管数据”,Java堆中存储的主要就是对象实例数据。一个JVM实例只存在一个堆,其大小默认情况下是内存的1/4(HotSpot)。堆从GC回收的角度看物理上分为两个部分:新生区、老年区;而逻辑上又可以分为三个部分:

  • 新生区 PSYoungGen
  • 老年区 ParOldGen
  • 元空间 MetaSpace


image.png
物理上


image.png
逻辑上

新生区

新生区是所有对象产生的地方,也是大部分对象消亡的地方。新生区又可以细分为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)错误。
image.png


元空间(仅仅逻辑上属于堆!)

元空间其实是对方法区的实现。之前提过方法区存储的是类的模板信息,这就类似一个接口,只告诉你有什么而不是具体的,所以将方法区的模板实现后这些数据包括类、常量池、方法、编译器编译后的代码就存到了元空间中。(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");
}

image.png
然后我查看本机的内存大小,发现JVM堆的默认最大值大概就是本机内存的1/4.
image.png
在Eclipse下,找到JVM参数的配置,可以看到每一个运行程序对应一个JVM分配的堆
image.png


image.png


image.png


然后为了能够快速观察到OOM错误,我将堆的大小改为了10m,然后 while(true) 使劲 new 对象,之后通过控制台就可以观察到垃圾回收的过程:先是多个GC,之后出现FullGC,FullGC搞不定最后就出现了OOM错误。
image.png

posted @ 2020-03-27 18:21  Bankarian  阅读(250)  评论(0编辑  收藏  举报