关于JVM整体结构


在这里插入图片描述

一、线程栈:

以Math类为例:

public class Math {
	public static int num = 123;
    public static User user = new User();

    public int compute() {
        int a = 1;
        int b = 2;
        int c = (a + b) * 10;
        return c;
    }

    public static void main(String[] args) {
    	Math math = new Math();
        math.compute();
    	System.out.println("test");
    }
}

当运行main方法时便会启动主线程,此时会在栈内开辟一块独立内存空间供其使用,即线程栈,用来存放内部局部变量。

每启动一个线程便会开辟一块线程栈,线程栈之间互相独立,存放各自内部的局部变量

main方法在线程栈内又开辟一块专属的内存空间,即main方法的栈帧,main方法里的math变量存于其中;
接着执行compute方法时,会在main栈帧上方开辟一块compute方法的栈帧,存放compute方法里的a、b、c变量。

一个方法对应一个栈帧,后生成的栈帧会在先生成的栈帧上方,不断叠加上去,方法执行完成后出栈,即销毁对应栈帧,栈帧为先进后出,后进栈的compute方法会先执行完成

在这里插入图片描述

生成反汇编文件的指令(JVM内部的汇编语言):javap -c Math.class > Math.txt

Compiled from "Math.java"
public class com.autumn.jvm.Math {
  public static int num;

  public static com.autumn.jvm.User user;

  public com.autumn.jvm.Math();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public int compute();
    Code:
       //将int类型常量1压入操作数栈
       0: iconst_1
       //将int类型值存入局部变量1(局部变量0为this)
       1: istore_1
       //将int类型常量2压入操作数栈
       2: iconst_2
       //将int类型值存入局部变量2
       3: istore_2
       //从局部变量1中装载int类型值
       4: iload_1
       //从局部变量2中装载int类型值
       5: iload_2
       //执行int类型的加法
       6: iadd
       //将一个8位带符号整数压入栈(即将10压入栈)
       7: bipush        10
       //行号即为内存地址的映射值,上方的 10 映射到行号8,因此接下去从9继续
       //执行int类型的乘法
       9: imul
      //将int类型值存入局部变量3
      10: istore_3
      //从局部变量3中装载int类型值
      11: iload_3
      // 从方法中返回int类型的数据
      12: ireturn

  public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class com/autumn/jvm/Math
       3: dup
       4: invokespecial #3                  // Method "<init>":()V
       7: astore_1
       8: aload_1
       9: invokevirtual #4                  // Method compute:()I
      12: pop
      13: return

  static {};
    Code:
       0: bipush        123
       2: putstatic     #5                  // Field num:I
       5: new           #6                  // class com/autumn/jvm/User
       8: dup
       9: invokespecial #7                  // Method com/autumn/jvm/User."<init>":()V
      12: putstatic     #8                  // Field user:Lcom/autumn/jvm/User;
      15: return
}

需对照JVM指令手册查看
在这里插入图片描述

局部变量表:

存放方法内部的局部变量。

操作数栈:

存放具体的值,即字面量。

动态链接:

当程序执行到符号时(例如compute方法),会将compute方法的符号引用替换成直接引用,即指向compute方法内部的代码指令加载到方法区的常量池内所生成的内存地址。

方法出口:

存放方法结束位置的信息,当方法结束之后根据此信息继续往下执行代码。例如compute方法执行完毕之后会找到结束位置,继续往下执行main方法。

二、程序计数器

程序计数器是每个线程独有的,记录运行代码的内存位置,也称为行号。
运行过程中由字节码执行引擎进行修改。
当线程被挂起再恢复时,便可根据程序计数器的记录来让程序接着从中断处继续执行下去。

三、方法区

Math.class最终加载到方法区,以元素信息形式存放在方法区内,由字节码执行引擎进行执行。

四、本地方法栈

由C/C++实现的方法,即native修饰的方法。
在这里插入图片描述

五、堆

在这里插入图片描述

分为年轻代和老年代,比例为1:2。
年轻代分为Eden区和Survivor区,Survivor区又分为Survivor0区和Survivor1区,三者比例为8:1:1。
新创建的对象一般存于Eden,当Eden剩余空间不足以存放新的对象时,会触发minor gc,由字节码执行引擎在后台开启垃圾收集线程进行垃圾收集。
首先会将线程栈里为对象的局部变量和方法区里为对象的静态变量作为gc roots,逐级去寻找其引用对象,凡是有被引用的对象都会被标记为非垃圾对象,存入到S0,且分代年龄加1,而没被标记的为垃圾对象直接清理。
当Eden再次无足够剩余空间存放新的对象时又会触发minor gc,会回收Eden和S0,将存活的对象放入到S1,分代年龄加1,垃圾对象被清理。
每次触发minor gc,会将存活对象反复在S0到S1进行来回存放,并使分代年龄加1,直到分代年龄达到15后仍然存活,便会被存入老年代,例如静态变量、对象池、缓存对象、spring容器对象等。
当老年代剩余空间不够存放新的对象时,触发full gc,回收整个堆以及方法区。若老年代的gc roots仍旧被引用而无法回收,仍无多余空间,则引发内存溢出(OOM)

启动JDK自带的JVM调优诊断工具指令:jvisualvm

启动程序之后,双击查看对应线程,点击标题栏Profiler后面的Visual GC即可查看GC运行过程。
若没有Visual GC则选择工具 → 插件 → 可用插件 → 选择Visual GC进行安装即可。

在执行gc时,会触发STW(Stop The World),即停止用户线程,用户线程例如用户操作的下单,添加购物车等,若STW时间过长,会影响性能,让用户觉得系统卡顿。
因此JVM调优的主要目的就是减少gc,主要减少full gc,因为其回收时间较长,导致STW时间较长。
应该减少full gc的次数,或一次full gc的执行时间,也应该减少minor gc次数,不过minor gc触发的STW时间极短。

六、JVM内存参数设置

在这里插入图片描述
SpringBoot程序的JVM参数设置通用配置(若Tomcat启动直接加在bin目录下的catalina.sh):

java ‐Xms2048M ‐Xmx2048M ‐Xmn1024M ‐Xss512K ‐XX:MetaspaceSize=256M ‐XX:MaxMetaspaceSize=256M ‐jar microservice‐eureka‐server.jar

元空间(JDK1.8之前称为永久代)的JVM参数有两个:
-XX:MaxMetaspaceSize:
设置元空间最大值,默认为-1,即不限制,与本地内存大小一致

-XX:MetaspaceSize:
指定元空间触发full gc的初始阈值(非元空间大小,元空间无初始大小),以字节为单位,默认为21M
元空间一旦达到21M便会触发full gc,同时垃圾收集器会对该值进行动态调整:若释放大量空间,则适当降低该值;若释放少量空间,则在不超过-XX:MaxMetaspaceSize的情况下,适当提高该值。
与早期JDK版本的-XX:PermSize参数意思不同,-XX:PermSize代表永久代的初始容量。

由于调整元空间的大小需要full gc,此操作非常消耗内存,若在应用程序启动时发生大量full gc,通常都是由于永久代或元空间发生了大小调整,因此一般建议在JVM参数中将MaxMetaspaceSize和MetaspaceSize设置成一样的值,并设置得比初始值大。 例如8G物理内存,建议设置为256M。

StackOverflowError示例:
public class StackOverflowTest {
    // JVM设置
    // -Xss:128(-Xss默认1M)
    static int count = 0;

    static void redo() {
        count++;
        redo();
    }

    public static void main(String[] args) {
        try {
            redo();
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("count:" + count);
        }
    }
}

在启动配置里添加参数,若没有VM options,则点击Modefy options → Add VM options,比较加参数前后的运行结果。

运行结论: -Xss设置得越小,count就越小。说明一个线程栈可分配栈帧越少,但对JVM整体来说可开启的线程数会越多

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值