08 堆
1 堆的核心概述
- jvm启动时创建
- 一个jvm实例对应一个堆
- 堆物理上不连续,逻辑上连续
- 几乎所有对象都在堆上分配
- 数组、对象永不会存储在栈上,栈帧只存引用变量地址
2 设置堆内存大小与OOM
jdk7及之前: 新生代+老年代+永久区 (PermSize)
-Xms10M -Xmx20M -XPermSize
jdk8及之后:新生代+老年代+元空间 (MetaSpaceSize)
-Xms10M -Xmx20M -XMetaspaceSize1024m
默认大小
- 初始大小:物理电脑内存 1/64
- 最大内存:物理电脑内存 1/4
DEMO:
-Xms100m -Xmx110m -XX:+PrintGCDetails // 初始大小 long initialMemory = Runtime.getRuntime().totalMemory() / 1024/1024; // 最大 long maxMemory = Runtime.getRuntime().maxMemory() / 1024/1024; System.out.println("-Xms:" + initialMemory + "M"); System.out.println("-Xmx:" + maxMemory + "M");
结果
-Xms:96M -Xmx:98M Heap PSYoungGen total 29696K, used 9768K [0x00000007bdb80000, 0x00000007bfc80000, 0x00000007c0000000) eden space 25600K, 38% used [0x00000007bdb80000,0x00000007be50a388,0x00000007bf480000) from space 4096K, 0% used [0x00000007bf880000,0x00000007bf880000,0x00000007bfc80000) to space 4096K, 0% used [0x00000007bf480000,0x00000007bf480000,0x00000007bf880000) ParOldGen total 68608K, used 0K [0x00000007b9200000, 0x00000007bd500000, 0x00000007bdb80000) object space 68608K, 0% used [0x00000007b9200000,0x00000007b9200000,0x00000007bd500000) Metaspace used 5059K, capacity 5328K, committed 5504K, reserved 1056768K class space used 594K, capacity 627K, committed 640K, reserved 1048576K
Gc
设置值:100m = S0C+S1C+EC+OC = 4096 + 4096 + 25600 + 68608 = 102400 = 100m
-Xms:96M=S0C/S1c + EC + OC = 4096 + 25600 + 68608 = 98304 = 96M
- Virsualvm 插件:Visual GC 可显示GC情况
3 年轻代与老年代
Eden Survivor0 Survivor1
Eden 新创建对象
比例:默认 -XX:SurvivorRatio=8 8:1:1
不通过参数设置,并不满足8:1:1
新生代/老年代比例
默认:-XX:NewRatio=2 新生代占1,老年代占2,即新生代点堆1/3
-Xmn 设置新生代空间大小
-XX:MaxTenuringThreshold=15 回收15次后如果还存活,晋升老年代
4 图解对象分配过程
Edgn区为 近直角三角形 S0/S1 为 间隔梯形 老年区 阶梯形
5 MinorGC MajorGC FullGC
- 并非每次三个区域同时回收,大部分回收是新生代
- 新生代回收: Minor GC / Young GC
- 老年代回收: Major GC / Old GC
- 只有GMS GC 会单独收集老年代
- 很多时候 Major GC 和 Full GC混淆使用
- 混合回收:Mixed GC, 回收整个新生代及部分老年代
- 目前只有G1 GC存在
- 整堆回收: Full GC, 回收整个堆及方法区
触发机制
年轻代 Minor GC
- Eden区空间不足时触发, Survivro不会触发GC
- 非常频繁,但回收速度快
- 会引发STW
老年代 Major GC/Full GC
- 出现之前,经常出现一次MinorGC (但Parallel 收器不会,期直接进行Major GC)
- MajorGC速度一般比MinorGC慢10位以上,STW时间更长
- Major之前内存仍不足,OOM
Full GC触发:
- System.gc(),建议系统执行Full GC,但不必然执行
- 老年代空间不足
- 方法空间不足
- 通过MinorGC进入老年代大小,大于老年代可用内存
应尽量避免出现FullGC
6 堆空间分代思想
- 不同对象生命周期不一样,70-99% 对象是临时对象
- 优化GC性能
7 内存分配策略
优先分配Eden
大对象直接分配到老年代
长期存活的对象分配到老年代
动态对象年龄判断
- Survivor相同年龄对象的内存总和大于Survivor空间一半,年龄大于等于此年龄对象直接进入老年代,无需等到MaxTenuringThreshold中要求的年龄
DEMO直接分配对象到老年区
/** * 对象直接分配至老年代 * -Xms60m -Xmx60m -XX:NewRatio=2 -XX:+PrintGCDetails -XX:SurvivorRatio=8 * Eden S0 S1 OLD * 16 2 2 40 */ public static void main(String[] args) { // 20M byte[] bytes = new byte[20 * 1024 * 1024]; // Byte会出现Gc OOM 为啥? //Byte[] bytes = new Byte[20 * 1024 * 1024]; }
Heap PSYoungGen total 18432K, used 2299K [0x00000007bec00000, 0x00000007c0000000, 0x00000007c0000000) eden space 16384K, 14% used [0x00000007bec00000,0x00000007bee3ee60,0x00000007bfc00000) from space 2048K, 0% used [0x00000007bfe00000,0x00000007bfe00000,0x00000007c0000000) to space 2048K, 0% used [0x00000007bfc00000,0x00000007bfc00000,0x00000007bfe00000) ParOldGen total 40960K, used 20480K [0x00000007bc400000, 0x00000007bec00000, 0x00000007bec00000) object space 40960K, 50% used [0x00000007bc400000,0x00000007bd800010,0x00000007bec00000) Metaspace used 3133K, capacity 4496K, committed 4864K, reserved 1056768K class space used 345K, capacity 388K, committed 512K, reserved 1048576K
8 为对象分配内存:TLB
9 小结堆空间的参数设置
- -Xms60m -Xmx60m -XX:NewRatio=2 -XX:+PrintGCDetails -XX:SurvivorRatio=8
- -XX:+PrintFlagsInitial 查看所有参数默认初始值
- -XX:+PrintFlagsFinal 查看所有参数最终值
- -XX:+PrintGC 简化版GC信息
X 堆是分配对象的唯一选择吗?
逃逸分析
- jdk1.7后特性
- -XX:-DoEscapeAnalysis (-关闭 + 开启)
- 一个对象没有逃逸出方法的话,可能被优化成栈上分配
TaoBaoVM: GCIH(GC invisible heap)
- 生命周期较长的Java对象,移至堆外
- gc不管理GCIH内部对象
结论:
- 能使用局部变量的,就不要在方法定义
优化
栈上分配
package nohi.jvm._08_heap; import java.util.concurrent.TimeUnit; /** * <h3>thinkinjava</h3> * * @author NOHI * @description <p>演示逃逸分析、栈上分配</p> * @date 2022/11/12 23:05 **/ public class StackAllocation { /** * 演示逃逸分析、栈上分配 * 1. 分配足够大内存,不开启逃逸分析: 不产生GC,查看内存使用 * -Xms1G -Xmx1G -XX:-DoEscapeAnalysis -XX:NewRatio=2 -XX:+PrintGCDetails -XX:SurvivorRatio=8 * 2. 分配足够大内存,开启逃逸分析: 不产生GC,查看内存使用 * -Xms1-Xms100m -Xmx100m -XX:+DoEscapeAnalysis -XX:NewRatio=2 -XX:+PrintGCDetails -XX:SurvivorRatio=8G -Xmx1G -XX:+DoEscapeAnalysis -XX:NewRatio=2 -XX:+PrintGCDetails -XX:SurvivorRatio=8 * 3. 分配100m内存,不使用逃逸分析,产品GC * -Xms100m -Xmx100m -XX:-DoEscapeAnalysis -XX:NewRatio=2 -XX:+PrintGCDetails -XX:SurvivorRatio=8 * 4. 分配100内存,开启逃逸分析,不产生GC * @param args */ public static void main(String[] args) throws InterruptedException { StackAllocation s = new StackAllocation(); long start = System.currentTimeMillis(); for (int i = 0; i < 10000000; i++) { s.createUser(); } System.out.println("耗时:" + (System.currentTimeMillis() - start)); TimeUnit.MINUTES.sleep(5); } public void createUser(){ Test t = new Test(); } class Test{ } }
同步分配
如果一个对象被发现只能从一个线程被访问到,那么对这个对象的操作可以不考虑同步
同步代价高,降低并发性和性能
优化:同步省略/锁消除
public void f(){ Object hollis = new Object(); synchronized (hollis) { ..... } }
public void f(){ Object hollis = new Object(); //synchronized (hollis) { ..... //} }
标题替换
- 标量:无法再分解成更小的数据的数据。
- 正对而言的为聚合量,可以再分解
- -XX:+EliminateAllocations (默认打开)