- 一个JVM实例只存在一个堆内存,堆也是内存管理的核心区域。
- Java堆区在JVM启动的时候被创建,其空间大小也确定好了。堆是JVM管理的最大一块内存空间。(堆内存的大小是可以调节的,启动java程序时使用-Xms -Xmx参数即可)
- 《Java虚拟机规范》规定,堆可以处于物理上不连续的内存空间中,但在逻辑上,它应该被视为连续的。
- 所有的线程共享Java堆,在这里还可以划分线程私有的缓冲区(Thread Local Allocation Buffer,TLAB)
- 《Java虚拟机规范》中对Java堆的描述是:所有的对象实例以及数组都应当在运行时分配在堆上。
- 数组和对象可能永远不会存储在栈上,因为栈中只会保存引用,这个引用指向对象或者数组在堆中的位置。
- 在方法结束后,堆中的对象不会马上被移除,仅仅在垃圾收集的时候才会被移除。
- 堆,是GC(Garbage Collection,垃圾收集器)执行垃圾回收的重点区域。
- 现代垃圾收集器大部分都基于分代收集理论设计,堆空间可细分为:
- Java7及之前堆内逻辑分为三个部分:新生区、养老区、永久区
- Java8及之后内存逻辑分为三个部分:新生区、养老区、元空间
- Java堆区用于存储Java对象实例,那么堆的大小在启动JVM的时候就已经确定好了,我们也可以通过-Xms和-Xmx来进行设置堆空间(年轻代+老年代)
- -Xms用于表示堆区的起始内存,等价于-XX:InitialHeapSize
- -Xmx则用于表示堆区的最大内存,等价于-XX:MaxHeapSize
- 一旦堆区的内存大小超过“-Xmx”所指定的最大内存时,将会抛出OutOfMemory异常
- 通常会将-Xms和-Xmx两个参数配置相同的值,其目的是为了能够在java垃圾回收机制清理完堆区后不需要重新分配计算堆区的大小,从而提高性能。
- 默认情况下,初始内存大小:物理内存大小/64、最大内存大小:物理内存大小/4
- 堆内存查看方式:
- jps | jstat -gc pid
- -XX:+PrintGCDetails
- OOM的举例与说明:
存储在JVM中的Java对象可以被划分为两类:
- 一类是生命周期较短的瞬时对象,这类对象的创建和消亡都非常迅速
- 另一类对象的生命周期较长,在某些极端情况下还能够与JVM的生命周期保持一致。
- JVM堆区进一步划分的话,还可以划分为年轻代(YoungGen)和老年代(OldGen)。其中年轻代又可以划分为Eden空间、Survivor0空间和Survivor1空间(有时也叫做from区,to区)。
- NewRatio:设置新生代和老年代的比例,默认值是2,即新生代:老年代 = 2
- 通过 jinfo 来查看当前比例:
- 在HotSpot中,Eden空间和另外Survivor空间缺省所占比例是8:1:1,开发人员可以通过选项“-XX:SurvivorRation”调整这个空间比例。
- 几乎所有的Java对象都是在Eden区被new出来的。
- 绝大部分的Java对象的销毁都在新生代进行
- 可以使用选项“-Xmn”设置新生代最大内存大小。
- new的对象先放伊甸园区。此区有大小限制。
- 当伊甸园的空间填满时,程序又需要创建对象,JVM的垃圾回收器将对伊甸园区进行垃圾回收(Minor GC),将伊甸园区不再被其他对象所引用的对象进行销毁。再加载新的对象放到伊甸园区。并将伊甸园中的剩余对象移动到幸存者0区
- 如果再次触发垃圾回收,此时上次幸存下来的放到幸存者0区的,如果没有回收,就会放到幸存者1区。触发垃圾回收后在伊甸园区幸存下来的对象也会放到1区
- 如果再次经历垃圾回收,此时会重新放回0区,接着再去幸存者1区。
- 经历15次转移的对象,会被放到养老区。也可以通过选项“-XX:MaxTenuringThread=
”进行设置
JVM在进行GC时,并非每次都对上面三个内存区域(新生代、老年代、方法区)一起回收,大部分都是在新生代进行回收。
针对HotSpot VM的实现, GC按照回收区域又分为两大类型:部分收集(Partial GC)和整堆收集(Full GC)。
- 部分收集:
- 新生代收集(Minor GC / Young GC),只针对新生代进行垃圾收集
- 老年代收集(Major GC / Old GC),只针对老年代的垃圾收集
- 目前,只有CMS GC会有单独收集老年代的行为
- 注意,很多时候Major GC 和 Full GC会混淆,具体分辨就看是老年代回收还是整堆回收
- 混合收集(Mixed GC):收集整个新生代和部分老年代的垃圾回收
- 目前只有G1 GC会有这种行为
- 整堆收集:收集整个java堆和方法区的垃圾收集。
年轻代GC的触发机制:
- 当年轻代空间不足时,就会触发Minor GC,这里的年轻代指的是Eden满,Survior满不会引发GC。
- 因为Java中对象大多是朝生夕死的,所以Minor GC非常频繁,而且一般回收速度也较快。
- Minor GC会触发STW,暂停其他用户的线程,等垃圾回收结束,用户线程才恢复运行。
老年代GC触发机制:
- 指发生在老年代的GC,对象从老年代消失时,我们说“Major GC”或“Full GC”发生了
- 出现Major GC,一般至少会发生一次Minor GC(并非绝对的,Parallel Scavenge收集器的收集策略中就有直接进行Major GC的过程)
- Major GC的速度一般会比Minor GC慢10倍以上,STW时间更长
- 如果Major GC后,内存还不够,就会OOM
Full GC触发机制:
- 调用System.gc(),系统建议执行Full GC,但是不是必然执行的。
- 老年代空间不足
- 方法区空间不足
- ...
说明:full gc是开发或调优中尽量要避免的,这样STW的时间更短一些。
6、堆空间分代思想为什么要进行分代?不分代就不能正常工作吗?
- 经研究,不同对象的生命周期不同,70%-99%的对象都是临时对象
- 新生代:有Eden区和两块survivor区,其中to区总为空
- 老年代:存放新生代中多次GC后还存活的对象、或者新生代中放不下的对象
- 不分代也是完全可以的,分代只是为了优化GC性能。如果不进行分代,所有对象都在一个空间里,每次进行垃圾回收都要把全部对象都检查一遍,影响性能。分代之后,由于大部分对象都是朝生夕死的,所以在创建对象的新生区进行频繁的Minor GC是合理的,而多次GC后仍然存活的对象预计是长期存在的,所以存放到老年区,这样这一部分对象就不会被频繁地检查了。
如果对象在Eden出生并经过一次Minor GC后仍然存活,并且能被survivor容纳的话,将被移动到survivor空间,并将对象年龄设为1。对象每经过一次Minor GC并存活下来后,年龄都会加1。当增长到一定程度之后,将会晋升至老年区。
对象晋升老年区的阈值,可以通过:-XX:MaxTenuringThreshold来设置
不同年龄段对象分配原则:
- 优先分配到Eden
- 大对象直接分配到老年代
- 尽量避免出现太大的对象
- 长期存活的对象分配到老年代
- 动态对象年龄判断
- 如果Survivor区中相同年龄的对象大小的总和大于Survivor空间的一半,大于等于该年龄的对象可以直接进入老年区,无需等到MaxTenuringThreshold设定的值
- 空间分配担保
- -XX:HandlePromotionFailure
为什么要有TLAB?
- 堆区是线程共享区域,任何线程都可以访问到堆区中的共享数据
- 在并发环境下,从堆区读写数据是线程不安全的。
- 为避免多个线程操作同一地址的数据,需要使用加锁等机制,影响分配速度
什么是TLAB?
- 从内存模型而不是垃圾收集的角度,对Eden区继续进行划分,JVM为每个线程分配了一个私有缓存区域,它包含在Eden空间中。
- 多线程同时分配内存时,可以避免线程安全问题,同时还能提高内存分配的吞吐量,因此我们可以将这种内存分配方式称为快速分配策略
相关说明:
- 尽管不是所有的实例对象都能够在TLAB中分配内存,但JVM确实是将TLAB作为内存分配的首选
- 可以通过选项“-XX:UseTLAB”来设置是否开启TLAB空间
- 默认情况下,TLAB空间的内存非常小,仅占Eden空间的1%。可以通过选项“-XX:TLABWasteTargetPercent”设置TLAB所占用空间的百分比大小。
- 一旦对象在TLAB空间分配内存失败,JVM就会通过加锁的机制来确保数据操作的线程安全性,从而直接在Eden空间分配内存。
-XX:+PrintFlagsInitial : 查看所有的参数的默认初始值
-XX:+PrintFlagsFinal :查看所有的参数的最终值(可能会存在修改,不再是初始值)
具体查看某个参数的指令: jps:查看当前运行中的进程
jinfo -flag SurvivorRatio 进程id
-Xms:初始堆空间内存 (默认为物理内存的1/64)
-Xmx:最大堆空间内存(默认为物理内存的1/4)
-Xmn:设置新生代的大小。(初始值及最大值)
-XX:NewRatio:配置新生代与老年代在堆结构的占比
-XX:SurvivorRatio:设置新生代中Eden和S0/S1空间的比例
-XX:MaxTenuringThreshold:设置新生代垃圾的最大年龄
-XX:+PrintGCDetails:输出详细的GC处理日志
打印gc简要信息:① -XX:+PrintGC ② -verbose:gc
-XX:HandlePromotionFailure:是否设置空间分配担保



