首页 > 系统相关 >Java类加载机制与JVM运行时数据区各逻辑内存区域与JDK的版本相关差异浅谈

Java类加载机制与JVM运行时数据区各逻辑内存区域与JDK的版本相关差异浅谈

时间:2025-01-20 11:55:33浏览次数:1  
标签:... Java 浅谈 JDK jvms 虚拟机 线程 加载

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文件做了如下解释:

 

 

 

 

 

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,浅谈,JDK,jvms,虚拟机,线程,加载
From: https://www.cnblogs.com/CCTVCHCH/p/18680971

相关文章

  • Java项目部署后无法访问的原因及解决方法
    当您部署了一个Java项目但无法访问时,可能是由多个因素引起的。为了快速定位并解决问题,您可以按照以下步骤进行排查和修复:检查应用服务器配置:确保应用服务器(如Tomcat、Jetty、WildFly等)已正确安装并配置。检查服务器的启动日志,确保没有启动错误或异常信息。确认应用服务器监......
  • 【转】[JavaScript] 为什么需要 ===
    转自:kimi.ai在JavaScript中,===是严格等于运算符(StrictEqualityOperator),它与==(等于运算符)一起用于比较两个值是否相等。然而,===和==的行为和用途有所不同,这主要是由于JavaScript的类型系统和历史设计导致的。以下是一些关键原因,解释为什么JavaScript中需要===:1.......
  • JavaScript笔记APIs篇02——DOM事件
     黑马程序员视频地址:黑马程序员前端JavaScript入门到精通全套视频教程https://www.bilibili.com/video/BV1Y84y1L7Nn?vd_source=0a2d366696f87e241adc64419bf12cab&spm_id_from=333.788.videopod.episodes&p=78 目录事件监听(绑定)事件监听其他版本(了解)事件类型事件对象......
  • 【转】[JavaScript] JS 对象和 JSON 的区别与转换
    转自:kimi.ai在JavaScript中,JS对象和JSON是两个密切相关但又有所区别的概念。以下是它们的主要区别:1. 定义和用途JS对象JS对象是JavaScript中的一种数据结构,用于存储键值对(key-valuepairs)。它是JavaScript中的基本数据类型之一,可以用来表示复杂的数据结构,例如用......
  • 老榕树的Java专题:Java 中如何实现异步
    在Java编程中,异步操作是一项关键技术,它允许程序在执行某些耗时任务时,不会阻塞主线程,从而提高整体的性能和响应性。本文将探讨Java中实现异步的几种常见方式。一、使用Thread类Java的Thread类是实现异步的基础方式。通过创建一个继承自Thread类的子类,并在run方法中定义......
  • Java访问网页获取返回内容
    前言需要一直关注网站有没有出成绩结果,一直盯着好麻烦的说既然是程序员,应该可以做个定时任务代劳吧代码使用的是seleniumSpringBoot环境,JDK17这只是一个简陋版,做出来不需要太多代码pom.xml<parent><groupId>org.springframework.boot</groupId><art......
  • 高级java每日一道面试题-2025年01月20日-数据库篇-并发事务带来哪些问题?
    如果有遗漏,评论区告诉我进行补充面试官:并发事务带来哪些问题?我回答:并发事务带来的主要问题在多用户环境中,多个事务可能同时对数据库进行读写操作,这可能导致以下几种常见的并发问题:1.脏读(DirtyRead)定义:当一个事务能够读取到另一个未提交事务的数据修改时,称为......
  • 初始JavaEE篇 —— 快速上手 SpringBoot
    找往期文章包括但不限于本期文章中不懂的知识点:个人主页:我要学编程程(ಥ_ಥ)-CSDN博客所属专栏:JavaEE目录SpringBoot相关介绍与解惑 SpringBoot项目的创建 通过官方提供的网页来创建通过IDEA来创建SpringBoot项目的介绍pom.xml文件项目目录介绍第一个Hel......
  • IntelliJ IDEA 2024.3 Java开发工具
    IntelliJIDEA2024.3Java开发工具JetBrainsIntelliJIDEA2024mac,是一款Java开发工具,IntelliJIDEA凭借无与伦比的Java和Kotlin支持脱颖而出。从一开始就支持尖IDEA2024.3中文版开发工具端语言功能,保持领先地位。IntelliJIDEA对您的代码了如指掌,利用这些知识在每个......
  • ElasticSearch Java 使用
    目录创建工程,导入坐标创建索引index创建映射mapping建立文档document建立文档(通过XContentBuilder)建立文档(使用Jackson转换实体)1)添加jackson坐标2)创建Article实体3)代码实现查询文档操作关键词查询字符串查询使用文档ID查询文档查询文档分页操作批量插入数据分页查询查询......