JVM 从入门到精通(一)初窥Java虚拟机

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: JVM 从入门到精通(一)初窥Java虚拟机

文章目录


Java虚拟机是什么

Java虚拟的体系结构


Java虚拟机是什么


首先你要意识到,当你说“Java虚拟机”的时候,可能指的是如下三种不同的东西:


  • 抽象规范
  • 一个具体的实现
  • 一个运行中的虚拟机实例


Java虚拟的体系结构



image.png


1.Java程序执行流程


Java程序的执行依赖于编译环境和运行环境。源码代码转变成可执行的机器代码,由下面的流程完成:


image.png


Java技术的核心就是Java虚拟机,因为所有的Java程序都在虚拟机上运行。Java程序的运行需要Java虚拟机、Java API和Java Class文件的配合。Java虚拟机实例负责运行一个Java程序。当启动一个Java程序时,一个虚拟机实例就诞生了。当程序结束,这个虚拟机实例也就消亡。


2.Java虚拟机


Java虚拟机的主要任务是装载class文件并且执行其中的字节码。Java虚拟机包含一个类装载器(class loader),它可以从程序和API中装载class文件,Java API中只有程序执行时需要的类才会被装载,字节码由执行引擎来执行。


image.png


当Java虚拟机由主机操作系统上的软件实现时,Java程序通过调用本地方法和主机进行交互。Java方法由Java语言编写,编译成字节码,存储在class文件中。本地方法由C/C++/汇编语言编写,编译成和处理器相关的机器代码,存储在动态链接库中,格式是各个平台专有。所以本地方法是联系Java程序和底层主机操作系统的连接方式。


3.Java虚拟机的内部体系结构


在 Java虚拟机规范中,一个虚拟机实例的行为是分别按照子系统、内存区、数据类型和指令来描述的,这些组成部分一起展示了抽象的虚拟机内部体系结构。


image.png


3.1 Class文件


Class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在Class文件中,中间没有添加任何分隔符号。Class文件采用一种类似于C语言结构体的伪结构来存储数据,这种伪结构中只包含两种数据类型,无符号数和表。无符号数属于基本的数据类型,以u1,u2,u4,u8分别代表1个字节,2个字节,4个字节和8个字节的无符号数,可以用来描述数字、索引引用、数量值或者按照utf-8编码构成字符串值。表是由多个无符号数或者其他表作为数据项构成的复合数据类型,所有表都习惯性地以“_info”结尾,用来描述有层次关系的复合结构数据。


Class文件的内容包括:

ClassFile {
    u4 magic;                                     //魔数:0xCAFEBABE,用来判断是否是Java class文件
    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];  //属性表
}


访问标志:类还是接口;是否定义为public类型,abstract类型;若为类,是否声明为final。

字段:用来描述接口或类中的变量,但不包括在方法内部的变量。

字面量:文本字符串,声明为final的常量值等。

符号引用:类和接口的全限定名;字段的名称和描述符;方法的名称和描述符。

类索引、父类索引、接口索引:用来确定类的继承关系。


3.2 运行时数据区域


Java虚拟机在执行Java程序的时候会把它管理的内存划分为若干个不同的数据区域,这些区域有各自的用途以及创建和销毁的时机,有的区域随着虚拟机进程的启动而存在(线程共享),有的区域则随着用户线程的启动和结束而建立和销毁(线程私有)。Java虚拟机所管理的内存包括以下几个运行时数据区域。


3.2.1 程序计数器


对于一个运行中的Java程序而言,每一个线程都有它的程序计数器,也叫PC寄存器,可以看做当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里,字节码解释器就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支,循环,跳转,异常处理,线程恢复等都需要依赖这个计数器。


程序计数器既能持有一个本地指针,也能持有一个returnAddress。当线程执行某个Java方法时,程序计数器的值总是下一条被执行指令的地址。这里的地址可以是一个本地指针,也可以是方法字节码中相对该方法起始指令的偏移量。如果该线程正在执行一个本地方法,那么此时程序计数器的值是“undefined”。


程序计数器属于线程私有的内存,也就是说,每当创建一个线程,都将得到该线程自己的一个程序计数器。Java虚拟机的多线程是通过线程的轮换并且分配处理器的执行时间来实现的,在一个确定的时刻,一个处理器都只会执行一条线程中的指令,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器。


3.2.2 Java虚拟机栈(Java栈)


Java虚拟机栈也是线程私有的,它的生命周期和线程相同。Java虚拟机栈描述的是Java方法执行的内存模型,每个方法在执行时会创建一个栈帧,用于存放局部变量表,操作数栈,动态链接,方法出口等信息。一个Java方法从调用到执行结束的过程,相当于一个栈帧在Java虚拟机栈中从入栈到出栈的过程。


(1)局部变量表


局部变量表用于存放编译时期可知的各种基本数据类型,对象的引用(不等同于对象,可能是指向对象的起始地址的指针,也可能是指向一个代表对象的句柄)以及returnAddress类型(指向了一条字节码地址)。局部变量在方法执行时被创建,在方法执行结束时销毁。


字节码指令通过从0开始的索引使用其中的数据。类型为int, float, reference和returnAddress的值在数组中占据一项,而类型为byte, short和char的值在存入数组前都被转换为int值,也占据一项。但类型为long和double的值在数组中却占据连续的两项。


(2)操作数栈


和局部变量区一样,操作数栈也是被组织成一个以字长为单位的数组。它通过标准的栈操作访问–压栈和出栈。由于程序计数器无法被程序指令直接访问,Java虚拟机的指令是从操作数栈中取得操作数,所以它的运行方式是基于栈而不是基于寄存器。虚拟机把操作数栈作为它的工作区,因为大多数指令都要从这里弹出数据,执行运算,然后把结果压回操作数栈。


(3)帧数据区


除了局部变量区和操作数栈,Java栈帧还需要帧数据区来支持常量池解析、正常方法返回以及异常派发机制。每当虚拟机要执行某个需要用到常量池数据的指令时,它会通过帧数据区中指向常量池的指针来访问它。除了常量池的解析外,帧数据区还要帮助虚拟机处理Java方法的正常结束或异常中止。如果通过return正常结束,虚拟机必须恢复发起调用的方法的栈帧,包括设置程序计数器指向发起调用方法的下一个指令;如果方法有返回值,虚拟机需要将它压入到发起调用的方法的操作数栈。为了处理Java方法执行期间的异常退出情况,帧数据区还保存一个对此方法异常表的引用。


3.2.3 本地方法栈


任何本地方法接口都会使用某种本地方法栈,本地方法栈与Java虚拟机栈发挥的作用类似。当线程调用Java方法时,虚拟机会创建一个新的栈帧并压入Java栈。当它调用的是本地方法时,虚拟机会保持Java栈不变,不再在线程的Java栈中压入新的栈,虚拟机只是简单地动态连接并直接调用指定的本地方法。


3.2.4 堆


Java程序在运行时创建的所有类实例或数组(数组在Java虚拟机中是一个真正的对象)都放在同一个堆中,堆是虚拟机管理的内存最大的一块,被所有线程所共享,在虚拟机启动的时候创建。堆内存的唯一目的就是存放对象实例,几乎所有的对象实例都在堆中分配内存。


3.2.5 方法区


方法区同Java堆一样,是各个线程共享的内存区域,用于存储已经被虚拟机加载的类信息,常量,静态变量以及及时编译器编译后的代码等信息。当虚拟机装载某个类型时,它使用类装载器定位相应的class文件,然后读入这个class文件并将它传输到虚拟机中,接着虚拟机提取其中的类型信息,并将这些信息存储到方法区。方法区也可以被垃圾回收器收集,因为虚拟机允许通过用户定义的类装载器来动态扩展Java程序。


方法区中存放了以下信息:

• 这个类型的全限定名(如全限定名java.lang.Object)
• 这个类型的直接超类的全限定名
• 这个类型是类类型还是接口类型
• 这个类型的访问修饰符(public, abstract, final的某个子集)
• 任何直接超接口的全限定名的有序列表
• 该类型的常量池,Class文件中除了有类的版本,字段,方法,接口等描述信息外,还有一项是常量池,用于存放编译时生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放
• 字段信息(字段名、类型、修饰符)
• 方法信息(方法名、返回类型、参数数量和类型、修饰符)
• 除了常量以外的所有类(静态)变量
• 指向ClassLoader类的引用(每个类型被装载时,虚拟机必须跟踪它是由启动类装载器还是由用户自定义类装载器装载的)
• 指向Class类的引用(对于每一个被装载的类型,虚拟机相应地为它创建一个java.lang.Class类的实例存于堆中。比如你有一个到java.lang.Integer类的对象的引用,那么只需要调用Integer对象引用的getClass()方法,就可以得到表示java.lang.Integer类的Class对象)


3.3 类加载子系统


虚拟机把Java描述类的信息加载到内存,并对数据进行校验,转换解析和初始化,形成可以被Java虚拟机直接使用的Java类型,这就是Java虚拟机的类加载机制。类从加载到虚拟机开始,到卸载出内存为止,它的整个生命周期包括:

加载,验证,准备,解析,初始化,使用和卸载7个阶段。


image.png


3.3.1 加载


加载是类加载的一个阶段,在该阶段,Java虚拟机主要完成以下三件事情:

• 根据此类的全限定名来确定该类的二进制字节流;
• 将这个字节流所代表的静态数据存储结构转换为方法区的运行时数据结构;
• 在堆内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。


/

3.3.2 验证


验证是连接的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息是否符合该虚拟机的要求。


3.3.3 准备


准备阶段正式为类变量在方法区中分配内存并且赋初始值,初始值一般为该类变量类型的零值。


3.3.4 解析


解析阶段虚拟机将常量池内的符号引用替换为直接引用。

符号引用:以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义的定位到目标即可。

直接引用:是指能直接指向目标的指针,相对偏移量或是一个能间接定位到目标的句柄。


3.3.5 初始化


初始化是类加载过程的最后一个阶段,在前面的类加载过程中,除了在加载阶段用户可以自定义类加载器参与之外,其余动作完全由虚拟机主导和控制,到了初始化阶段,才真正开始执行类中定义的Java代码(字节码)。准备阶段为类变量分配内存并且赋初始值,赋值操作在初始化阶段执行。


目录
相关文章
|
4天前
|
监控 安全 Java
Java中的多线程编程:从入门到实践####
本文将深入浅出地探讨Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的摘要形式,本文将以一个简短的代码示例作为开篇,直接展示多线程的魅力,随后再详细解析其背后的原理与实现方式,旨在帮助读者快速理解并掌握Java多线程编程的基本技能。 ```java // 简单的多线程示例:创建两个线程,分别打印不同的消息 public class SimpleMultithreading { public static void main(String[] args) { Thread thread1 = new Thread(() -> System.out.prin
|
10天前
|
Java 大数据 API
14天Java基础学习——第1天:Java入门和环境搭建
本文介绍了Java的基础知识,包括Java的简介、历史和应用领域。详细讲解了如何安装JDK并配置环境变量,以及如何使用IntelliJ IDEA创建和运行Java项目。通过示例代码“HelloWorld.java”,展示了从编写到运行的全过程。适合初学者快速入门Java编程。
|
16天前
|
存储 安全 Java
🌟Java零基础-反序列化:从入门到精通
【10月更文挑战第21天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
53 5
|
13天前
|
安全 Java 调度
Java中的多线程编程入门
【10月更文挑战第29天】在Java的世界中,多线程就像是一场精心编排的交响乐。每个线程都是乐团中的一个乐手,他们各自演奏着自己的部分,却又和谐地共同完成整场演出。本文将带你走进Java多线程的世界,让你从零基础到能够编写基本的多线程程序。
29 1
|
20天前
|
Java 数据处理 开发者
Java多线程编程的艺术:从入门到精通####
【10月更文挑战第21天】 本文将深入探讨Java多线程编程的核心概念,通过生动实例和实用技巧,引导读者从基础认知迈向高效并发编程的殿堂。我们将一起揭开线程管理的神秘面纱,掌握同步机制的精髓,并学习如何在实际项目中灵活运用这些知识,以提升应用性能与响应速度。 ####
43 3
|
21天前
|
Java
Java中的多线程编程:从入门到精通
本文将带你深入了解Java中的多线程编程。我们将从基础概念开始,逐步深入探讨线程的创建、启动、同步和通信等关键知识点。通过阅读本文,你将能够掌握Java多线程编程的基本技能,为进一步学习和应用打下坚实的基础。
|
22天前
|
存储 算法 Java
Java虚拟机(JVM)的内存管理与性能优化
本文深入探讨了Java虚拟机(JVM)的内存管理机制,包括堆、栈、方法区等关键区域的功能与作用。通过分析垃圾回收算法和调优策略,旨在帮助开发者理解如何有效提升Java应用的性能。文章采用通俗易懂的语言,结合具体实例,使读者能够轻松掌握复杂的内存管理概念,并应用于实际开发中。
|
22天前
|
Java
[Java]Socket套接字(网络编程入门)
本文介绍了基于Java Socket实现的一对一和多对多聊天模式。一对一模式通过Server和Client类实现简单的消息收发;多对多模式则通过Server类维护客户端集合,并使用多线程实现实时消息广播。文章旨在帮助读者理解Socket的基本原理和应用。
18 1
|
23天前
|
SQL IDE Java
入门Cloud Toolkit:简化你的Java应用开发与部署流程
【10月更文挑战第19天】作为一名长期从事Java开发的程序员,我一直致力于寻找能够简化日常开发工作的工具。在众多工具中,阿里巴巴推出的Cloud Toolkit引起了我的注意。这款免费的插件旨在帮助开发者更轻松地进行开发、测试及部署工作,尤其是在与云服务交互时表现尤为出色。本文将从个人的角度出发,介绍Cloud Toolkit的基本功能及其使用技巧,希望能帮助初学者快速上手这款实用工具。
18 1
|
1月前
|
存储 安全 Java
jvm 锁的 膨胀过程?锁内存怎么变化的
【10月更文挑战第3天】在Java虚拟机(JVM)中,`synchronized`关键字用于实现同步,确保多个线程在访问共享资源时的一致性和线程安全。JVM对`synchronized`进行了优化,以适应不同的竞争场景,这种优化主要体现在锁的膨胀过程,即从偏向锁到轻量级锁,再到重量级锁的转变。下面我们将详细介绍这一过程以及锁在内存中的变化。
37 4