02-JVM内存模型深度剖析与优化

一、JDK体系结构

在这里插入图片描述


JDK: JDK提供了编译、运行Java程序所需的各种资源和工具;包括Java编译器,Java运行时环境【JRE】;开发工具包括编译工具(javac.exe) 打包工具(jar.exe)等。
JRE: 即JAVA运行时环境,JVM就是包括在JRE中,以及常用的JAVA类库等;
SDK: SDK是基于JDK进行扩展的,是解决企业级开发的工具包。如JSP、JDBC、EJB等就是由SDK提供的 ;
JVM(Java Virtual Machine),Java虚拟机,可以看做是一台抽象化的计算机,它有一套完整的体系架构,包括处理器、堆栈 、寄存器等。
在运行时环境,JVM会将Java字节码解释成机器码。机器码和平台相关的(不同硬件环境、不同操作系统,产生的机器码不同),所以JVM在不同平台有不同的实现。
目前JDK默认使用的实现是Hotspot VM。

二、Java语言的跨平台特性

在这里插入图片描述


一次编译,到处执行(Write Once ,Run Anywhere)。
用Java创建的可执行二进制程序,能够不加改变的运行于多个平台。从软件层面屏蔽不同操作系统底层硬件与指令上的区别

三、JVM整体结构及内存模型

3.1 内存模型

官方文档参考:[https://doc](https://doc)s.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.5

Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为个不同的数据区。这些区域有各自的用途,以及创建和销毁事件。
JVM用来存储加载的类信息、常量、静态变量、编译后的代码等数据。

在这里插入图片描述

PC寄存器(线程私有)

PC寄存器,也叫程序计数器。JVM支持多个线程同时运行,每个线程都有自己的程序计数器。倘若当前执行的是JVM方法,则该寄存器中保存当前执行指令的地址;倘若执行的是native方法,则PC寄存器为空。
这个内存区域是唯一一个在虚拟机中没有规定任何OutOfMemoryError情况的区域。

虚拟机栈(线程私有)

每个线程有一个私有的栈,随着线程的创建而创建。栈里面存放着一种叫做“栈帧”的东西,每个方法在执行的时候会创建一个栈帧,存储了局部变量表(基本数据类型和对象引用),操作数栈,动态连接,方法出口等信息。
每个方法从调用到执行完毕,对应一个栈帧在虚拟机栈中的入栈和出栈。
(方法中的局部变量的空间可以进行释放)
通常所说的栈,一般是指虚拟机栈中的局部变量表部分。局部变量表所需的内存在编译期间完成分配。
栈的大小可以固定也可以动态扩展,当扩展到无法申请足够的内存,则OutOfMemoryError。
当栈调用深度大于JVM所允许的范围,会抛出StackOverflowError的错误
演示栈帧:

public class StackTest {

    public int method2(){
        int a=1;
        int b=2;
        int c=a+b;
        return c;
    }

    public int method1(){
       return method2();
    }


    public static void main(String[] args) {
        StackTest stackTest = new StackTest();
        int i = stackTest.method1();
        System.out.println(i);
    }
}

本地方法栈(线程私有)

和虚拟机栈类似,主要为虚拟机使用到的Native方法服务。 也会抛出StackOverflowError和OutOfMemoryError。

堆(线程共享)

堆内存是JVM所有线程共享的部分,在虚拟机启动的时候就已经创建。
和程序开发密切相关,应用系统对象都保存在Java堆中。所有的对象和数组都在堆上进行分配。这部分空间可通过GC进行回收。对分代GC来说,堆也是分代的,是GC的主要工作区间。当申请不到空间时,会抛出OutOfMemoryError。
演示内存溢出:

public class HeapOutOfMemoryErrorTest {
    byte[] arr = new byte[1024 * 1000];//1M

    public static void main(String[] args) throws InterruptedException {
        ArrayList<HeapTest> list = new ArrayList<>();
        while (true) {
            list.add(new HeapTest());
        }
    }
}

结果:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at com.zengqingfa.exercise.jvm.HeapTest.<init>(HeapTest.java:13)
	at com.zengqingfa.exercise.jvm.HeapOutOfMemoryErrorTest.main(HeapOutOfMemoryErrorTest.java:17)

方法区(线程共享)

方法区也是所有线程共享的。主要用于存储类的信息、常量池、方法数据、方法代码等。方法区逻辑上属于堆的一部分,但是为了与堆进行区分,通常又叫“非堆”。
这个区域的内存回收目标主要针对常量池的回收和对类型的卸载。
当方法区无法满足内存分配需求时,则抛出OutOfMemoryError异常。
在HotSpot虚拟机中,用永久代来实现方法区,将GC分代收集扩展至方法区,但是这样容易遇到内存溢出的问题。
JDK1.7中,已经把放在永久代的字符串常量池移到堆中。
JDK1.8撤销永久代,引入元空间。

运行时常量池

运行时常量池就是将编译后的类信息放入方法区中,也就是说它是方法区的一部分。** 运行时常量池用来动态获取类信息**,包括:class文件元信息描述、编译后的代码数据、引用类型数据、类文件常量池等。 运行时常量池是在类加载完成之后,将每个class常量池中的符号引用值转存到运行时常量池中。

在这里插入图片描述

3.2 演示

堆内存回收

堆内存的回收过程,使用jvisualvm工具

public class HeapTest {

    byte[] arr = new byte[1024 * 100];//100kb

    public static void main(String[] args) throws InterruptedException {
        ArrayList<HeapTest> list = new ArrayList<>();
        while (true) {
            list.add(new HeapTest());
            Thread.sleep(10);
        }
    }
}

使用命令行:

jvisualvm

图形界面:

在这里插入图片描述


没有VisualGC界面,可以使用插件,下载即可,重启

在这里插入图片描述

启动程序,查看程序的运行过程:

在这里插入图片描述

四、jvm内存参数设值

在这里插入图片描述


Spring Boot程序的JVM参数设置格式(Tomcat启动直接加在bin目录下catalina.sh文件里):

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

关于元空间的JVM参数有两个:-XX:MetaspaceSize=N和 -XX:MaxMetaspaceSize=N
-XX:MaxMetaspaceSize: 设置元空间最大值, 默认是-1, 即不限制, 或者说只受限于本地内存大小。
-XX:MetaspaceSize: 指定元空间触发Fullgc的初始阈值(元空间无固定初始大小), 以字节为单位,默认21M,达到该值就会触发full gc进行类型卸载, 同时收集器会对该值进行调整: 如果释放了大量的空间, 就适当降低该值; 如果释放了很少的空间, 那么在不超 过-XX:MaxMetaspaceSize(如果设置了的话) 的情况下, 适当提高该值。这个跟早期jdk版本的**-XX:PermSize**参数意思不一样,- XX:PermSize代表永久代的初始容量。
由于调整元空间的大小需要Full GC,这是非常昂贵的操作,如果应用在启动的时候发生大量Full GC,通常都是由于永久代或元空间发生 了大小调整,基于这种情况,一般建议在JVM参数中将MetaspaceSize和MaxMetaspaceSize设置成一样的值,并设置得比初始值要大, 对于8G物理内存的机器来说,一般我会将这两个值都设置为256M。

linux中jdk8 栈内存的默认大小:1M

[root@k8s-node02 ~]#  java -XX:+PrintFlagsFinal -version | grep ThreadStackSize
     intx CompilerThreadStackSize                  = 1024                                   {pd product} {default}
     intx ThreadStackSize                          = 1024                                   {pd product} {default}
     intx VMThreadStackSize                        = 1024                                   {pd product} {default}
openjdk version "11.0.10" 2021-01-19 LTS
OpenJDK Runtime Environment 18.9 (build 11.0.10+9-LTS)
OpenJDK 64-Bit Server VM 18.9 (build 11.0.10+9-LTS, mixed mode, sharing)

演示栈内存溢出:

public class StackOverflowTest {

    //jvm设置 -Xss128k, -Xss默认1M     8倍   12548/8=1588
    static int count = 0;

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

    public static void main(String[] args) {
        try {
            method();
        } catch (Throwable e) {
            e.printStackTrace();
            System.out.println(count);

        }
    }
}

运行结果:

	java.lang.StackOverflowError
	at com.zengqingfa.exercise.jvm.StackOverflowTest.method(StackOverflowTest.java:15)
	at com.zengqingfa.exercise.jvm.StackOverflowTest.method(StackOverflowTest.java:16)
	at com.zengqingfa.exercise.jvm.StackOverflowTest.method(StackOverflowTest.java:16)
  at com.zengqingfa.exercise.jvm.StackOverflowTest.method(StackOverflowTest.java:16)
1098

**结论: -Xss设置越小count值越小,说明一个线程栈里能分配的栈帧就越少,但是对JVM整体来说能开启的线程数会更多 **
JVM内存参数大小该如何设置? JVM参数大小设置并没有固定标准,需要根据实际项目情况分析

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值