Java类加载机制与JVM运行时数据区各逻辑内存区域与JDK的版本相关差异浅谈
Java类加载机制与JVM运行时数据区各逻辑内存区域与JDK的版本相关差异浅谈
【摘要】
JVM(Java Virtual Machine)作为Java研发人员工作的每天都会接触到的虚拟机,其运行机制与底层原理想必大家都略知一二,今天我将从初学者的角度出发,结合甲骨文官方的技术文档,对部分Java虚拟机的相关知识做一些简单的梳理。
(Oracle官方文档地址:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.6.2)
目录
一 JVM(Java Virtual Machine)简介... 3
1.1 源码文件到.class文件的过程... 3
1.2 如何阅读字节码文件... 3
二 类加载过程... 4
2.1 装载... 4
2.2 链接... 4
2.2.1 验证:保证被加载类的正确性... 4
2.2.2 准备... 5
2.2.3 解析... 5
2.3 初始化... 5
三 类加载器与双亲委派模型... 5
3.1 类加载器基础概念... 5
3.2 双亲委派机制基础概念... 6
3.3 优势... 6
3.4 安全性... 6
3.5 如何破坏双亲委派模型... 7
四 运行时数据区... 8
4.1 方法区... 8
4.2 堆... 9
4.3 Java虚拟机栈... 9
4.3.1 栈帧... 10
4.3.2 理解栈帧... 11
4.4 程序计数器... 12
4.5 本地方法栈... 12
五 参考文献... 12
一 JVM(Java Virtual Machine)简介
Java虚拟机有自己完善的硬件架构,如处理器、堆栈等,还具有其自己相相应的指令系统。
Java虚拟机本质上就是一个程序,当它在命令行上启动的时候,就开始执行保存在某字节码文件中的指令。Java语言的可移植性正是建立在Java虚拟机的基础上。任何平台只要装有针对于该平台的Java虚拟机,字节码文件(.class)就可以在该平台上运行。这就是“一次编译,多次运行”。

图1:JDK与JRE关系图

1.1
源码文件到.class文件的过程
使用javac编译 Person.java--->Person.class
Person.java -> 词法分析器 -> tokens流 -> 语法分析器 -> 语法树/抽象语法树 -> 语义分析器-> 注解抽象语法树 -> 字节码生成器 -> Person.class文件
1.2
如何阅读字节码文件
甲骨文官方文档中有说明解释(https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.1),文档中对字节码.class文件做了如下解释:
A class file consists of a
single ClassFile structure:
ClassFile {
u4 magic;
u2 minor_version;
u2 major_version;
u2 constant_pool_count;
cp_info constant_pool[constant_pool_count-1];
u2 access_flags;
u2 this_class;
u2 super_class;
u2 interfaces_count;
u2 interfaces[interfaces_count];
u2 fields_count;
field_info fields[fields_count];
u2 methods_count;
method_info methods[methods_count];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
The items in the ClassFile structure are as follows:magic
The magic item supplies the magic number identifying the class file format; it has the value 0xCAFEBABE.
说明:编译后的字节码文件通过16进制编码后就形成了可阅读的格式,参照官方文档的说明:u4代表的含义就是前8位16进制数,含义即文件格式magic的值为:0xCAFEBABE。
二 类加载过程
2.1 装载
(1) 通过一个类的全限定名获取定义此类的二进制字节流。
(2) 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
(3) 在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口。
2.2 链接
2.2.1 验证:保证被加载类的正确性
文件格式验证/元数据验证/字节码验证/符号引用验证
2.2.2 准备
为类的静态变量分配内存,并将其初始化为默认值(比如整型此时初始化为默认值0)
2.2.3 解析
把类中的符号引用转换为直接引用(指向真实的内存物理地址)
2.3 初始化
对类的静态变量、静态代码块执行初始化操作。(赋予真实的变量值)
图2:类加载过程
三 类加载器与双亲委派模型
3.1 类加载器基础概念

图3:三种类加载器的层级关系
3.2 双亲委派机制基础概念
定义:如果一个类加载器在接到加载类的请求时,它首先不会自己尝试去加载这个类,而是把这个请求任务委托给父类加载器去完成,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。

图4:不同加载器负责的目录范围和功能的差异
3.3 优势
Java类随着加载它的类加载器一起具备了一种带有优先级的层次关系。比如,Java中的
Object类,它存放在rt.jar之中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object在各种类加载环境中都是同一个类。如果不采用双亲委派模型,那么由各个类加载器自己取加载的话,那么系统中会存在多种不同的Object类。
3.4 安全性
这种模式也一定程度上出于安全考虑,避免用户自己编写的类动态替换 Java 的一些核心类,比如 String,同时也避免了重复加载,因为 JVM 中区分不同类,不仅仅是根据类名,相同的 class 文件被不同的 ClassLoader 加载就是不同的两个类,如果相互转型的话会抛java.lang.ClassCaseException的异常。
3.5
如何破坏双亲委派模型
我们来看看Java的源码内容:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}}
这是public abstract class ClassLoader这个类中的loadClass方法代码片段的粘贴,我们从标记红色的代码块可以看到它寻找父类加载器的方法,显而易见,只要我们在自己的类加载器中重写这个loadClass方法,就可以破坏类加载的双亲委派机制。
四 运行时数据区

图5:运行时数据区示意图
4.1 方法区
官方文档原文:

翻译:

简单总结:
(1) 方法区是各个线程共享的内存区域,在虚拟机启动时创建
(2) 用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
(3) 虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却又一个别名叫做Non-Heap(非堆),目的是与Java堆区分开来
(4) 当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常
(5) 方法区在JDK 8中就是Metaspace【元空间】,
在JDK6或7中就是Perm Space【永久代】。
(6) Run-Time Constant Pool(常量池)在方法区分配
4.2 堆
官方文档原文:
懒得贴了,有兴趣的话给大家网址自己去看吧
(https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.6.2)
翻译:(勉强贴一下)

总结:
(1) 堆是Java虚拟机所管理内存中最大的一块,在虚拟机启动时创建,被所有线程共享
(被多个线程共享,首先想到的就是这部分的数据是线程不安全的,堆的生命周期跟虚拟机的生命周期一致)。
(2) Java对象实例以及数组都在堆上分配。
(3) 当堆无法满足内存分配需求时,将抛出OutOfMemoryError异常。
4.3 Java虚拟机栈
官方文档原文:
(https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.6.2)
翻译:
懒得翻译了,大家自行谷歌,不太想凑字数。
总结:
Java虚拟机栈
(1) 虚拟机栈是一个线程执行的区域,保存着一个线程中方法的调用状态。换句话说,一个Java线程的运行状态,由一个虚拟机栈来保存,所以虚拟机栈肯定是f,独有的,随着线程的创建而创建。
(2) 每一个被线程执行的方法,为该栈中的栈帧,即每个方法的执行对应一个栈帧
(3) 调用一个方法,就会向栈中压入一个栈帧;一个方法调用完成,就会把该栈帧从栈中弹出。
4.3.1 栈帧
每个栈帧对应一个被调用的方法,可以理解为一个方法的运行空间。
每个栈帧中包括局部变量表(Local Variables)、操作数栈(Operand Stack)、动态链接、方法返回地址(Return Address)和附加信息。
(方法返回地址:记录上一级方法调用该方法后回调的行数位置)
举例:有如下方法:

图6:一个三层链式调用的方法
那么对应的栈帧操作过程就是:

图7:Java虚拟机栈对应的栈帧操作过程
4.3.2 理解栈帧
源码文件:

编译后的字节码文件含义(顺序执行):


图8:虚拟机栈示意图
4.4 程序计数器
官方文档原文:
(https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.6.2)
别问,问就是自己翻译。
一个JVM进程中有多个线程在执行,而线程中的内容是否能够拥有执行权,是根据CPU调度来的。假如线程A正在执行到某个地方,突然失去了CPU的执行权,切换到线程B了,然后当线程A再获得CPU执行权的时候,怎么能继续执行呢?这就是需要在线程中维护一个变量,记录线程执行到的位置。
(1)程序计数器占用的内存空间很小,由于Java虚拟机的多线程是通过线程轮流切换,并分配处理器执行时间的方式来实现的,在任意时刻,一个处理器只会执行一条线程中的指令。因此,为了线程切换后能够恢复到正确的执行位置,每条线程需要有一个独立的程序计数器(线程私有)。
(2)如果线程正在执行Java方法,则计数器记录的是正在执行的虚拟机字节码指令的地址。
(3)如果正在执行的是Native方法,则这个计数器为空。
4.5 本地方法栈
官方文档原文:
(https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.6.2)
嘿嘿嘿,鸡汤来咯!
一句话明白本地方法栈:如果当前线程执行的方法是Native类型的,这些方法就会在本地方法栈中执行。
五 参考文献
1. 《The Structure of the Java Virtual Machine》https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.6.2
2. 《Java 语言和虚拟机规范》
https://docs.oracle.com/javase/specs/index.html
3. 《The class File Format》
https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.1
4. 《Java 虚拟机 规范》
https://docs.oracle.com/javase/specs/jvms/se10/html/index.html
Java类加载机制与JVM运行时数据区各逻辑内存区域与JDK的版本相关差异浅谈的更多相关文章
- Java内存管理:Java内存区域 JVM运行时数据区
转自:https://blog.csdn.net/tjiyu/article/details/53915869 下面我们详细了解Java内存区域:先说明JVM规范定义的JVM运行时分配的数据区有哪些, ...
- JVM运行时数据区和垃圾回收机制
最近参考各种资料,尤其是<深入理解Java虚拟机 JVM高级特性和最佳实践>,大牛之作.把最近学习的Java虚拟机组成和垃圾回收机制总结一下. 你不会的都是新知识,学无止境,每天进步一点点 ...
- Jvm运行时数据区 —— Java虚拟机结构小记
关于jvm虚拟机的文章网上都讲烂了.尤其是jvm运行时数据区的内容. 抱着眼见为实的想法,自己翻了翻JVM规范,花了点时间稍微梳理了一下. 以下是阅读Java虚拟机规范(Java SE 8版)的第二章 ...
- Java中的字符串常量池和JVM运行时数据区的相关概念
什么是字符串常量池 JVM为了减少字符串对象的重复创建,其维护了一个特殊的内存,这段内存被成为字符串常量池或者字符串字面量池 工作原理 当代码中出现字面量形式创建字符串对象时,JVM首先会对这个字面量 ...
- Jvm运行时数据区
一:运行时数据区 Java虚拟机在执行Java程序的过程中会把它管理的内存分为若干个不同的数据区域.这些区域有着各自的用途,一级创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有些区域则依赖用户 ...
- JVM 运行时数据区 (三)
JVM运行时数据区 运行时数据区由 程序计数器.java虚拟机栈.本地方法栈.堆.方法区 组成: 1.程序计数器 每一个Java线程都有一个程序计数器,用于保存程序执行到当前方法的哪一个指令,它是线程 ...
- JVM运行时数据区及对象在内存中初始化的过程
JVM运行时数据区 Java虚拟机所管理的内存区域,也称为运行时数据区,分为以下几个运行时数据区,如图所示 程序计数器:当前程序所执行字节码的行号指示器 程序计数器(Program Counter R ...
- JVM总结(一):概述--JVM运行时数据区
大三下,趁着寒假重温一遍JVM,准备在一个系列来总价一下学习JVM的整个过程.争取在接下来的一个星期内更新完这一个系列,然后回家过年. JVM运行时数据区 线程私有的数据区 程序计数器 虚拟机栈 本地 ...
- JVM 运行时数据区(二)
@ 目录 运行时数据区 共享区 堆区 方法区 隔离区 虚拟机栈 栈帧 本地方法栈 程序计数器 运行时数据区 JVM 运行时数据区主要分为5块 方法区 JDK1.8以后叫做元数据区(Metaspace) ...
- JVM运行时数据区与JVM堆内存模型小结
前提 JVM运行时数据区和JVM内存模型是两回事,JVM内存模型指的是JVM堆内存模型. 那JVM运行时数据区又是什么? 它包括:程序计数器.虚拟机栈.本地方法栈.方法区.堆. 来看看它们都是干嘛的 ...
随机推荐
- TypeError: fs.existsSync is not a function | import { ipcRenderer } from 'electron'
在electron的渲染进程中导包会发生TypeError: fs.existsSync is not a function node_modules/electron/index.js:6 var ...
- .NET 9正式发布,亮点是.NET Aspire和AI
Microsoft 今天正式发布了 .NET 9,这是迄今为止最高效.最现代.最安全.最智能.性能最高的 .NET 版本.这是来自世界各地的数千名开发人员又一年努力的结果.此新版本包括数千项性能.安全 ...
- 多校A层冲刺NOIP2024模拟赛09
多校A层冲刺NOIP2024模拟赛09 考试唐完了,T2.T4 都挂了 100 分,人麻了. 排列最小生成树 给定一个 \(1, 2,\dots , n\) 的排列 \(p_1, p_2,\dots, ...
- (Redis基础教程之十一) 如何使Redis中的Key过期
介绍 Redis是一个开源的内存中键值数据存储.默认情况下,Redis密钥是_永久性_的,这意味着Redis服务器将继续存储它们,除非手动将其删除.但是,在某些情况下,您已经设置了密钥,但是您知道要在 ...
- 成为Java GC专家系列(3) — 如何优化Java垃圾回收机制
本文是成为Java GC专家系列文章的第三篇.在第一篇<成为JavaGC专家Part I - 深入浅出Java垃圾回收机制>中我们学习了不同GC算法的执行过程,GC是如何工作的,什么是新生 ...
- MySQL之查询操作
1)使用in查询, 保持顺序 SELECT * FROM `template_data` where template_id in (7339747298123169843,7339747324194 ...
- tmux之常见问题
1. 使用tmux ls的时候显示错误 failed to connect to server: Connection refused 解决: 查看进程是否存在 ps -aux|grep tmux 发 ...
- H5 新增表单
1.提示占位 placeholder <input type="text" name="userName" placeholder="请输入用户 ...
- LeetCode题集-5 - 最长回文子串之马拉车(二)
书接上回,我们今天继续来聊聊最长回文子串的马拉车解法. 题目:给你一个字符串 s,找到 s 中最长的回文子串. 01.中心扩展法优化-合并奇偶处理 俗话说没有最好只有更好,看着O(n^2)的时间复杂度 ...
- 【Amadeus原创】SQL Server查询某数据库所有表名行数和空间占用率
` select object_name(id) tablename, 8reserved/1024 reserved, rtrim(8dpages)+'kb' used, 8(reserved-dp ...