JVM灵魂拷问系列之JVM内存模型知多少


JVM灵魂拷问系列之JVM内存模型知多少

1.JVM中有几块内存区域?JDK8之后对内存分代做了什么改进?

前提流程分析:我们在部署项目时,无论是旧项目(SSH、SSM)还是新项目(SpringBoot内置tomcat),都会放在tomcat中的目录中部署运行的。
tomcat由java编写,本质上是一个JVM进程,当请求到来时,jvm通过编译后的项目字节码来执行我们的业务代码响应请求

众所周知,不讲tomcat自带的线程池,我们平时使用的框架或者自定义的线程池都说明了一点,系统中运行着多个线程,这些线程既有自己独有的空间(栈内存),也有共同分享的空间(堆内存)

1.1 堆内存(线程共享)

由图可知,堆分为三个部分:新生代、老年代和持久代/元空间

元空间不存放在堆中而是存放在在本地内存中,这里用作比较之用

JVM在启动过程中,线程会创建很多对象,这些对象一般优先存放在 新生代的Eden伊甸园区域 ,如果对象大于一定程度,则存放在 老年代

java语言的特点:一处编译,处处运行。说的就是我们写的项目代码会通过编译器编译成字节码文件,由不同系统的java虚拟机来运行他们。而这些 字节码文件(类信息等) 也是需要一个地方进行存放的,JDK8之前,存放地为 永久代 ,JDK8之后为 元空间

JDK8前后分代改进:

  • JDK8以前:在 永久代 存放常量池和类信息
  • JDK8以后:在 metaspace元空间 存放类信息

为什么 JDK1.8 把永久代干掉

  1. oracle收购两家公司,其中一家公司JDK1.8以前没有永久代,干脆干掉了再新增一个元空间
  2. 永久代存放一系列常量,类、方法的相关信息,但是这些都没办法确定,但是 永久代空间固定,可能导致内存溢出(内存不足以存放信息-OutOfMemoryError:PermGen)
  3. 元空间本质是本地内存,理论上 取决于操作系统可以分配的内存大小,可以解决内存不足的情况

1.2 栈内存/虚拟机栈(线程独享)

当线程创建时,会创建一个由多个栈帧组成的虚拟机栈,存放着例如 局部变量、对象引用

1.3 本地方法栈(线程独享)

存放一些由 C语言编写的native方法 ,例如我们常用到的 Unsafe(CAS)

1.4 程序计数器(线程独享)

用来 记录线程执行字节码的地址 ,也就是对执行到哪一行代码做一个标记。当线程被挂起,再恢复时,方便及时定位到继续执行的位置

1.5 方法区(线程共享)

方法区是 逻辑层面 上的划分,如图

方法区在 中存储着 静态变量 和 字符串常量
方法区在 元空间 中存储着 类相关信息 和 运行时常量

【静态常量池】:属于,存放着 文本常量 或者 被final修饰的常量,也存放着一些 类名、方法名
【运行时常量池】:属于元空间/本地内存,当类被加载到内存中,JVM会把静态常量池中的内容存放到运行时常量池中。
【字符串常量池】:属于,运行时常量池分出来的一部分,类加载到内存中时,字符串会存放在字符串常量池中

2.你知道JVM是如何运行起来的吗?对象是如何分配的?

举个例子🌰:

public class MyController {

    private static final String PREFIX = "名称前缀";

    public void doRequest(){
        MyService myService = new MyService();
        myService.printName("testName");
    }

    private class MyService{

        public void printName(String name){
            System.out.println(PREFIX + name);
        }

    }

}

  1. java代码通过编译器编译成JVM可读懂的class字节码文件。项目启动,JVM调用main方法,或者存在Spring容器实例化必需类时,都会通过 类加载机制 把MyController.class文件加载到内存中,在堆内存中创建MyController实例,将MyController类相关信息存放在元空间中,在堆内存的静态常量池中创建静态常量
  2. 当请求到来时,线程执行doRequest()方法,会在 堆内存 中创建一个MyService对象实例,同时在栈内存中创建一个栈帧,存放方法信息和局部引用变量myService,指向堆内存中的示例。静态常量池的”名称前缀”也会存入运行时常量池
  3. 当myService执行printName()方法时,会创建printName()方法对应的栈帧,字符串常量池创建”testName”,局部变量name也存在栈帧中,指向”testName”
  4. printName()方法执行完毕,对应栈帧销毁,而后doRequest()执行完毕,对应栈帧销毁。因栈帧中的引用销毁了,堆内存相关的数据没有引用,下一次会被垃圾回收

【参考链接】:
1:JVM内存结构个人笔记


评论
  目录