1.新版JVM内存组成部分和堆空间分布
JVM内存的5大组成(基于JDK8的HotSpot虚拟机,不同虚拟机不同版本会有不一样)
名称 | 作用 | 特点 |
---|---|---|
程序计数器 | 也叫PC寄存器,用于记录当前线程执行的字节码指令位置,以便线程在恢复执行时能够从正确的位置开始 | 线程私有 |
Java虚拟机栈 | 用于存储Java方法执行过程中的局部变量、方法参数和返回值,以及方法执行时的操作数栈 | 线程私有 |
本地方法栈 | 用于存储Java程序调用本地方法的参数和返回值等信息。 | 线程私有 |
堆 | 用于存储Java程序创建的对象,所有线程共享一个堆,堆中的对象可以被垃圾回收器回收,以便为新的对象分配空间 | 线程共享 |
元数据区 | 用于存储类的元数据信息,如类名、方法名、字段名等,以及动态生成的代理类、动态生成的字节码等 元空间是位于本地(直接)内存中的,而不是像JDK8之前方法区位于堆内存中的。 | 线程共享 |
堆空间内存分布
- 用于存储Java程序创建的对象,所有线程共享一个堆
- 堆中的对象可以被垃圾回收器回收,以便为新的对象分配空间
2.JVM堆空间垃圾回收流程
(1)面试题:说下JVM里面堆内存划分和堆内存垃圾回收流程
- 新建对象,放到Eden区,满后触发Minor GC(每次都是由Eden区满触发Minor GC,接连放对象到S0或S1)
- 存活的对象移动到Survivor的S0区,如果S0满后触发Minor GC
- S0存活下来的对象移动到S1区,然后S0区空闲
- S1满后触发Minor GC,再次移动到S0区,然后S1区空闲
- 反复GC每次对象涨1岁,到达一定次数后(默认15),进入老年代
- 当老年代内存不足会触发Full GC,出现STW(Stop-The-World)
- 堆被垃圾回收,基本都是采用分代收集算法,不通区域的采用不同的垃圾回收算法
- 方法结束后,堆中的对象不会马上移除,在垃圾回收的时候才会被移除
(2)面试题:堆空间里面分配比例如何
官方推荐一般老年代与新生代的占比为2:1,即老年代占整个堆空间的2/3,新生代占整个堆空间的1/3,在Yong区又分三个区域 Eden、Survivor-0、Survivor-1,Eden分整个Yong的8/10,两个Survivor各占1/10。
3.JVM内存垃圾回收相关参数
(1)JVM参数格式分类
格式 | 解释 | 例子 |
---|---|---|
标准参数(-) | 所有JVM都实现这些参数的功能 | -verbose:gc 打印GC简要信息 |
非标准参数(-X) | 不保证所有JVM实现都满足 | -Xmx2048m等价 -XX:MaxHeapSize JVM最大堆内存为2048M |
非稳定参数(-XX) | 不稳定未来可能取消,但很有用 | -XX:+PrintGCDetails每次GC时打印详细信息。 |
-XX:+ | 开启对应的参数 | -XX:+PrintGCDetails 开启每次GC时打印详细信息。 |
-XX:- | 关闭对应的参数 | -XX:-DisableExplicitGC 禁止调用System.gc() |
-XX:= | 设定数字参数 | -XX:NewRatio=2 新生代和老年代内存比例 |
(2)JVM堆栈内存配置参数
参数 | 解释 |
---|---|
-Xms | 初始堆大小,推荐和最大堆一样 |
-Xmx | 最大堆大小,推荐和初始堆一样 |
-Xmn | 年轻代大小 |
-Xss | 每个线程的栈大小 |
(3)JVM常见的命令行参数配置
参数 | 解释 |
---|---|
-XX:+PrintGCDetails | 打印GC回收信息 |
-XX:NewRatio | 新生代和老年代空间大小的比率,由-XX:NewRatio 参数控制-XX:NewRatio 参数的默认值是2,表示新生代和老年代的比例是1:2如果将 -XX:NewRatio 设置为4,表示新生代和老年代的比例是1:4 |
-XX:MaxMetaspaceSize | 元空间所分配内存的最大值,默认没限制 |
-XX:+UseConcMarkSweepGC | 设置并发收集器 |
4.JVM虚拟机栈参数调整案例实战
JVM虚拟机栈
- 用来存储Java程序中的方法调用和局部变量的内存区域
- 每个线程都有自己的虚拟机栈,其生命周期与线程相同
- 当一个方法被调用时,Java虚拟机会在该线程的虚拟机栈中创建一个栈帧,用来存储该方法的局部变量、方法返回值等信息
- 异常情况
- 默认情况下,JVM虚拟机栈的大小是固定的,JDK1.5后通常为1MB
- 如果线程在执行方法时需要更多的栈空间,JVM会抛出StackOverflowError异常
- JVM参数
xss
,比如-Xss1m
表示1MB
(1)案例:模拟递归调用,对count一直++,直到栈溢出
public class StackFrameDemo {
private static int count = 0;
public static void main(String[] args) {
try {
recursiveMethod();
} catch (Throwable t) {
System.out.println("Stack overflow after " + count + " invocations.");
t.printStackTrace();
}
}
private static void recursiveMethod() {
count++;
recursiveMethod();
}
}
- 配置栈大小,最少208k,低于208k启动不起来项目,我们这块配置 524k,-Xss524k
- 再次测试
-
结论:
-
栈越小,递归调用的次数就越少,因为栈空间不足导致栈溢出异常
-
栈越大,递归调用的次数就越多,因为有足够的栈空间来存储方法调用的信息
-
5.JVM堆参数调整压测案例实战
- 需求
- 通过调整不同的JVM堆参数,查看相关指标
- 测试接口
@RestController
@RequestMapping("api/v1/data")
public class DataController {
@RequestMapping("compute")
public String compute() {
Byte[] b = new Byte[1024*1024];
return "success";
}
}
- JVM参数
-
调整参数一
- 参数
-Xms64m -Xmx64m
- 性能指标
- 参数
-
调整参数二
-
参数
-Xms640m -Xmx640m
-
性能指标
-
6.JDK8之后的方法区实现和元空间的联系
(1)什么是方法区和元空间
-
【方法区】是JVM中用来存储类的元数据信息的区域,包括类的结构、方法、字段信息等,Java堆类似各个线程共享的内存区域
-
元空间、永久代是方法区具体的落地实现
-
java8之前是称为永久代(PermGen),java8后引入的一个新概念【元空间】用于替代旧版JVM中的永久代(PermGen)
- 方法区和永久代以及元空间的关系很像 Java 中接口和类的关系
- 类实现了接口,类就可以看作是永久代和元空间,接口可以看作是方法区
- 永久代是 JDK 1.8 之前的方法区实现,JDK 1.8 及以后方法区的实现便成为元空间
-
-
元空间的大小是动态的,可以根据需要进行自动扩展,如果元空间不足,JVM会抛出 OutOfMemoryError : Metaspace
-
元空间大小配置
-
-XX:MetaspaceSize
- 用来设置元空间初始大小的参数,它的默认值是21 MB
-
-XX:MaxMetaspaceSize
- 用来设置元空间最大大小的参数,它的默认值是-1 即不限制,使用的是本地内存,不像旧版的永久代是堆内存
- 如果不限制元空间的大小,可能会导致元空间占用过多的内存,从而引起内存溢出
-
系统参数查看
- 这两个参数的单位是字节(B),可以使用K、M、G等后缀来表示更大的单位
-
public class HeapDemo {
public static void main(String[] args) throws InterruptedException {
System.out.println("测试元空间进程");
Thread.sleep(10000000);
}
}
- 查看命令
jps #查看进程号
jinfo -flag MetaspaceSize 进程号 #查看Metaspace分配内存空间
jinfo -flag MaxMetaspaceSize 进程号 #查看Metaspace最大空间
- 调整
-XX:MetaspaceSize=126m -XX:MaxMetaspaceSize=524m