首页 > 系统相关 >【面试精讲】JVM 的内存布局和运行原理(附代码)

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

时间:2024-03-21 13:30:04浏览次数:17  
标签:Java 虚拟机 内存 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,虚拟机,内存,JVM,方法,精讲,加载
From: https://blog.csdn.net/FMC_WBL/article/details/136858374

相关文章

  • Java 8 内存管理原理解析及内存故障排查实践
    作者:vivo互联网服务器团队- ZengZhibin介绍Java8虚拟机的内存区域划分、内存垃圾回收工作原理解析、虚拟机内存分配配置,介绍各垃圾收集器优缺点及场景应用、实践内存故障场景排查诊断,方便读者面临内存故障时有一个明确的思路和方向。一、背景Java是一种流行的编程语言,可......
  • 【CKA模拟题】查找集群中使用内存最高的node节点
    题干Forthisquestion,pleasesetthiscontext(Inexam,diffclustername)kubectlconfiguse-contextkubernetes-admin@kubernetesFindtheNodethatconsumesthemostMEMORYinallcluster(currentlywehavesinglecluster).Then,storetheresultin......
  • 浮点数在内存中的存储及精度问题
    1.引子通过上图,我们发现即使是在我们看来字节大小、实际意义一样的数据,以浮点数、整数两种不同的形式进行存放、取出结果是不同的值,这就说明计算机对浮点数与整数是完全不同的处理方式。毕竟我们都知道计算机是只能识别二进制,因此如何表示小数以及用科学计数法表示数中的点后......
  • C++ 多重继承下的内存布局
    1.多重继承多重继承示例代码如下:classBase1{public:voidf0(){}virtualvoidf1(){}inta;};classBase2{public:virtualvoidf2(){}intb;};classDerived:publicBase1,publicBase2{public:voidd(){}voidf2(){}......
  • [转帖]JVM优化之调整大内存分页(LargePage)
    https://nowjava.com/article/31311 在这篇文章中:内存分页大小对性能的提升原理调整OS和JVM内存分页cat/proc/meminfo|grepHugeecho4294967295>/proc/sys/kernel/shmmaxecho154>/proc/sys/vm/nr_hugepages本文将从内存分页的原理,如何调整分页大小两节......
  • 【C语言】结构体的内存对齐问题
    1.结构体内存对齐我们已经基本掌握了结构体的使用了。那我们现在必须得知道结构体在内存中是如何存储的?内存是如何分配的?所以我们得知道如何计算结构体的大小?这就引出了我们今天所要探讨的内容:结构体内存对齐。1.1对齐规则首先得掌握结构体的对齐规则:1.结构体的第⼀......
  • 数据在内存中的存储
    文章目录数据在内存中的存储整数在内存中的存储大小端字节序存储浮点数在内存中的存储存取数据在内存中的存储整数在内存中的存储整数在内存中是以补码的形式存储的整数的二进制表示有三种:原码、反码、补码对于有符号整数,它的最高位视为符号位,1表示负,0表示正。......
  • Go Redis专题精讲
    GoRedis专题精讲一、介绍1.1、客户端列表go-redis提供各种类型的客户端:Redis单节点客户端Redis集群客户端Redis哨兵客户端Redis分片客户端Redis通用客户端go-redis也可以用于kvrocks,kvrocks是分布式键值NoSQL数据库,使用RocksDB作为存储引擎......
  • 3 python的数值在内存中如何存储
    python的数值在内存中如何存储 在Python中,数值在内存中的存储方式取决于数值的类型和大小,以及Python的版本。Python使用固定的字节数来表示整数类型,并且对于浮点数,通常使用双精度(64位)或者扩展精度(128位)的浮点表示。整数的存储方式:对于较小的整数,Python通常使用一个机器字长......
  • 内存检测工具——ASan(AddressSanitizer)的介绍和使用
    ASan介绍ASan全称AddressSanitizer,是一种内存错误检测工具,目的是帮助开发者检测和调试内存相关的问题,如使用未分配的内存、使用已释放的内存、堆内存溢出等。ASan是由Google开发的,广泛用于C、C++等语言的代码中。ASan的工作原理是在编译时将额外的代码插入到目标程序中,对内存的......