【面试精讲】JVM 的内存布局和运行原理(附代码)

【面试精讲】JVM 的内存布局和运行原理(附代码)

目录

一、JVM内存布局

1、堆(Heap)

2、方法区(Method Area)

3、程序计数器(Program Counter Register)

4、虚拟机栈(VM Stack)

5、本地方法栈(Native Method Stack)

二、JVM运行原理

1、类加载机制

2、类加载机制详解

2.1、 加载阶段

2.2、验证阶段

2.3、准备阶段

2.4、解析阶段

2.5、初始化

总结

 博主v:XiaoMing_Java


Java虚拟机(JVM)是Java技术的核心,它提供了一个平台无关的运行环境,使得Java程序能够在多种硬件和操作系统平台上不加修改地运行。

本文将详细探讨JVM的内存布局以及其运行原理,并提供相关代码示例来增进理解。

一、JVM内存布局

JVM的内存布局分为若干个独立的区域,主要包括堆(Heap)、方法区(Method Area)、程序计数器(Program Counter Register)、虚拟机栈(VM Stack)和本地方法栈(Native Method Stack)。下面将逐一介绍这些区域的功能和特点。

1、堆(Heap)

堆是JVM内存中最大的一块区域,也是垃圾收集器管理的主要区域。所有的对象实例以及数组都在这里分配内存。堆被所有线程共享,为了优化垃圾回收和减少内存分配延迟,它通常被分为新生代(Young Generation)、老年代(Old Generation)和永久代(Permanent Generation,Java 8 中已经被元空间(Metaspace)所取代)。

// 示例:在堆上分配对象内存
public class HeapExample {
    public static void main(String[] args) {
        Object obj = new Object(); // 在堆上创建一个对象
        int[] array = new int[10]; // 在堆上分配一个整型数组
    }
}

2、方法区(Method Area)

方法区与堆一样,是各个线程共享的内存区域。它用于存储已被虚拟机加载的类信息、常量、静态变量等数据。在HotSpot JVM中,方法区的一部分实现为永久代,但在Java 8及其之后的版本中已被元空间所替代。

// 示例:静态变量存储在方法区
public class MethodAreaExample {
    private static int STATIC_VARIABLE = 123;
}

3、程序计数器(Program Counter Register)

程序计数器是当前线程所执行的字节码的行号指示器。每个线程都有自己独立的程序计数器,是线程私有的内存。如果线程执行的是Java方法,计数器记录的是正在执行的虚拟机字节码指令的地址;如果是Native方法,计数器值则为空(Undefined)。

4、虚拟机栈(VM Stack)

虚拟机栈是线程私有的,它的生命周期与线程相同。每个方法在执行时都会创建一个栈帧(Stack Frame),用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一次方法调用,都会有一个栈帧被压入虚拟机栈,方法返回时弹出。

// 示例:虚拟机栈的使用
public class VMStackExample {
    public static void main(String[] args) {
        methodA();
    }

    private static void methodA() {
        int a = 10; // 局部变量存储在栈帧的局部变量表中
        methodB();
    }

    private static void methodB() {
        double b = 20.0; // 这是另一个栈帧的局部变量表项
    }
}

5、本地方法栈(Native Method Stack)

本地方法栈与虚拟机栈类似,区别在于本地方法栈服务于Native方法。当一个线程调用Native方法时,例如通过JNI(Java Native Interface),会进入本地方法栈。

二、JVM运行原理

JVM的运行原理涉及到类加载机制、执行引擎以及垃圾回收策略等方面。

1、类加载机制

  1. 加载阶段(Loading)
  2. 验证阶段(Verification)
  3. 准备阶段(Preparation)
  4. 解析阶段(Resolution)
  5. 初始化阶段(Initialization)
  6. 使用阶段(Using)
  7. 卸载阶段(Unloading)

JVM类加载机制遵循“加载(Loading)- 链接(Linking)- 初始化(Initialization)”的过程,其中:

  1. 加载:读取.class文件,转换成运行时数据结构,并在方法区生成对应的Class对象。
  2. 链接:验证类数据的正确性,为静态变量分配内存并设置默认初始值,然后将符号引用转换为直接引用。
  3. 初始化:执行类构造器<clinit>()方法的过程。
// 示例:类加载过程
public class ClassLoadingExample {
    static {
        System.out.println("Class <ClassLoadingExample> is initialized.");
    }

    public static void main(String[] args) {
        // 使用Class.forName触发类加载
        Class.forName("com.example.ClassLoadingExample");
    }
}

2、类加载机制详解

2.1、 加载阶段

此阶段用于查到相应的类(通过类名进行查找)并将此类的字节流转换为方法区运行时的数据结构,然后再在内存中生成一个能代表此类的 java.lang.Class 对象,作为其他数据访问的入口。

小贴士:需要注意的是加载阶段和连接阶段的部分动作有可能是交叉执行的,比如一部分字节码文件格式的验证,在加载阶段还未完成时就已经开始验证了。

2.2、验证阶段

此步骤主要是为了验证字节码的安全性,如果不做安全校验的话可能会载入非安全或有错误的字节码,从而导致系统崩溃,它是 JVM 自我保护的一项重要举措。

验证的主要动作大概有以下几个:

文件格式校验包括常量池中的常量类型、Class 文件的各个部分是否被删除或被追加了其他信息等;
元数据校验包括父类正确性校验(检查父类是否有被 final 修饰)、抽象类校验等;
字节码校验,此步骤最为关键和复杂,主要用于校验程序中的语义是否合法且符合逻辑;
符号引用校验,对类自身以外比如常量池中的各种符号引用的信息进行匹配性校验。

2.3、准备阶段

此阶段是用来初始化并为类中定义的静态变量分配内存的,这些静态变量会被分配到方法区上。

HotSpot 虚拟机在 JDK 1.7 之前都在方法区,而 JDK 1.8 之后此变量会随着类对象一起存放到 Java 堆中。

2.4、解析阶段

此阶段主要是用来解析类、接口、字段及方法的,解析时会把符号引用替换成直接引用。

所谓的符号引用是指以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可;而直接引用是可以直接指向目标的指针、相对偏移量或者是一个能间接定位到目标的句柄。

符号引用和直接引用有一个重要的区别:使用符号引用时被引用的目标不一定已经加载到内存中;而使用直接引用时,引用的目标必定已经存在虚拟机的内存中了。

2.5、初始化

初始化阶段 JVM 就正式开始执行类中编写的 Java 业务代码了。到这一步骤之后,类的加载过程就算正式完成了。

总结

了解JVM的内存布局以及运行原理,对于编写高效稳定的Java程序至关重要。通过掌握JVM的架构,开发者可以更好地理解程序的性能特点,优化内存使用,预防内存泄露,以及有效地利用JVM提供的监控和调试工具。以上内容详细介绍了JVM内存各个区域的职责和特性,并且通过代码示例阐述了JVM类加载、执行引擎运作以及垃圾回收的基本原理,旨在为深入理解JVM提供一份参考资料。

如果本文对你有帮助 欢迎 关注 、点赞 、收藏 、评论, 博主才有动力持续记录遇到的问题!!!

 博主v:XiaoMing_Java

  📫作者简介:嗨,大家好,我是 小明(小明Java问道之路),互联网大厂后端研发专家,2022博客之星TOP3 / 博客专家 / CSDN后端内容合伙人、InfoQ(极客时间)签约作者、阿里云签约博主、全网 6 万粉丝博主。


🍅 文末获取联系 🍅  👇🏻 精彩专栏推荐订阅收藏 👇🏻

专栏系列(点击解锁)

学习路线(点击解锁)

知识定位

🔥Redis从入门到精通与实战🔥

Redis从入门到精通与实战

围绕原理源码讲解Redis面试知识点与实战

🔥MySQL从入门到精通🔥

MySQL从入门到精通

全面讲解MySQL知识与企业级MySQL实战

🔥计算机底层原理🔥

深入理解计算机系统CSAPP

以深入理解计算机系统为基石,构件计算机体系和计算机思维

Linux内核源码解析

围绕Linux内核讲解计算机底层原理与并发

🔥数据结构与企业题库精讲🔥

数据结构与企业题库精讲

结合工作经验深入浅出,适合各层次,笔试面试算法题精讲

🔥互联网架构分析与实战🔥

企业系统架构分析实践与落地

行业最前沿视角,专注于技术架构升级路线、架构实践

互联网企业防资损实践

互联网金融公司的防资损方法论、代码与实践

🔥Java全栈白宝书🔥

精通Java8与函数式编程

本专栏以实战为基础,逐步深入Java8以及未来的编程模式

深入理解JVM

详细介绍内存区域、字节码、方法底层,类加载和GC等知识

深入理解高并发编程

深入Liunx内核、汇编、C++全方位理解并发编程

Spring源码分析

Spring核心七IOC/AOP等源码分析

MyBatis源码分析

MyBatis核心源码分析

Java核心技术

只讲Java核心技术

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-03-21 14:36:01       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-03-21 14:36:01       101 阅读
  3. 在Django里面运行非项目文件

    2024-03-21 14:36:01       82 阅读
  4. Python语言-面向对象

    2024-03-21 14:36:01       91 阅读

热门阅读

  1. TC551001CPI

    2024-03-21 14:36:01       38 阅读
  2. Spring面试题

    2024-03-21 14:36:01       41 阅读
  3. vim | vim scp的使用

    2024-03-21 14:36:01       38 阅读
  4. 將mysql表創建到hive腳本

    2024-03-21 14:36:01       41 阅读
  5. MPI4.1文档4-MPI数据类型 MPI DataTypes

    2024-03-21 14:36:01       34 阅读
  6. 邦芒解析:职场中得不到理想薪水的八大原因

    2024-03-21 14:36:01       38 阅读
  7. CSS是什么,它主要用于做什么?

    2024-03-21 14:36:01       38 阅读
  8. Android 图形渲染和显示系统关系

    2024-03-21 14:36:01       40 阅读
  9. CSS有哪些选择器?

    2024-03-21 14:36:01       40 阅读