堆
一、堆(Heap)的核心概述
堆空间:新生代+老年代(不包含永久区/元空间)
1、图示
堆:要考虑到内存空间中的最大空间
一个进程对应一个JVM实例,一个JVM实例中对应一个运行时数据区,一个进程只有一份堆,一个进行只有一份方法区,一个进程中对应多个线程,多个线程共享 堆空间、方法去;
每个线程独自拥有一套虚拟机栈、PC程序计数器、本地方法栈
2、概述
- 引用关系
3、内存细分
4、堆空间内存结构
堆空间:新生代+老年代(不包含永久区/元空间)
- **(JDK7)**↓
- **(JDK8)**↓
二、设置堆内存大小与OOM
1、堆空间大小设置
-Xms size #堆空间初始化内存大小
-Xmx size #堆空间最大空内存大小
2、OOM举例
三、新生代&老年代
1、堆空间结构
对象被创建会先放着伊甸区
,若没被GC,那后来会进入幸存者区,然后还没有被GC就再到老年区
2、堆空间设置比例大小
-XX:NewRatio=size #设置新生代老年代比例【新生代:老年代】
-XX:SurvivorRatio=size #设置新生代与幸存者区比例【伊甸区:幸存者1区:幸存者2区】
-Xmn size #设置新生代空间大小 (一般不设置)
- 对象存放流程
先放入Eden区,若还存活;就放入Survivor区,若还存活;就放入Tenured老年代
四、图解对象分配过程
1、概述
-XX:MaxTenuringThreshold=size #设置进入老年区的阈值
- YGC/Minor GC触发的条件:
当伊甸园区满的时候,且还要进行加入对象时触发
- 当幸存者区满的时候,不会触发YGC/Minor GC
- 当幸存者区满了后,可能会被直接转移至老年代
2、对象分配的一般过程图示
- 步骤1:↓
当伊甸园区满的时候,再造对象时,会触发SWT(StopWord停止用户线程),启动YGC/Minor GC(GC线程),去回收伊甸园区里的内存空间,通过可达性分析算法,进行垃圾回收;如果还有被占用/使用的对象,那就会被转移到幸存者区;
并为每一个转移还在使用的对象分配一个年龄计数器,并将每个年龄计数器赋值为1;
这时,S0为FROM区,S1为TO区;(谁空谁是TO)
此时伊甸园区里面就没有数据了,完全情况,垃圾被回收,再使用的被转移至幸存者区S0中,此时S1为空;
- 步骤2:↓
那么接下来创建的对象,就会再被放入伊甸园区;
再放放放放…,此时伊甸园区又满了;
再次触发,YGC/Minor GC垃圾回收,情况如上;
但是此时还在被使用的对象,就会被转移至S1中;此时S0存之前转移的对象,S0存此时转移来的对象;被设置转移对象的年龄计数器为1,;
但是!!!此时YGC/Minor GC垃圾回收,会对之前S1中存储对象进行判断,他们是否还再被使用;
如果还被使用就转移到S1区,然后给他们的年龄计数器+1;如果不使用,就被垃圾回收;
此时!!!伊甸园区和S0里面已经被GC清空了!此时S0就变成了幸存者TO区。
这时,S0是幸存者TO区,S1是幸存者FROM区;(谁空谁是TO)
之后就一直重复如上的过程!!!!!!
- 步骤3:↓
重复如上的过程。。。。
如果当幸存者区里面的的对象还有被占用,且他的年龄计数器大于15,他就会被Promotion(晋升);
将其还被占用的对象,且年龄计数器大于15的对象,转移至老年代;
此时就不再考虑年龄计数器了,年龄计数器只用于: 幸存者区===>老年区的指标
这里的年龄计数器的15,为阈值;默认为15;
进入老年代后,再被回收的可能性就会很小了。。。
3、总结
- 谁空谁是TO
- 频繁收集新生代,较少收集养老带,几乎不动永久带
4、流程图
- 代码示例
- 伊甸园区/幸存者S0/幸存者S1/老年代区的内存使用情况图
5、常用的调优工具
五、Minr GC、Major GC与Full GC
1、概述
2、最简单的分代式GC策略触发条件
六、堆空间分代思想
最主要的原因就是针对对应活跃度的数据做出GC回收,而不是对全部的对象数据进行GC遍历;
又浪费性能,又浪费时间,提升效率
七、内存分配策略
1、一般情况
2、对象提升原则(内存分配策略)
同年龄对象内存和大于s区的一半,大于等于该年龄对象全部进入老年代
八、为对象分配内存:TLAB
为每一个线程分配缓冲区,大致意思就是把Eden分成小格小格的,每个线程先用自己的小格子来分配对象,避免线程安全问题
1、目的
2、定义
在伊甸园区为每一个线程分配一份独立的缓存区
3、图示
4、说明
默认开启了TLAB空间,JVM会将TLAB区域作为对象内存分配的首选
,如果对象被分配到了TLAB中就不用考虑线程安全问题,而当TLAB满了的时候,对象就只能放在外面,此时就需要加锁了来保证线程数据安全,与数据不被覆盖
- 命令行代码
-XX:UseTLAB #查看是否开启TLAB空间
-XX:TLABWasteTargetParent #设置TLAB空间比例
- 流程图
5、堆空间一定是共享的吗????
不是的,对堆中有TLAB,线程缓冲区;每一个线程都有一部分独立的空间在堆空间中被划分;
分配空间的时候,先优先分配到对应线程的TLAB区,如果不够,就分配到堆中,并给对于空间加独占锁,保证不会出现线程安全问题
九、堆空间参数设置
- 空间分配担保参数说明
HandlePromotionFailure
十、堆是分配对象存储的唯一选择吗?
- 是的,先阶段堆是分配对象存储的唯一选择,虽然有如下的栈上分配,但是最主要技术不成熟,所还是用的也是标量分配,而不是栈上分配
- new的对象默认会放入堆空间进行内存分配
1、前言
2、逃逸分析概念
根据该对象是否只是方法内使用还是在外部方法被引用,来判断它是否发生逃逸;
如果没有发生逃逸,就使用栈上分配;反之使用堆上分配
- 没有发送逃逸的例子
V对象的作用范围只在方法内部,所以没有发生逃逸
//没有发生逃逸
public void m1(){
V v = new V();
//....
}
没有发生逃逸的对象,则可以分配到栈上,随着方法执行结束,栈空间就被移除,栈空间线程私有,因为一个方法对应一个栈中的栈帧,栈帧执行完毕,会弹出栈,那么栈帧里面对应局部变量表、操作数栈都随之被移除,空间也就被释放了;在栈空间中根本不存在GC
- 发生逃逸的例子
sb这个对象在createSB()方法里面被创建,但是他的作用域不只是这个方法,他被return了;
所以有可能其他方法会获取到sb这个对象的引用,所以他就发生了逃逸
public static StringBuffer createSB(String s1,String s2){
StringBuffer sb = new StringBuffer();
sb.append(s1);
sb.append(s2);
return sb;
}
- 如何快速的判断这个对象有没有发生逃逸分析???
判断new的对象实体,是否有可能在被方法外调用
- 参数设置
-XX: +DoEscapeAnalysis #显式开启逃逸分析
-XX: +PrintEscapeAnalysis #查看逃逸分析筛选结果
- 结论
使用局部变量,少使用成员变量
3、逃逸分析代码优化
4、栈上分配
使用局部变量,少使用成员变量
5、同步省略(消除)
6、标量替换
new Point(1,2)的对象逃逸分析后,发现未发生逃逸
那就会优化将new Point分解成int x =1 , int y =2:
并放置在栈帧中对应的局部变量表中,而不是放在堆空间中,因此就减少GC了
如下:↓
原本需要创建一个对象的内存分配,转而分解成创建两个基本类型,实现的减少堆内存占用
- 标量替换参数设置
-XX:+EliminateAllocations #开启标量替换,默认打开