堆
-
堆针对一个JVM进程来说是唯一的,一个进程只有一个JVM,但是一个进程包含多个线程,他们共享同一个堆空间
-
JVM实例只存在一个堆内存,堆也是java内存管理的核心区域
-
Java堆去在JVM启动的时候即可被创建,其空间大小已经确定了,是JVM管理的最大一块内存空间
- 堆内存大小是可以调节的
-
堆可以处于物理上不连续的内存空间中,但逻辑上他们被视为连续的
-
所有线程共享堆空间,堆里面也有划分的线程私有的缓冲区 Thread Local Allocation Buffer TLAB
- -Xms10m 最小堆内存
- -Xmx 最大堆内存
-
几乎所有对象实例都在堆中分配内存,还有一些在栈上分配,数组和对象可能永远不会存储在栈上,因为栈帧中保留引用,这个引用指向了对象或者数组在堆中的位置
-
The heap is the run-time data area from which memory for all class instances and arrays is allcated 所有对象实例及数组都应该当在运行时分配到堆上
-
方法结束之后堆中的对象不会马上移除,仅在触发GC的时候才会移除
-
在GC回收的时候因为STW (stop the world)的影响,用户线程会暂定执行,所以减少GC运行的次数和时间可以有效优化程序
-
堆是GC执行垃圾回收的终点区域
-
堆内存划分
- JDK7以前堆内存逻辑上分为三部分
- Young Genration Space 新生代 New/Young 又细分为Eden伊甸园区和Survivor0 Survivor1区
- Tenure Genration Space 老年代 Old/Tenure
- Permanent Space 永久代 Perm
- JDK8之后内存逻辑上也分为三部分主要跟JDK7以前的区别在于永久代变成了元空间
- Young Genration Space 新生代 New/Young 又细分为Eden伊甸园区和Survivor0 Survivor1区
- Tenure Genration Space 老年代 Old/Tenure
- Meta Space 元空间 Meta
- JDK7以前堆内存逻辑上分为三部分
-
设置堆大小与OOM
- java堆区用于存储java对象实例,堆的大小在JVM启动时候已经设定好了
- -Xms 设置起始堆空间大小 memory start 或者 -XX:InitialHeapSize
- -Xmx 设置堆最大内存空间 memory max 或者 -XX:maxHeapSize
- 一旦堆区中的内存大小超过了-Xmx指定的内存大小就会抛出OOM异常
- 通常在生产中-Xms和-Xmx两个参数设置成相同,为了能够在GC在清理完堆区之后不需要重新分割计算堆区大小从而提高性能
- java堆区用于存储java对象实例,堆的大小在JVM启动时候已经设定好了
-
默认值
-
初始内存大小默认为系统内存/64
-
最大内存大小默认为系统内存/4
-
public class HeapTest2 { public static void main(String[] args) { // 返回Java虚拟机中的堆内存总量 long initialMemory = Runtime.getRuntime().totalMemory() / 1024 / 1024; // 返回Java虚拟机试图使用的最大堆内存 long maxMemory = Runtime.getRuntime().maxMemory() / 1024 / 1024; System.out.println("-Xms:" + initialMemory + "M"); System.out.println("-Xmx:" + maxMemory + "M"); } }
-
-
使用jstat -gc 进程ID 查看内存分配情况
- S0C S1C Survivor 0/1 Capacity 幸存者区总容量
- S0U S1U Survivor 0/1 Used 已使用的幸存者区
- EC Eden Capacity
- EU Eden Used
- OC Old Capacity
- OU Old Used
- MC Meta Capacity
- MU Meta Used
- JVM参数 -XX:+PrintGCDetails
-
可以使用JVisualVM 中的抽样器查看哪些资源内存占用高
-
新生代和老年代
- 一类生命周期较短的瞬时对象,对象的创建和消亡都非常迅速通,常存放在新生代,也可能因为在Eden满执行GC的时候此时TO也满了,会直接放在O区
- 另一类对象生命周期非常长,极端情况下与JVM生命周期一致,通常放在O区
-
新生代又可细分为Eden空间和Survivor0和Survior1 也称from to区
-
新生代中可调节的参数
- 默认-XX:NewRatio=2 表示新生代占1 老年代占2 新生代占整个堆的1/3
- 可修改-XX:NewRatio=4 表示新生代占1 老年代占4 新生代栈整个堆的1/5
- 如果发现整个项目中,生命周期长的对象偏多,就可以通过调整老年代大小
-
在HotSpot中,Eden空间和另外两个Survivor的默认比例是8:1:1,也可以通过-XX:SurviorRatio调整空间比例,但是实际在JVisualVm中查看其实是6:1:1 需要显示指定–XX:SurviorRatio=8
-
几乎所有java对象都在Eden区被new出来,绝大多数java对象在新生代销毁.有些大对象在Eden中无法存储会直接进入老年代
-
80%的对象都是朝生夕死,也可以使用-Xmn来设置新生代的最大内存大小,一般不用设置,如果设置了具体大小,则使用具体大小-XX:NewRatio参数失效
-
新生代中的对象存在一个生命周期计数器的概念,默认生命周期计数器达到15的时候会进入老年代,或者当有一半以上对象的生命周期计数器都大于2也会进入老年代 也可以通过-XX:MaxTenuringThreshold=N进行设置
图解对象分配的过程
-
对一个新对象的内存分配是一个非常复杂严谨的事,JVM设计者不仅需要考虑内存如何分配,分配到哪里的问题,也需要考虑与内存分配密切相关的内存回收算法,还需要考虑GC在执行完成之后是否会在内存空间产生内存碎片
- new的对象先进入Eden 如果大于Eden空间直接进入Old
- 当Eden空间满的时候,程序还需要创建对象,触发GC(MinorGC),会对Eden和S1/S0进行垃圾回收,不被其他对象引用的对象销毁
- 触发 GC之后 Eden和幸存者FROM区的剩余对象 移动到 幸存者TO区
- 如果FROM区中的生命周期计数器大于15将对象放入OLD区
- 当OLD区内存不足,再次触发GC(MajorGC)会被OLD NEW区进行垃圾回收
- 如果进行MajorGC之后依然无法存储对象 产生OOM
-
图解过程
-
我们创建的对象,一般都是存放在Eden区的,当我们Eden区满了后,就会触发GC操作,一般被称为 YGC / Minor GC操作
-
当我们进行一次垃圾收集后,红色的将会被回收,而绿色的还会被占用着,存放在S0(Survivor From)区。同时我们给每个对象设置了一个年龄计数器,一次回收后就是1。
同时Eden区继续存放对象,当Eden区再次存满的时候,又会触发一个MinorGC操作,此时GC将会把 Eden和Survivor From中的对象 进行一次收集,把存活的对象放到 Survivor To区,同时让年龄 + 1
-
我们继续不断的进行对象生成 和 垃圾回收,当Survivor中的对象的年龄达到15的时候,将会触发一次 Promotion晋升的操作,也就是将年轻代中的对象 晋升到 老年代中
-
只有在Eden满的时候才会触发MinorGC Survivor满的时候不会触发GC
-
当Survivor满的时候会直接放入Old
-
-
对象分配特殊情况
常用调优工具
- java自带的命令 jinfo jstat Jmap等
- Eclipse: Memory Analyzer Tool
- Jconsole
- Visual VM
- Jprofiler
- Java Flight Recorder
- GCViewer
- GCEasy
总结
- 针对幸存者S0 和S1 的总结: 复制中以后有交换 谁空谁是TO
- 关于垃圾回收,频繁出现在新生代,很少出现在老年代,几乎不出现在永久代和元空间
- 新生代采用复制算法目的是为了减少内存碎片