堆
1.堆的核心概述
1.1堆存储内容
一个进程对应一个JVM
进程:只有一个方法区和堆;
线程:共享方法区和堆;
堆中TLAB每个线程都有一块,这块是私有的
在IDEA中设置堆空间大小:
运行JDK目录下的jvisualvm.exe
查看对空间设置大小
对象也可以在栈上分配;
栈里存储的是对象的引用地址值;
堆里存储的是对象;
类的结构存储在方法区中;
堆空间是垃圾回收时,才会清除对象;
1.2 堆的内存分配
JDK7 堆空间内存
JDK8的堆内存分配
目前设置参数只会影响到:新生代+老年代=堆分配的大小
而元空间是无法影响到的
JDK8之后元空间代替了永久代
设置IDEA打印堆空间信息
堆空间打印
而jdk1.7是永久代,不是元空间Metaspace
2.设置堆空间大小
-Xms设置堆起始内存大小;
-Xmx设置堆最大内存大小;
获得运行时方法区实例
查看进程使用内存
注意:
-Xms 600m -Xmx 600m
但真正能创建对象时,新生代只能是S0或S1中的一个;
所以最后堆内存为575m
同理:但真正能创建对象时,新生代只能是S0或S1中的一个;
3.OOM内存溢出
运行JDK目录下的jvisualvm.exe
可以看出OldMemory无法GC了,因为都满了
运行过程中监控:
4.年轻代与老年代
from和to区是来回切换的,当Survivor0满了,然后清空了就编程了to区;
4.1年轻代与老年代设置
查看设置情况:默认是1:2
EC:150M
S0C:25M
S1C:25M
而现实是自适应内存分配机制是1:1:6;若想1:1:8则需要显式指定
survivor超过一定时间后会移动到老年代
5.图解对象分配过程
总共有8个步骤;
红色的是垃圾了;age年龄计数器;
YGC后,那个Survival是空的就变成了to区;
那S0/S1中的age为15后,就晋升到老年代;
YGC触发条件是Eden区满了时(这时Survivor会一起被GC回收),但Survivor区满了时不会触发YGC;
6.对象分配的特殊情况
YGC之后:Eden区会把对象清空,或者把对象移动到Survivor区域;Eden是空的;
若这时年轻代放不下对象,这直接放入到老年代;
若老年代放不下,则老年代触发FullGC;
YGC是Eden空间不够时;survivor区域会被动被处理;这时会把survivor区域需要的保留的对象,移动到老年代;
7.代码举例与JVisualVM演示对象的分配过程
例子:
从图中看到,当Eden达到峰值时,触发YGC;这时把Eden的对象搬到Survivor中;Survivor满了后,搬到Old Gen中;
Metaspace元空间没什么变化;
8.JVM调优工具
JProfiler安装JProfiler v11.0.2
然后在IDEA中下载JProfiler插件;然后在IDEA中启动;
2.Minor GC、Major GC与Full GC
调优就是减少GC,应用程序的STW暂停,用户进程的干预;
Major GC与Full GC是Minor GC的10倍以上;所以重点调优这两个GC;
2.1三种GC方式
Major GC:只是老年代的垃圾收集
Full GC:收集三个区域;整个Java堆(年轻代,老年代)和方法区的垃圾收集
2.2最简单的分代式GC策略的触发条件
YGC触发条件是Eden满时;随便清理Survivor;而Survivor满了是不会触发YGC的;
YGC会引发STW,暂停其他用户线程;因为要标记哪些是垃圾;
threshold默认为15;
老年代GC
一般出现Major GC会伴随至少一次的Minor GC;但非绝对,在Parallel Scavenger收集器的收集策略里就直接进入Major GC
Full GC
Full GC是会触发三个区域;年轻代,老年代和方法区;
这时STW的时间会很长,可能是Minor GC的10倍。
例子:
设置参数
当出现OOM时,一般都会出现Full GC;因为只有Full GC后,老年代还不够,则OOM;
2.3堆空间分代思想
年轻代、老年代、永久代
年轻代、老年代、元空间
虚拟机的规范中方法区是堆中的一个逻辑部分,但是它却拥有一个叫做非堆(Non-Heap)的别名;
对于方法区的实现,不同虚拟机中策略也不同。HotSpot用永久代/元空间来实现的。
堆:年轻代(Eden、S0、S1)、老年代
元空间(MetaSpace):方法区的实现,堆的逻辑部分
1.存放常量池、方法元信息、Class类元信息
2.使用本地内存,非堆内存,但逻辑属于堆
是HotSpot虚拟机方法区的实现
2.4总结内存分配策略(对象提升Promote规则)
默认age15岁时会晋升到老年代;
注意:创建大对象,但却只使用一次;
例子:
NewRatio:老年代与新生代的比例;默认2:1
-Xmx60m 堆内存最大60m;新生代20m,老年代40m;
SurvivorRatio:新生代比例;默认是8:1:1;Eden 8 : S0 1 : S1 1
3.为堆分配TLAB
堆空间一定是线程共享的吗?
TLAB是线程私有的;不能共享;
为每个线程分配线程缓冲区;
默认是开启的 -XX:UseTLAB=true;
4.堆空间参数设置
若Eden设置的大,会造成minor GC 失去意义;
若Eden设置过小,则会频繁的YGC;
说明
HadnlePromotionFailure空间担保,在1.7之后就永远都是true了
5.堆是分配对象的唯一选择吗
6.逃逸分析
若何判断是否逃逸,就是看对象是否在外部被引用。
若对象声明为静态,只要外部使用,仍然会发生逃逸;
只要未发生逃逸,则对象就可以分配到栈上;
6.1 判断是否逃逸分析
一个对象的作用域只在本方法内:则未发生逃逸
返回String对象,在常量区,这样就不会逃逸了。
1.7以后默认开启逃逸分析;
查看本进程的JVM参数:
jinfo -flag xxxxx 进程PID
结论:开发中能使用局部变量的,就不要使用在方法外定义。这样可以方法栈空间了;
6.2代码优化
6.3栈上分配
例子:
-DoEscapeAnalysis 未开启逃逸分析;所有的对象在堆中进行分配;
+DoEscapeAnalysis 开启逃逸分析;
对象未到100万个;
所以逃逸分析好处:
1.分配对象在栈上,不会进行GC;
2.内存中不会维护过多的对象;
3.执行时间比较短;
6.4同步省略
编译的时候还会有同步代码块;但运行时会消除掉同步代码
6.5标量替换
逃逸分析是在服务器端才有的功能
对象还是分配在堆空间上