堆是JVM运行时对象实例及数组存储的位置,由所有线程共享,其大小在JVM启动时就已被确定

堆的组成结构

JDK1.7及之前堆是由新生代,老年代,永久代组成
JDK1.8及之后堆改为由新生代,老年代,元空间组成

image.png

堆的大小设置

堆的大小在JVM启动时就已确定,可以通过"-Xmx","-Xms"来进行设置
"-Xmx"用于表是堆的起始大小,未设置是默认为系统内存的1/64
"-Xms"用与表示堆的最大大小,未设置时默认为系统内存的1/4

image.png

当堆内存分配过少时,JVM将会启动失败,下图未内存为1m时运行结果

image.png

一旦堆中所占用的内存超过最大内存,就会抛出"OutOfMemoryError"错误

image.png

堆的起始大小和最大大小推荐配置为相同大小,可以避免堆的扩容和释放产生的资源消耗,使得服务更健壮
堆时垃圾回收的主要区域

新生代和老年代

新生代是对象实例创建时存储的位置,分为eden区,survivor0区和survivor1区(也可叫做from区和to区,其中正在存储数据的为from区,未存储数据的为to区)
当一个对象经过垃圾回收次数的阈值是,该对象会由新生代移动至老年代

image.png

新生代和老年代的大小设置

新生代和老年代在堆中的默认大小为1:2,可以通过下条参数显示GC回收信息查看

-XX:+PrintGCDetails

image.png

可以通过 –XX:NewRatio 命令来修改新生代和老年代的比例大小,例如–XX:NewRatio=3代表新生代和老年代的比例为1:3,即新生代占堆内存的1/4,老年代占堆内存的3/4

 –XX:NewRatio=3

新生代和老年代的数据存储

当一个实例创建时,其默认存储位置为新生代的伊甸园区,当伊甸园区内存空间不足时,将会出发GC,会将伊甸园区和from区进行垃圾回收,清空伊甸园区和from区,将存活的实例存放至to区,存活实例的年龄加一
虚拟机给每个对象定义了一个对象年龄(Age)计数器。如果对象在 Eden 出生并经过第一次 Minor GC 后仍然存活,并且能被 Survivor 容纳的话,将被移动到 Survivor 空间中,并将对象年龄设为 1。对象在 Survivor 区中每熬过一次 Minor GC,年龄就增加 1 岁,当它的年龄增加到一定程度(默认为 15 岁)时,就会被晋升到老年代中。对象晋升老年代的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold 来设置。

-XX:MaxTenuringThreshold=5

image.png

特殊情况

1.如果一个实例体积过大,伊甸园区无法存放,将会直接分配至靠年代
2.如果幸存者区垃圾回收后任内存无法放下存活实例,此时,该实例会直接晋升老年代
3.在面情况下,如果老年代也无法存放该实例,将会触发老年代的垃圾回收,如果GC操作后,内存任然不足,将会抛出"OutOfMemoryError"错误

image.png

堆的垃圾回收

堆时JVM垃圾回收的只要区域,程序运行时打大部分垃圾回收操作都是在堆中进行的,JVM在进行垃圾回收时,并非每次都是对堆中所有区域一起回收,大部分的回收都是对新生代的垃圾回收
在HotSpot虚拟机中,其垃圾回收按照回收区域可分为两大类,即部分收集(Partail GC)和整堆收集(Full GC)
部分收集指不是完整的收集整个堆中的垃圾,其中又分为老年代收集(Major GC)和新生代收集(Minor Gc)
整堆收集(Full GC)是指收集整个java堆和方法区的垃圾收集

Minor GC

当年轻代中的伊甸园区空间不足时,就会触发Minor GC(幸存者区空间满时不会触发),每次Minor GC都会清理整个年轻代的内存
应为Java程序中,大多对象都为临时对象,所以Minor GC的执行会非常频繁,且回收速度较快
Minor GC会引发STW,暂停其他用户的线程,等垃圾回收结束,用户线程才恢复运行

Major GC

当老年代中空间不足时,就会触发Major GC,经常会伴随至少一次的Minor GC(并不绝对,在Parallel Scavenge收集器的收集策略中由直接进行Major GC的策略选择过程)
因为Major GC的速度相较于Minor GC的速度要满上许多,STW时间更长,所以频繁的进行Major GC会影响程序的使用

Full GC

当准备要触发一次Minor GC时,如果发现统计数据说之前Minor GC的平均晋升大小比目前老年代剩余的空间大,则不会触发Minor GC而是转为触发Full GC(因为HotSpot VM的GC里,除了CMS的concurrFent collection之外,其它能收集老年代的GC都会同时收集整个堆,包括新生代,所以不需要事先触发一次单独的Minor GC);或者,如果有perm gen的话,要在perm gen分配空间但已经没有足够空间时,也要触发一次full GC;或者System.gc()、heap dump带GC,默认也是触发full GC。