文章目录
JVM 学习路线图
堆的核心概念
JVM 参数 -Xms10m -Xmx10m
下面的容量相加为10M
VisualVM 工具
内存细分
堆空间大小的设置
/**
* 1. 设置堆空间大小的参数
* -Xms 用来设置堆空间(年轻代+老年代)的初始内存大小
* -X 是jvm的运行参数
* ms 是memory start
* -Xmx 用来设置堆空间(年轻代+老年代)的最大内存大小
*
* 2. 默认堆空间的大小
* 初始内存大小:物理电脑内存大小 / 64
* 最大内存大小:物理电脑内存大小 / 4
*
* 3. 手动设置:-Xms600m -Xmx600m
* 开发中建议将初始堆内存和最大的堆内存设置成相同的值。
*
* 4. 查看设置的参数:方式一: jps -l / jstat -gc 进程id
* 方式二:-XX:+PrintGCDetails
*
* @author shkstart shkstart@126.com
* @create 2020 20:15
*/
public class HeapSpaceInitial {
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");
System.out.println("系统内存大小为:" + initialMemory * 64.0 / 1024 + "G");
System.out.println("系统内存大小为:" + maxMemory * 4.0 / 1024 + "G");
}
}
java visualVM 观察OOM
/**
* -Xms600m -Xmx600m
* @author shkstart shkstart@126.com
* @create 2020 17:51
*/
public class HeapInstanceTest {
byte[] buffer = new byte[new Random().nextInt(1024 * 200)];
public static void main(String[] args) {
ArrayList<HeapInstanceTest> list = new ArrayList<HeapInstanceTest>();
while (true) {
list.add(new HeapInstanceTest());
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
VisualVM 工具查看堆内存变化
观察YGC和堆区内存占用情况,当 Old 区满时,Eden区再次满时,程序就会报错:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
JProfiler 11 工具查看堆内存情况
年轻代与老年代
jps -l 查询进程pid
jstat -gc pid 查看堆空间占用情况 (单位为字节,除以1024转为兆)
C : 代表总大小
U : 代表已使用大小
jinfo -flag NewRatio pid 查看 pid 的jvm参数 NewRatio 的值
默认情况下,新生代和老年代的比例为 1:2
例如:-Xmx=600M 则 新生代:200M ,老年代:400M,而新生代的分配比率为 8:1:1,则 S0:20M S1:20,Eden:160M
比率为8:1:1 需要配置参数为: -XX:SurvivorRatio=8
/**
* -Xms600m -Xmx600m
* <p>
* -XX:NewRatio : 设置新生代与老年代的比例。默认值是2.
* <p>
* -XX:SurvivorRatio :设置新生代中Eden区与Survivor区的比例 8:1:1。默认值是8
* -XX:-UseAdaptiveSizePolicy :关闭自适应的内存分配策略 (暂时用不到)
* <p>
* -Xmn:设置新生代的空间的大小。 (一般不设置)
*
* @author shkstart shkstart@126.com
* @create 2020 17:23
*/
public class EdenSurvivorTest {
public static void main(String[] args) {
System.out.println("我只是来打个酱油~");
try {
Thread.sleep(1000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
图解对象分配过程
当Eden区满时,则触发 YGC,而S0或S1 满时则不会触发 YGC
常用调优工具
MinorGc和MajorGc和FullGc
调优的重点就时减少GC的次数
分代式GC触发条件
/**
* 测试MinorGC 、 MajorGC、FullGC
* -Xms9m -Xmx9m -XX:+PrintGCDetails
*
* @author shkstart shkstart@126.com
* @create 2020 14:19
*/
public class GCTest {
public static void main(String[] args) {
int i = 0;
try {
List<String> list = new ArrayList<>();
String a = "atguigu.com";
while (true) {
list.add(a);
a = a + a;
i++;
}
} catch (Throwable t) {
t.printStackTrace();
System.out.println("遍历次数为:" + i);
}
}
}
产生GC日志和堆空间大小日志:-XX:+PrintGCDetails
堆分区分代思想
内存分配策略
空间分配担保:因为Survior区比较小,把Survior 区无法存放的对象,直接存放到老年代区,前提是老年代区可以容纳下该对象
/** 测试:大对象直接进入老年代
* -Xms60m -Xmx60m -XX:NewRatio=2 -XX:SurvivorRatio=8 -XX:+PrintGCDetails
* @author shkstart shkstart@126.com
* @create 2020 21:48
*/
public class YoungOldAreaTest {
public static void main(String[] args) {
byte[] buffer = new byte[1024 * 1024 * 20];//20m
}
}
Eden:16M ,S1:2M , S2:2M , Old:40M
没有打印GC日志,说明没有发生GC,对象直接进入老年代
对象分配过程:TLAB
/**
* 测试-XX:UseTLAB参数是否开启的情况:默认情况是开启的
*
* @author shkstart shkstart@126.com
* @create 2020 16:16
*/
public class TLABArgsTest {
public static void main(String[] args) {
System.out.println("我只是来打个酱油~");
try {
Thread.sleep(1000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
查看jvm 默认参数
堆空间常用的jvm参数
/**
* 测试堆空间常用的jvm参数:
* -XX:+PrintFlagsInitial : 查看所有的参数的默认初始值
* -XX:+PrintFlagsFinal :查看所有的参数的最终值(可能会存在修改,不再是初始值)
* -XX:+PrintFlagsFinal -XX:SurvivorRatio=5 //修改jvm参数
* 具体查看某个参数的指令: jps:查看当前运行中的进程
* jinfo -flag SurvivorRatio 进程id
*
* -Xms:初始堆空间内存 (默认为物理内存的1/64)
* -Xmx:最大堆空间内存(默认为物理内存的1/4)
* -Xmn:设置新生代的大小。(初始值及最大值)
* -XX:NewRatio:配置新生代与老年代在堆结构的占比
* -XX:SurvivorRatio:设置新生代中Eden和S0/S1空间的比例
* -XX:MaxTenuringThreshold:设置新生代垃圾的最大年龄
* -XX:+PrintGCDetails:输出详细的GC处理日志
* 打印gc简要信息:① -XX:+PrintGC ② -verbose:gc
* -XX:HandlePromotionFailure:是否设置空间分配担保
*
* @author shkstart shkstart@126.com
* @create 2020 17:18
*/
public class HeapArgsTest {
public static void main(String[] args) {
}
}
:= 5 :说明jvm参数值修改了
逃逸分析
/**
* 逃逸分析
*
* 如何快速的判断是否发生了逃逸分析,大家就看new的对象实体是否有可能在方法外被调用。
* @author shkstart
* @create 2020 下午 4:00
*/
public class EscapeAnalysis {
public EscapeAnalysis obj;
/*
方法返回EscapeAnalysis对象,发生逃逸
*/
public EscapeAnalysis getInstance(){
return obj == null? new EscapeAnalysis() : obj;
}
/*
为成员属性赋值,发生逃逸
*/
public void setObj(){
this.obj = new EscapeAnalysis();
}
//思考:如果当前的obj引用声明为static的?仍然会发生逃逸。
/*
对象的作用域仅在当前方法中有效,没有发生逃逸
*/
public void useEscapeAnalysis(){
EscapeAnalysis e = new EscapeAnalysis();
}
/*
引用成员变量的值,发生逃逸
*/
public void useEscapeAnalysis1(){
EscapeAnalysis e = getInstance();
//getInstance().xxx()同样会发生逃逸
}
}
注意:开发时能使用局部变量的,就不要使用在方法外定义
/**
* 栈上分配测试
* <p>
* 测试1
* 未开启逃逸分析: -Xmx1G -Xms1G -XX:-DoEscapeAnalysis -XX:+PrintGCDetails
* 开启逃逸分析: -Xmx1G -Xms1G -XX:+DoEscapeAnalysis -XX:+PrintGCDetails
* <p>
* 都没有产生gc日志,只是开启逃逸分析时,执行的比较快
* <p>
* 测试2
* 未开启逃逸分析: -Xmx256m -Xms256m -XX:-DoEscapeAnalysis -XX:+PrintGCDetails
* 产生了gc日志
* <p>
* 开启逃逸分析: -Xmx256m -Xms256m -XX:+DoEscapeAnalysis -XX:+PrintGCDetails
* 没有产生gc日志
*
* @author shkstart shkstart@126.com
* @create 2020 10:31
*/
public class StackAllocation {
public static void main(String[] args) {
long start = System.currentTimeMillis();
for (int i = 0; i < 10000000; i++) {
alloc();
}
// 查看执行时间
long end = System.currentTimeMillis();
System.out.println("花费的时间为: " + (end - start) + " ms");
// 为了方便查看堆内存中对象个数,线程sleep
try {
Thread.sleep(1000000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}
private static void alloc() {
User user = new User();//未发生逃逸
}
static class User {
}
}
未开启逃逸分析:执行结果:花费的时间为: 106 ms
内存中维护很多user 对象
开启逃逸分析:执行结果:花费的时间为: 7 ms
内存中没有维护很多user 对象
标量替换
/**
* 标量替换测试
* 未开启标量替换: -Xmx100m -Xms100m -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:-EliminateAllocations
* <p>
* 开启标量替换 : -Xmx100m -Xms100m -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:+EliminateAllocations
* 结论:开启标量替换时,花费的时间更少,没有产生gc
*
* @author shkstart shkstart@126.com
* @create 2020 12:01
*/
public class ScalarReplace {
public static class User {
public int id;
public String name;
}
public static void alloc() {
User u = new User();//未发生逃逸
u.id = 5;
u.name = "www.atguigu.com";
}
public static void main(String[] args) {
long start = System.currentTimeMillis();
for (int i = 0; i < 10000000; i++) {
alloc();
}
long end = System.currentTimeMillis();
System.out.println("花费的时间为: " + (end - start) + " ms");
}
}
jdk 64 位默认就是sever 模式