堆目录
堆的概述
-
对于大多数虚拟机来讲,java堆是java虚拟机管理的内存中对打的一块,java堆 是被所有线程共享的一块内存区域,在虚拟机中自动创建,此内存区域的唯一的目的就是存放对象实例,几乎所有的对象实例都在这里分配内存,。这一点在java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配,但是随着JIT编译器的发展也逃逸分析技术逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化发生,所有的对象都分在堆上也逐渐变的不是那么绝对
-
java堆是垃圾回收管理器的主要区域,因此很多时候也被称作“GC堆”,从内存回收的角度来看,由于现在收集器基本都采用分代收集算法,所以java堆中可以细分为:新生代、老年代;再细致一点有Eden空间、From Survivor空间、To Survivor 空间等。从内存角度分配的角度来看,线程共享的java堆可能划分出多个线程私有的分配缓冲区(TLAB)。不过无论怎么划分,都与存放内容无关,无论哪个区域,存储的都依然是对象实例,进一步的划分的目的是为了更好的回收内存,或者更快的分配内存。
-
根据java虚拟机规范的规定,java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,就像我们我们的磁盘空间一样。在实现时,既可以实现成固定大小的,也可以是可扩展的,不过当前主流的虚拟机都是按照可扩展来实现的(通过-Xmx 和 -Xms控制)。如果在堆中没有内存完成实例分配,并且堆也无法在扩展时,将会抛出OutOfMemoryError异常
堆的发展历程(jdk1.7 , jdk1.8)
在jdk1.7 即之前堆的内存逻辑分为三个部分:
- 新生代(Young Generation Space):上文也又说到新生代又可以细分为Eden空间、From Survivor 空间,To survivor 空间
- 养老区(Tenure generation space):老年代。
- 永久区(Permanent Space)
在jdk1.8 及之后对的内部结构
- 新生区(Young Generation Space):其中新生区又被细分为Eden空间和From Survivor空间、To survivor 空间 。
- 养老区(Tenuregeneration space):老年代。
- 元空间(Meta Space)(这个是改变了,元空间已经不是使用虚拟机的内存)
内部结构的介绍(新生代、老年代)
-
新生代
- 新生的对象优先存放在新生区中,新生对象朝生夕死,存活率低,在常规应用进行一次GC(垃圾回收)一般可以回收70%~95%的空间,回收效率高
- Hotspo虚拟机将新生区划分为三块(上文有提到)一块较大的Eden空间,和两块较小的survivor(幸存者)空间,默认比例8:1:1。划分的目的是因为HotSpot采用复制算法来回收新生代, 设置这个比例是为了充分利用内存空间,减少浪费。新生成的对象在Eden区分配,当Eden区没有足够的空间进行分配时,虚拟机将发起一次Minor GC 。
- 在Java中新创建的对象,JVM会在Eden区中创建该对象,经历过一次Minor GC之后仍然存活并且能够被Survivor From区容纳,该对象将会被移动到Survivor From区中并将该对象的年龄设置为1。该对象每在Survivor区中熬过一次Minor GC其年龄就会增加1岁,当该对象的年龄到达一定的程度之后(Hotspot默认为15岁,不同的JVM会有不同的默认值)该对象就会被移动到老年代。
-
老年代:
- 在新生代中经历了多次(具体看虚拟机配置的阀值,默认是15)GC后仍然存活下来的对象会进入老年代中。老年代中的对象生命周期较长,存活率比较高,在老年代中进行GC的频率相对而言较低,而且回收的速度也比较慢。
堆内存设置与OOM
jvm堆区用于存储java对象实例,在jvm启动时堆空间就会被确定下来,可以通过-Xms 和-Xmx选项进行配置堆空间大小(一般两者配置等大)
- -Xms:表示堆区的起始内存,等价于-XX:InittialHeapSize
- -Xmx:表示堆区的最大内存,等价于-XX:MaxHeapSize.
一旦堆区的内存大小超过-Xmx所指定的最大内存时,将会抛出OutOfMemoryError错误(OOM)。通常情况下会将-Xms与-Xmx两个选项配置成相同的参数值,这样做能够在JVM进行垃圾回收清理完毕堆区后不需要重新分隔计算堆区的大小从而达到提升性能的目的。
在默认情况下:
- -Xms:物理内存大小 / 64。
- -Xmx:物理内存大小 / 4。
Minor GC 、Major GC 、Full GC
-
Minor GC(新生代收集)
- 当年轻代空间不足(空间已满指的是Eden空间不足),Survivor区空间不足不会触发GC,每次Minor GC会顺带收集Survivor区空间,这是被动关系
- java中80%的对象是朝生夕死,所以Minor GC会非常频繁,相应的回收速度也是非常快
- Minor GC 会导致STW(Stop The World )(可以去看我的另外一篇关于介绍STW),即暂停所有用户线程的执行,直到GC完毕
-
Minor GC(老年代收集)
- 当老年代空间不足,会尝试触发Minor GC,如果经过Minor GC 后,空间还是不足,则触发Major GC
- Major GC的执行速度通常比Minor GC的执行速度慢10倍以上,也就代表STW的时间更长;
- 如果Major GC后空间还是不足,此时会出现OOM异常;
-
full GC(整堆收集)
-
调用System.gc()时,系统建议执行Full GC,但是不必然执行 ;
-
老年代空间不足;
-
方法区空间不足;
-
通过Minor GC后进入老年代的对象大小大于此时老年代的可用空间;
-
由Eden区,S0(from)区向S1(to)区复制时,对象大小大于to区可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小;
-
Full GC是开发中尽量避免的,也是调优重点关注的地方之一。
-
堆空间常用配置参数
/**
* 测试堆空间的常用的jvm参数:
*
* -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 简要信息 : 1:-XX:+PrintGC 2: -Verbose:gc
*
* -XX:HandlePromotionFailure :是否设置空间分配担保
*
TLAB(线程私有缓冲区)
堆区是所有线程都共享了,那么如何保证线程安全性呢,就有TLAB了。TLAB是包含在Eden区中,大约占整个Eden空间的1%,是在分配内存的过程中私有的,当内存分配完毕后,此区域还是线程共享的,TLAB参数设置
-XX:+/-UseTLAB :开启/关闭TLAB
-XX:TLABWasteTargetPercent :设置TLAB占用Eden区百分比大小