概述
- java进程:JVM进程=1:1;JVM进程:runtime实例=1:1;runtime实例:堆区=1:1
- JVM启动时创建并确定大小
- 堆区必须逻辑上连续
- 几乎所有的引用类型(数组,对象)都在堆区分配内存
- 堆区是GC的重点区域,GC时才会回收堆区中的对象
堆空间
分析
- 80%+的对象都在eden区死亡(朝生夕死)
GC
总述
分析
- 频繁在新生代收集,很少在老年代收集,几乎不在永久代/元空间收集
分类 | GC | 触发 | 介绍 |
---|---|---|---|
部分收集 | YGC(Young/Minor GC) | eden空间不足 | 收集新生代 |
部分收集 | MGC(Major/Old GC) | 老年代空间不足 | 收集老年代,限CMS GC |
部分收集 | Mixed GC | 收集整个新生代部分老年代,限G1 GC | |
全部收集 | Full GC | 老年代或永久代/元空间不足 | 新生代+老年代+永久代/元空间 |
YGC(Young/Minor GC)
分析
- eden空间不足
- YGC回收新生代(eden+survivor)中的垃圾
- 回收速度非常快
- STW(stop the world)停止用户线程,专注垃圾收集
- STW是否影响系统性能
- Survivor区满不会触发Minor GC
-XX:MaxTenuringThreshold=15
,默认15
MGC(Magor/Old GC)
分析
- MGC回收老年代中的垃圾
- 只有CMS GC会单独回收老年代
FGC(Full GC)
分析
- 老年代或永久代/元空间不足
- FGC回收新生代+老年代+永久代/元空间中的垃圾
- STW是否影响系统性能,FGC的STW时间是YGC的STW时间的10倍以上
- 尽量避免FGC
System.gc()
系统建议执行FGC,不一定会执行
分代GC算法
特殊晋升:
- eden区放不下
- 新建的超大对象,eden区放不下,经过YGC之后,eden区还是放不下,放到老年代中
- survivor区放不下
- eden区中存活的1个大对象大小>to区可用空间,放到老年代中
- eden区中大量存活对象总大小>to区可用空间,放到老年代中
正常晋升:
- 对象存活超过阈值(年龄计数器),
-XX:MaxTenuringThreshold=
,默认等于15,从eden到to区年龄计数器=1 - from区相同年龄的所有对象占用空间大小>from区的一半
晋升失败处理-XX:+HandlePromotionFailure
- 是否处理可能的晋升失败,从新生代到老年代的晋升失败
- 老年代连续空间size>新生代对象总size,不可能晋升失败
# jdk7=+
if (老年代连续空间size>新生代对象总size)||(老年代连续空间size>历史晋升平均size){
Minor GC
}else {
Full GC
}
# jdk6=-
if (老年代连续空间size>新生代对象总size)
Minor GC
}else if (HandlePromotionFailure=true) {
if (老年代最大可用连续空间>历史晋升到老年代对象的平均大小) {
Minor GC
}else {
Full GC
}
}else if (HandlePromotionFailure=false) {
Full GC
}
堆区大小
堆区
- 堆区大小=新生代+老年代=(伊甸区+幸存区)+老年代=(eden+s0+s1)+老年代
- 堆区能存储对象的大小=(eden+s0/s1)+老年代。因为,s0和s1使用的复制算法
- 堆区大小不包括永久代或元空间。分代收集算法中才设计永久代或元空间
参数: -Xms
(堆区初始内存)-Xmx
(堆区最大内存)
默认:
-Xms=物理内存大小/64
-Xmx=物理内存大小/4
过程:
- 扩容:堆区内存先分配-Xms(堆区初始内存)》不够》扩容直到-Xmx(堆区最大内存)
- 缩容:扩容后的堆区内存》存在空闲》缩容直到-Xms(堆区初始内存)
OOM(OutOfMemoryError):
-
-Xmx指定内存
设置:
-Xms=-Xmx
- 目的:频繁的扩容和缩容会影响性能
TLAB(Thread Local Allocation Buffer)
总述
- 多线程情况下直接中堆区中划分内存空间需要加锁,否则线程不安全,加锁影响分配速度
- JVM为每个线程分配1个私有的缓存区域TLAB
- TLAB位于Eden空间中
- 线程用完了自己私有的TLAB再通过加锁使用Eden中公共的区域
逃逸分析
总述
- HotSpot并没有实现栈上分配,实现了标量替换
- jvm以server模式运行,才有逃逸分析,64位JVM默认是server模式
- 逃逸分析是JIT(即时编译器)中优化技术之一
- 动态分析对象的作用域,分析对象引用的使用范围
- 方法逃逸:当一个对象在方法中定义之后,作为参数传递到其它方法中
- 线程逃逸:如类变量或实例变量,可能被其它线程访问到
- 逃逸分析是“栈上分配”,“同步省略”,“标量替换”的基础
- 标量替换为栈上分配提供了很好的基础
标量替换
总述
- 将对象替换为对象的成员属性
- 标量:无法再分的最小数据量,基本类型成员属性就是标量
- 聚合量:对象就是聚合量
栈上分配
总述
- 经过“逃逸分析”的没有逃逸的对象,可以直接上栈上分配
- 没有GC,因为是栈上分配的对象
同步省略
总述
- 如果1个对象只能被1个线程访问,该对象的同步操作会被省略
反编译
设置堆大小
编译
javac -d D:\workspace\idea\JVMDemo\blog\target\classes\ VMHeapSize.java
运行
java -Xmx10m -Xms10m -classpath D:\workspace\idea\JVMDemo\blog\target\classes\ xcrj.VMHeapSize
结果
- java virtual vm在jdk/bin目录下
结果virtual GC
- 红框加起来等于10M
对象和数组位置
代码
package xcrj;
public class VMHeapLocation {
public static void main(String[] args) {
VMHeapLocation vmHeap1 = new VMHeapLocation();
int[] intArr = new int[10];
}
}
jclasslib
- 创建对象实例:new
- 创建数组实例:newarray
堆内存大小代码分析
代码
package xcrj;
/* * 先设置-Xms500m -Xmx500m * */
public class VMHeapSpaceSize {
public static void main(String[] args) {
//返回java虚拟机中的堆内存总量,开始时totalMemory=-Xms
long mUnit = 1024 * 1024;
long initialMemory = Runtime.getRuntime().totalMemory() / mUnit;
System.out.println("-Xms=" + initialMemory + "M");
//返回java虚拟机试图使用的最大堆内存,maxMemory=-Xmx
long maxMemory = Runtime.getRuntime().maxMemory() / mUnit;
System.out.println("-Xmx=" + maxMemory + "M");
// 64.0和4.0目的是转为浮点数
System.out.println("系统内存大小为: " + initialMemory * 64.0 / 1024 + "G");
System.out.println("系统内存大小为: " + maxMemory * 4.0 / 1024 + "G");
try {
Thread.sleep(2000000);
} catch (InterruptedException ie) {
ie.printStackTrace();
}
}
}
结果
# 编译
javac -d D:\workspace\idea\JVMDemo\blog\target\classes\ -encoding UTF-8 VMHeapSpaceSize.java
# 运行
java -Xmx500m -Xms500m -classpath D:\workspace\idea\JVMDemo\blog\target\classes\ xcrj.VMHeapSpaceSize
分析
- 设置的500m并没有,代码打印的结果为479M<500M
- 堆区能存储对象的大小=(eden+s0/s1)+老年代。因为,s0和s1使用的复制算法
- 系统内存大小也并不符合作者实际情况
堆内存大小命令分析
java命令执行同上
命令行查看
# 查看在运行的java进程
jps
# 查看java进程内存情况
jstat -gc java进程id号
分析
- E:eden;S:survivor;U:used;C:count;O:old
- 堆空间大小=新生代+老年代=(EC+S0C+S1C)+OC=500M
- 堆空间能够存储对象大小=(EC+S0C)+OC=(EC+S1C)+OC=479M
OOM
java
package xcrj;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/* * 先设置 -Xms=10m -Xmx=10m * */
public class VMHeapOOM {
public static void main(String[] args) {
List<Image> list = new ArrayList<>();
while (true) {
try {
Thread.sleep(10);
} catch (Exception e) {
}
int value = new Random().nextInt(1024 * 1024);
Image img = new Image(value);
list.add(img);
}
}
}
class Image {
private byte[] pixels;
public Image(int len) {
this.pixels = new byte[len];
}
}
OOM结果
java virsualVM/抽样器
- 发现byte[]对象占用了绝大部分内存
java virsualVM/Visual GC
-XX:NewRatio
代码
package xcrj;
/* * 先设置-Xms500m -Xmx500m * */
public class VMHeapSpaceSize {
public static void main(String[] args) {
//返回java虚拟机中的堆内存总量,开始时totalMemory=-Xms
long mUnit = 1024 * 1024;
long initialMemory = Runtime.getRuntime().totalMemory() / mUnit;
System.out.println("-Xms=" + initialMemory + "M");
//返回java虚拟机试图使用的最大堆内存,maxMemory=-Xmx
long maxMemory = Runtime.getRuntime().maxMemory() / mUnit;
System.out.println("-Xmx=" + maxMemory + "M");
// 64.0和4.0目的是转为浮点数
System.out.println("系统内存大小为: " + initialMemory * 64.0 / 1024 + "G");
System.out.println("系统内存大小为: " + maxMemory * 4.0 / 1024 + "G");
try {
Thread.sleep(2000000);
} catch (InterruptedException ie) {
ie.printStackTrace();
}
}
}
jstat结果
分析
- 老年代:新生代=3
- OC:(EC+S0C+S1C)=3
jinfo结果
# 查看JVM参数值
jinfo -flag NewRatio java进程id号
-XX:SurvivorRatio
分析
- 默认,具有自适应机制
强制设置为7:1:1
java -Xmx500m -Xms500m -XX:SurvivorRatio=7 -classpath D:\workspace\idea\JVMDemo\blog\target\classes\ xcrj.VMHeapSpace
结果
-Xmn100m -XX:NewRatio=3
总述
-Xmn100m -XX:NewRatio=3
一起设置,-XX:NewRatio=3
失效
指令
java -Xmx600m -Xms600m -Xmn100m -XX:NewRatio=3 -classpath D:\workspace\idea\JVMDemo\blog\target\classes\ xcrj.VMHeapSpaceSize
结果
- 发现老年代:新生代=OC:(EC+S0C+S1C)!=3:1
-XX:NewRatio=3
失效
-XX:+PrintGCDetails
代码
package xcrj;
import java.util.ArrayList;
import java.util.List;
public class VMHeapGCDetail {
public static void main(String[] args) {
try {
List<String> list = new ArrayList<>();
String str = "maybe you are a dream";
do {
list.add(str);
str += str;
} while (true);
// OutOfMemoryError继承Error继承Throwable
} catch (Throwable t) {
t.printStackTrace();
}
}
}
命令
# 编译
javac -d D:\workspace\idea\JVMDemo\blog\target\classes\ -encoding UTF-8 VMHeapGCDetail.java
# 运行
java -Xmx6m -Xms6m -XX:+PrintGCDetails -classpath D:\workspace\idea\JVMDemo\blog\target\classes\ xcrj.VMHeapGCDetail
结果
[GC (Allocation Failure) [PSYoungGen: 1008K->498K(1536K)] 1008K->722K(5632K), 0.0526596 secs] [Times: user=0.00 sys=0.00, real=0.05 secs]
[GC (Allocation Failure) [PSYoungGen: 1454K->496K(1536K)] 1678K->1173K(5632K), 0.0017728 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 1140K->504K(1536K)] 1817K->1597K(5632K), 0.0012724 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Ergonomics) [PSYoungGen: 1356K->0K(1536K)] [ParOldGen: 3589K->3162K(4096K)] 4945K->3162K(5632K), [Metaspace: 2589K->2589K(1056768K)], 0.0287266 secs] [Times: user=0.01 sys=0.00, real=0.03 secs]
[GC (Allocation Failure) [PSYoungGen: 0K->0K(1536K)] 3162K->3162K(5632K), 0.0021192 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(1536K)] [ParOldGen: 3162K->3118K(4096K)] 3162K->3118K(5632K), [Metaspace: 2589K->2589K(1056768K)], 0.0096239 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
java.lang.OutOfMemoryError: Java heap space
分析
- 抛出OutOfMemoryError之前有一次Full GC;可知执行一次FGC收集了PSYoungGen,ParOldGen和Metaspace中的垃圾
[GC (Allocation Failure) [PSYoungGen: 1008K->498K(1536K)] 1008K->722K(5632K), 0.0526596 secs] [Times: user=0.00 sys=0.00, real=0.05 secs]
:1008K,YGC调用之前新生代大小;498K,YGC调用之后剩余新生代大小;1536K,新生代总大小;722K,YGC调用之后剩余堆内存大小;5632K,堆内存总大小,大约6M;0.0526596 secs,YGC耗时时间,单位s;Times: user=0.00 sys=0.00, real=0.05 secs
分别为YGC用户耗时时间、系统耗时时间、实际耗时时间, 单位为秒
eden超大对象
代码
package xcrj;
/* * -Xms30m -Xmx30m -XX:NewRatio=2 -XX:+PrintGCDetails * 老年代=20M * 新生代=10M * */
public class VMHeapEdenBIgObj {
public static void main(String[] args) {
//10M大小对象
byte[] m10Bytes = new byte[1024 * 1024 * 10];
}
}
结果
分析
- 新生代=eden+from+to=10240K=10M
- 老年代=20480K=20M
- 创建的大对象m10Bytes=10M>eden的8192K,放到老年代
-XX:+PrintFlagsInitial
结果-默认初始值
java -Xms30m -Xmx30m -XX:+PrintFlagsInitial -classpath D:\workspace\idea\JVMDemo\blog\target\classes\ xcrj.VMHeapEdenBIgObj
结果-赋值初始值
java -Xms30m -Xmx30m -XX:NewRatio=3 -XX:+PrintFlagsInitial -classpath D:\workspace\idea\JVMDemo\blog\target\classes\ xcrj.VMHeapEdenBIgObj
-XX:+PrintFlagsFinal
指令
java -Xms30m -Xmx30m -XX:NewRatio=3 -XX:+PrintGCDetails -XX:+PrintFlagsFinal -classpath D:\workspace\idea\JVMDemo\blog\target\classes\ xcrj.VMHeapEdenBIgObj
结果
HotSpot标量替换
代码
package xcrj;
/* * 标量替换 * 先:关闭标量替换 * -Xms100m -Xmx100m -XX:+DoEscapeAnalysis -XX:-EliminateAllocations -XX:+PrintGC * 再:打开标量替换,默认打开 * -Xms100m -Xmx100m -XX:+DoEscapeAnalysis -XX:+EliminateAllocations -XX:+PrintGC * */
public class VMHeapScalarReplace {
/* * user没有发生逃逸,可以进行标量替换 * */
public static void createUser() {
User user = new User("xcrj", 20);
}
public static void main(String[] args) {
long startTime = System.currentTimeMillis();
int w200 = 20000000;
for (int i = 0; i < w200; i++) {
createUser();
}
long endTime = System.currentTimeMillis();
System.out.println("花费时间:" + (endTime - startTime) + "ms");
try {
Thread.sleep(1000000);
} catch (InterruptedException ie) {
ie.printStackTrace();
}
}
}
class User {
private String name;
private int age;
public User() {
}
public User(String name, int age) {
this.name = name;
this.age = age;
}
}
# jvm以server模式运行,才有逃逸分析,64位JVM默认是server模式
java -version
# 逃逸分析是标量替换的基础
# 关闭标量替换
java -Xms2G -Xmx2G -XX:+DoEscapeAnalysis -XX:-EliminateAllocations -XX:+PrintGCDetails -classpath D:\workspace\idea\JVMDemo\blog\target\classes\ xcrj.VMHeapScalarReplace
# 开启标量替换
java -Xms2G -Xmx2G -XX:+DoEscapeAnalysis -XX:+EliminateAllocations -XX:+PrintGCDetails -classpath D:\workspace\idea\JVMDemo\blog\target\classes\ xcrj.VMHeapScalarReplace
结果--server
模式
- 可以看到是server模式
结果--XX:-EliminateAllocations
,关闭标量替换
- 创建的200w个对象都在堆中
结果--XX:+EliminateAllocations
,开启标量替换,默认开启
- 开启标量替换之后,发现堆中的对象少了很多,只有71901个了
结果--XX:PrintGCDetails -XX:-EliminateAllocations
,关闭标量替换
- YGC调用之前,新生代使用了503809K
结果--XX:PrintGCDetails -XX:+EliminateAllocations
,开启标量替换,默认开启
- YGC调用之前,新生代使用了41984K 远远小于503809K,标量替换进行了优化
调优
参数
分类 | 参数 | 默认 | 作用 | 建议 |
---|---|---|---|---|
堆空间大小 | -Xms10m | 物理内存大小/64 | min堆大小 | 是 |
堆空间大小 | -Xmx10m | 物理内存大小/4 | max堆大小 | 是 |
新生代大小 | -Xmn10m | 新生代内存大小,和-XX:NewRatio=2一起设置,这个参数失效 | 否 | |
老年代:新生代 | -XX:NewRatio=2 | 2 | 老年代:新生代=2 | 否 |
eden:survivor | -XX:SurvivorRatio=8 | 自适应 | eden:s0:s1=8:1:1 | 否 |
新生代自适应 | -XX:+UseAdaptiveSizePolicy | 开启 | +号开启自适应,-号关闭自适应 | 是。大流量、低延迟系统 建议关闭 |
年龄计数器 | -XX:MaxTenuringThreshold=15 | 15 | 年龄计数器,控制survivor区到老年代 | 否 |
-XX:+HandlePromotionFailure | 开启 | 开启非安全转移 | 否 | |
TLAB启用 | -XX:+UseTLAB | 开启 | +号开启TLAB | 否 |
TLAB/Eden | -XX:TLABWasteTargetPercent | Eden的1% | TLAB占用Eden百分比 | 否 |
参数值 | -XX:+PrintFlagsInitial | 所有参数的默认初始值 | 否 | |
参数值 | -XX:+PrintFlagsFinal | 所有参数的最终值,赋值值 | 否 | |
GC | -XX:+PrintGCDetails | 打印GC细节,常用 | 否 | |
GC | -XX:+PrintGC | 打印GC简略信息 | 否 | |
GC | -verbose:gc | 打印GC简略信息 | 否 | |
逃逸分析 | -XX:+DoEscapeAnalysis | jdk6u23=+默认开启 | 逃逸分析 | 否 |
逃逸分析 | -XX:+PrintEscapeAnalysis | 打印逃逸分析筛选结果 | 否 | |
标量替换 | -XX:+EliminateAllocations | 开启 | 标量替换 | 否 |
JVM运行模式 | -server | 64位jvm默认server模式 | server模式,server模式下才有逃逸分析 | 否 |
指令
指令 | 作用 |
---|---|
javac -d D:\workspace\idea\JVMDemo\blog\target\classes\ -encoding UTF-8 VMHeapScalarReplace.java | 编译 |
java -Xms100m -Xmx100m -classpath D:\workspace\idea\JVMDemo\blog\target\classes\ xcrj.VMHeapScalarReplace | 运行 |
javap -classpath D:\workspace\idea\JVMDemo\blog\target\classes\ -v xcrj.VMHeapScalarReplace | 反编译 |
jps | 查看在运行的java进程 |
jstat -gc java进程id号 | 查看java进程GC内存情况 |
jinfo -flag NewRatio java进程id号 | 查看JVM参数值 |
jmap -histo [pid] | 查看堆中对象个数,占用内存byte,柱状图(histogram) |
javap -classpath D:\workspace\idea\JVMDemo\blog\target\classes\ -v xcrj.VMHeapScalarReplace
-Xms=-Xmx
设置:
-Xms=-Xmx
- 目的:频繁的扩容和缩容会影响性能
今天的文章JVM内存与垃圾回收-3-运行时数据区/堆区分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/65831.html