JVM 内存模型
JVM 整体结构及内存模型
JVM 内存模型图涉及变量代码如下:
class Test {
public static Order order = new Order();
public int test() {
int a = 1;
int b = 2;
User user = new User();
int c = a + b;
return c;
}
public static void main(String[] args) {
new Test().test();
System.out.println("===test方法出口返回===");
}
}
class User {
}
class Order {
}
JVM 为为每个线程分配了独立的栈空间,程序计数器,本地方法栈。在一个线程内,方法的调用会给方法分配一块栈帧,每个栈帧都有独立的局部变量表,操作数栈,动态链接,方法出口(保证了方法内部局部变量的作用范围)
test 方法操作数栈变化:在局部变量表中分配给变量 a 内存空间,将整数 1 压入操作数栈,然后出栈赋值给变量 a,在给变量 b 分配内存空间,将整数 2 压入操作数栈,然后出栈赋值给变量 b,从局部变量表中找到 a 和 b 的值,存入操作数栈,进行整数 add 操作,得到结果压入操作数栈,出栈赋值给变量 c,从局部变量表得到 c 的值,压入操作数栈,执行 return 返回。
GC 垃圾回收机制
Eden 区满了之后会执行 minor GC 清理 Eden 区和非空的 Survivor 区,GC 之后将存活对象移至空的 Survivor 区,并将分代年龄+1,分代年龄到 15 之后会将存活对象存入年老代,当年老代满了之后会执行 Full GC 清理年轻代,年老代和元空间,如果 Full GC 之后没有清理出空间就会出现 OOM(内存溢出)。 JVM 会根据 GC Roots 查找所有依赖的对象直到最后一个没有依赖的对象为止,将这些对象标记为非垃圾对象。每次 GC 会伴随 STW 机制。
什么是 GC Roots
JVM 中判断一个对象是否标记为可回收的对象是根据可达性分析算法,顾名思义,可达性分析需要知道当前对象(是否需要回收的对象)的起点,而这个起点对象在当前时刻一定是存活的,才能保证对当前对象是否需要回收的判断是正确的,所以 GC Root 表示:当前时刻存活的对象。
GC Roots 包含以下几种:
- 方法中的局部变量
- 静态变量引用的对象
- 常量(被 final 修饰)
- 本地方法引用的对象
什么是 STW
Stop 一 the 一 World,简称 STW,指的是 Gc 事件发生过程中,会使得用户线程停止执行,产生应用程序的卡顿。
JVM 为什么会设计 STW 机制
在 GC 期间,如果用户线程不停止,可能刚刚根据 GC Roots 找到的非垃圾对象可能变成垃圾对象,使得 GC 未能回收。
JVM 内存参数设置
Spring Boot 程序的 JVM 参数设置格式(Tomcat 启动直接加在 bin 目录下 catalina.sh 文件里)
‐Xms2048M ‐Xmx2048M ‐Xmn1024M ‐Xss512K ‐XX:MetaspaceSize=256M ‐XX:MaxMetaspaceSize=256M
关于元空间的 JVM 参数有两个:-XX:MetaspaceSize=N 和-XX:MaxMetaspaceSize=N
-XX:MaxMetaspaceSize:设置元空间最大值,默认是-1,即不限制,或者说只受限于本地内存大小。
-XX:MetaspaceSize:指定元空间触发 Full GC 的初始阈值(元空间无固定初始大小),以字节为单位,默认是 21M,达到该值就会触发 Full GC 进行类型卸载,同时收集器会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过-XX:MaxMetaspaceSize(如果设置了的话)的情况下,
适当提高该值。这个跟早期 jdk 版本的-XX:PermSize 参数意思不一样,-XX:PermSize 代表永久代的初始容量。由于调整元空间的大小需要 Full GC,这是非常昂贵的操作,如果应用在启动的时候发生大量 Full GC,通常都是由于永久代或元空间发生了大小调整,基于这种情况,一般建议在 JVM 参数中将 MetaspaceSize 和 MaxMetaspaceSize 设置成一样的值,并设置得比初始值要大,对于 8G 物理内存的机器来说,一般设置为 256M。
StackOverFlowError
public class TestStackOverFlow {
static int i = 0;
public static void overflow() {
i++;
overflow();
}
public static void main(String[] args) {
overflow();
}
}
结论:-Xss设置越小,说明一个线程栈里能分配的栈帧就越少,但是对JVM整体来说能开启的线程数会更多。
JVM优化就是尽可能让对象都在新生代里分配和回收,尽量别让太多对象频繁进入老年代,避免频繁对老年代进行垃圾回收,同时给系统充足的内存大小,避免新生代频繁的进行垃圾回收。