Java基础知识(21)- Java 虚拟机(JVM)
1. JVM 简介
Java 平台由 Java 虚拟机(Java Virtual Machine,JVM)和 Java 应用编程接口(Application Programming Interface,API)构成。
Java 应用编程接口是一套独立于操作系统的标准接口,可分为基本部分和扩展部分。在硬件或操作系统平台上安装一个 Java 平台之后,Java 应用程序就可运行。
Java 虚拟机(Java Virtual Machine,JVM)是通过在实际的计算机上仿真模拟各种计算机功能来实现的。由一套字节码指令集、一组寄存器、一个栈、一个垃圾回收堆和一个存储方法域等组成。
JVM 屏蔽了与操作系统平台相关的信息,使得 Java 程序只需要生成在 Java 虚拟机上运行的目标代码(字节码),就可在多种平台上不加修改的运行,这也是 Java 能够 “一次编译,到处运行” 的原因。
2. JRE、JVM 和 JDK 的关系
JRE(Java Runtime Environment,Java 运行环境)是 Java 平台,所有的程序都要在 JRE 下才能够运行。包括 JVM 和 Java 核心类库和支持文件。JRE 主要由 Java API 和 JVM 组成,提供了用于执行 java 应用程序最低要求的环境。
JVM(Java Virtual Machine,Java虚拟机)是 JRE 的一部分。JVM 主要工作是解释自己的指令集(即字节码)并映射到本地的 CPU 指令集和 OS 的系统调用。Java 语言是跨平台运行的,不同的操作系统会有不同的 JVM 映射规则,使之与操作系统无关,完成跨平台性。
JDK(Java Development Kit,Java 开发工具包)是用来编译、调试 Java 程序的开发工具包。包括Java工具(javac/java/jdb 等)和 Java 基础的类库(java API )。JDK 是 Programming tools、JRE 和 JVM 的一个集合。
3. JVM体系结构
1) 类加载子系统
(1) 加载类的过程
a) 装载(loading)
负责找到二进制字节码并加载至JVM中,JVM 通过类名、类所在的包名、ClassLoader 完成类的加载。因此,标识一个被加载了的类:类名 + 包名 + ClassLoader 实例ID。
b) 链接(linking)
负责对二进制字节码的格式进行校验、初始化装载类中的静态变量以及解析类中调用的接口。
完成校验后,JVM初始化类中的静态变量,并将其赋值为默认值。
最后对比类中的所有属性、方法进行验证,以确保要调用的属性、方法存在,以及具备访问权限(例如private、public等),否则会造成NoSuchMethodError、NoSuchFieldError等错误信息。
c) 初始化(initializing)
负责执行类中的静态初始化代码、构造器代码以及静态属性的初始化,以下四种情况初始化过程会被触发。
(2) 委派模式(Delegation Mode)
当 JVM 加载一个类的时候,下层的加载器会将任务给上一层类加载器,上一层加载检查它的命名空间中是否已经加载这个类,如果已经加载,直接使用这个类。如果没有加载,继续往上委托直到顶部。检查之后,按照相反的顺序进行加载。如果Bootstrap加载器不到这个类,则往下委托,直到找到这个类。一个类可以被不同的类加载器加载。
可见性限制:下层的加载器能够看到上层加载器中的类,反之则不行,委派只能从下到上。
不允许卸载类:类加载器可以加载一个类,但不能够卸载一个类。但是类加载器可以被创建或者删除。
2) 运行时数据区
(1) PC寄存器(或PC程序计数器)
JVM 中的PC寄存器是对物理PC寄存器的一种抽象模拟。
PC寄存器用来存储指向下一条指令的地址,也即将要执行的指令代码。由执行引擎读取下一条指令
(2) JVM栈
JVM栈是线程私有的,每个线程创建的同时都会创建JVM栈,JVM栈中存放当前线程中局部基本类型的变量(Java中定义的八种基本类型:boolean、char、byte、short、int、long、float、double)、部分的返回结果以及 Stack Frame,非基本类型的对象在JVM栈上仅存放一个指向堆的地址。
(3) 堆(Heap)
堆是JVM用来存储对象实例以及数组值的区域,可以认为Java中所有通过 new 创建的对象的内存都在此分配,Heap 中的对象的内存需要等待 GC 进行回收。
堆在 JVM 启动的时候就被创建,堆中储存了各种对象,这些对象被自动管理内存系统(Automatic Storage Management System),也就是常说的 “Garbage Collector(垃圾回收器)” 管理。这些对象无需、也无法显示地被销毁。
堆是 JVM 中所有线程共享的,因此在其上进行对象内存的分配均需要进行加锁,导致 new 对象的开销比较大。
JVM 将 Heap 分为两块:新生代 New Generation 和旧生代 Old Generation
(4) 方法区域(Method Area)
在Sun JDK中这块区域对应的为 Permanet Generation,又称为持久代。
方法区域存放所加载类的信息(名称、修饰符等)、类中的静态变量、类中定义为 final 类型的常量、类中的 Field 信息、类中的方法信息,当开发人员在程序中通过 Class 对象中的 getName,isInstance 等方法来获取信息时,这些数据都来源于方法区域,同时方法区域也是全局共享的,在一定条件下它也会被 GC,当方法区域需要使用的内存超过其允许的大小时,就会抛出 OutOfMemory 的错误信息。
(5) 运行时常量池(Runtime Constant Pool)
存放的为类中的固定常量信息、方法和Field的引用信息等,其空间从方法区域中分配。
(6) 本地方法堆栈(Native Method Stacks)
JVM 采用本地方法堆来支持native方法的执行,此区域用于存储每个native方法调用的状态。
3) 执行引擎
(1) 字节码解释器
是一种电脑程序,能够把高级编程语言一行一行直接翻译运行。解释器不会一次把整个程序翻译出来,只像一位“中间人”,每次运行程序时都要先转成另一种语言再作运行,因此解释器的程序运行速度比较缓慢。它每翻译一行程序叙述就立刻运行,然后再翻译下一行,再运行,如此不停地进行下去。它会先将源码翻译成另一种语言,以供多次运行而无需再经编译。其制成品无需依赖编译器而运行,程序运行速度比较快。
(2) 即时编译器 (Just In Time Compiler,JIT编译器)
即时编译器 (Just In Time Compiler,JIT编译器),是指一种在运行时期把字节码编译成原生机器码的技术,一句一句翻译源代码,但是会将翻译过的代码缓存起来以降低性能耗损。这项技术是被用来改善虚拟机的性能的。
在部分商用虚拟机中(如HotSpot),Java程序最初是通过解释器(Interpreter)进行解释执行的,当虚拟机发现某个方法或代码块的运行特别频繁时,就会把这些代码认定为“热点代码”。为了提高热点代码的执行效率,在运行时,虚拟机将会把这些代码编译成与本地平台相关的机器码,并进行各种层次的优化,完成这个任务的编译器称为即时编译器。
即时编译器并不是虚拟机必须的部分,Java虚拟机规范并没有规定Java虚拟机内必须要有即时编译器存在,更没有限定或指导即时编译器应该如何去实现。但是,即时编译器编译性能的好坏、代码优化程度的高低却是衡量一款商用虚拟机优秀与否的最关键的指标之一,它也是虚拟机中最核心且最能体现虚拟机技术水平的部分。
(3) 垃圾回收器
垃圾回收(Garbage Collection,GC),顾名思义就是释放垃圾占用的空间,防止内存泄露。有效的使用可以使用的内存,对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收。
4. JVM生命周期
Java实例对应一个独立运行的Java程序(进程级别)
1) 启动
启动一个 Java 程序,一个 JVM 实例就产生。拥有 public static void main(String[] args) 函数的 class 可以作为 JVM 实例运行的起点。
2) 运行
main() 作为程序初始线程的起点,任何其他线程均可由该线程启动。
JVM 内部有两种线程:守护线程和非守护线程。
main() 属于非守护线程,守护线程通常由 JVM 使用,程序可以指定创建的线程为守护线程。
3) 消亡
当程序中的所有非守护线程都终止时,JVM 才退出;若安全管理器允许,程序也可以使用 Runtime 类或者 System.exit() 来退出。
JVM执行引擎实例则对应了属于用户运行程序线程它是线程级别的。
5. JVM指令集
指令码 助记符 说明
0×00 nop 什么都不做
0×01 aconst_null 将null推送至栈顶
0×02 iconst_m1 将int型-1推送至栈顶
0×03 iconst_0 将int型0推送至栈顶
0×04 iconst_1 将int型1推送至栈顶
0×05 iconst_2 将int型2推送至栈顶
0×06 iconst_3 将int型3推送至栈顶
0×07 iconst_4 将int型4推送至栈顶
0×08 iconst_5 将int型5推送至栈顶
0×09 lconst_0 将long型0推送至栈顶
0x0a lconst_1 将long型1推送至栈顶
0x0b fconst_0 将float型0推送至栈顶
0x0c fconst_1 将float型1推送至栈顶
0x0d fconst_2 将float型2推送至栈顶
0x0e dconst_0 将double型0推送至栈顶
0x0f dconst_1 将double型1推送至栈顶
0×10 bipush 将单字节的常量值(-128~127)推送至栈顶
0×11 sipush 将一个短整型常量值(-32768~32767)推送至栈顶
0×12 ldc 将int, float或String型常量值从常量池中推送至栈顶
0×13 ldc_w 将int, float或String型常量值从常量池中推送至栈顶(宽索引)
0×14 ldc2_w 将long或double型常量值从常量池中推送至栈顶(宽索引)
0×15 iload 将指定的int型本地变量推送至栈顶
0×16 lload 将指定的long型本地变量推送至栈顶
0×17 fload 将指定的float型本地变量推送至栈顶
0×18 dload 将指定的double型本地变量推送至栈顶
0×19 aload 将指定的引用类型本地变量推送至栈顶
0x1a iload_0 将第0个int型本地变量推送至栈顶
0x1b iload_1 将第1个int型本地变量推送至栈顶
0x1c iload_2 将第2个int型本地变量推送至栈顶
0x1d iload_3 将第3个int型本地变量推送至栈顶
0x1e lload_0 将第0个long型本地变量推送至栈顶
0x1f lload_1 将第1个long型本地变量推送至栈顶
0×20 lload_2 将第2个long型本地变量推送至栈顶
0×21 lload_3 将第3个long型本地变量推送至栈顶
0×22 fload_0 将第0个float型本地变量推送至栈顶
0×23 fload_1 将第1个float型本地变量推送至栈顶
0×24 fload_2 将第2个float型本地变量推送至栈顶
0×25 fload_3 将第3个float型本地变量推送至栈顶
0×26 dload_0 将第0个double型本地变量推送至栈顶
0×27 dload_1 将第1个double型本地变量推送至栈顶
0×28 dload_2 将第2个double型本地变量推送至栈顶
0×29 dload_3 将第3个double型本地变量推送至栈顶
0x2a aload_0 将第0个引用类型本地变量推送至栈顶
0x2b aload_1 将第1个引用类型本地变量推送至栈顶
0x2c aload_2 将第2个引用类型本地变量推送至栈顶
0x2d aload_3 将第3个引用类型本地变量推送至栈顶
0x2e iaload 将int型数组指定索引的值推送至栈顶
0x2f laload 将long型数组指定索引的值推送至栈顶
0×30 faload 将float型数组指定索引的值推送至栈顶
0×31 daload 将double型数组指定索引的值推送至栈顶
0×32 aaload 将引用型数组指定索引的值推送至栈顶
0×33 baload 将boolean或byte型数组指定索引的值推送至栈顶
0×34 caload 将char型数组指定索引的值推送至栈顶
0×35 saload 将short型数组指定索引的值推送至栈顶
0×36 istore 将栈顶int型数值存入指定本地变量
0×37 lstore 将栈顶long型数值存入指定本地变量
0×38 fstore 将栈顶float型数值存入指定本地变量
0×39 dstore 将栈顶double型数值存入指定本地变量
0x3a astore 将栈顶引用型数值存入指定本地变量
0x3b istore_0 将栈顶int型数值存入第0个本地变量
0x3c istore_1 将栈顶int型数值存入第1个本地变量
0x3d istore_2 将栈顶int型数值存入第2个本地变量
0x3e istore_3 将栈顶int型数值存入第3个本地变量
0x3f lstore_0 将栈顶long型数值存入第0个本地变量
0×40 lstore_1 将栈顶long型数值存入第1个本地变量
0×41 lstore_2 将栈顶long型数值存入第2个本地变量
0×42 lstore_3 将栈顶long型数值存入第3个本地变量
0×43 fstore_0 将栈顶float型数值存入第0个本地变量
0×44 fstore_1 将栈顶float型数值存入第1个本地变量
0×45 fstore_2 将栈顶float型数值存入第2个本地变量
0×46 fstore_3 将栈顶float型数值存入第3个本地变量
0×47 dstore_0 将栈顶double型数值存入第0个本地变量
0×48 dstore_1 将栈顶double型数值存入第1个本地变量
0×49 dstore_2 将栈顶double型数值存入第2个本地变量
0x4a dstore_3 将栈顶double型数值存入第3个本地变量
0x4b astore_0 将栈顶引用型数值存入第0个本地变量
0x4c astore_1 将栈顶引用型数值存入第1个本地变量
0x4d astore_2 将栈顶引用型数值存入第2个本地变量
0x4e astore_3 将栈顶引用型数值存入第3个本地变量
0x4f iastore 将栈顶int型数值存入指定数组的指定索引位置
0×50 lastore 将栈顶long型数值存入指定数组的指定索引位置
0×51 fastore 将栈顶float型数值存入指定数组的指定索引位置
0×52 dastore 将栈顶double型数值存入指定数组的指定索引位置
0×53 aastore 将栈顶引用型数值存入指定数组的指定索引位置
0×54 bastore 将栈顶boolean或byte型数值存入指定数组的指定索引位置
0×55 castore 将栈顶char型数值存入指定数组的指定索引位置
0×56 sastore 将栈顶short型数值存入指定数组的指定索引位置
0×57 pop 将栈顶数值弹出 (数值不能是long或double类型的)
0×58 pop2 将栈顶的一个(long或double类型的)或两个数值弹出(其它)
0×59 dup 复制栈顶数值并将复制值压入栈顶
0x5a dup_x1 复制栈顶数值并将两个复制值压入栈顶
0x5b dup_x2 复制栈顶数值并将三个(或两个)复制值压入栈顶
0x5c dup2 复制栈顶一个(long或double类型的)或两个(其它)数值并将复制值压入栈顶
0x5d dup2_x1 <待补充>
0x5e dup2_x2 <待补充>
0x5f swap 将栈最顶端的两个数值互换(数值不能是long或double类型的)
0×60 iadd 将栈顶两int型数值相加并将结果压入栈顶
0×61 ladd 将栈顶两long型数值相加并将结果压入栈顶
0×62 fadd 将栈顶两float型数值相加并将结果压入栈顶
0×63 dadd 将栈顶两double型数值相加并将结果压入栈顶
0×64 isub 将栈顶两int型数值相减并将结果压入栈顶
0×65 lsub 将栈顶两long型数值相减并将结果压入栈顶
0×66 fsub 将栈顶两float型数值相减并将结果压入栈顶
0×67 dsub 将栈顶两double型数值相减并将结果压入栈顶
0×68 imul 将栈顶两int型数值相乘并将结果压入栈顶
0×69 lmul 将栈顶两long型数值相乘并将结果压入栈顶
0x6a fmul 将栈顶两float型数值相乘并将结果压入栈顶
0x6b dmul 将栈顶两double型数值相乘并将结果压入栈顶
0x6c idiv 将栈顶两int型数值相除并将结果压入栈顶
0x6d ldiv 将栈顶两long型数值相除并将结果压入栈顶
0x6e fdiv 将栈顶两float型数值相除并将结果压入栈顶
0x6f ddiv 将栈顶两double型数值相除并将结果压入栈顶
0×70 irem 将栈顶两int型数值作取模运算并将结果压入栈顶
0×71 lrem 将栈顶两long型数值作取模运算并将结果压入栈顶
0×72 frem 将栈顶两float型数值作取模运算并将结果压入栈顶
0×73 drem 将栈顶两double型数值作取模运算并将结果压入栈顶
0×74 ineg 将栈顶int型数值取负并将结果压入栈顶
0×75 lneg 将栈顶long型数值取负并将结果压入栈顶
0×76 fneg 将栈顶float型数值取负并将结果压入栈顶
0×77 dneg 将栈顶double型数值取负并将结果压入栈顶
0×78 ishl 将int型数值左移位指定位数并将结果压入栈顶
0×79 lshl 将long型数值左移位指定位数并将结果压入栈顶
0x7a ishr 将int型数值右(符号)移位指定位数并将结果压入栈顶
0x7b lshr 将long型数值右(符号)移位指定位数并将结果压入栈顶
0x7c iushr 将int型数值右(无符号)移位指定位数并将结果压入栈顶
0x7d lushr 将long型数值右(无符号)移位指定位数并将结果压入栈顶
0x7e iand 将栈顶两int型数值作“按位与”并将结果压入栈顶
0x7f land 将栈顶两long型数值作“按位与”并将结果压入栈顶
0×80 ior 将栈顶两int型数值作“按位或”并将结果压入栈顶
0×81 lor 将栈顶两long型数值作“按位或”并将结果压入栈顶
0×82 ixor 将栈顶两int型数值作“按位异或”并将结果压入栈顶
0×83 lxor 将栈顶两long型数值作“按位异或”并将结果压入栈顶
0×84 iinc 将指定int型变量增加指定值,可以有两个变量,分别表示index, const,index指第index个int型本地变量,const增加的值
0×85 i2l 将栈顶int型数值强制转换成long型数值并将结果压入栈顶
0×86 i2f 将栈顶int型数值强制转换成float型数值并将结果压入栈顶
0×87 i2d 将栈顶int型数值强制转换成double型数值并将结果压入栈顶
0×88 l2i 将将栈顶long型数值强制转换成int型数值并将结果压入栈顶
0×89 l2f 将将栈顶long型数值强制转换成float型数值并将结果压入栈顶
0x8a l2d 将将栈顶long型数值强制转换成double型数值并将结果压入栈顶
0x8b f2i 将将栈顶float型数值强制转换成int型数值并将结果压入栈顶
0x8c f2l 将将栈顶float型数值强制转换成long型数值并将结果压入栈顶
0x8d f2d 将将栈顶float型数值强制转换成double型数值并将结果压入栈顶
0x8e d2i 将将栈顶double型数值强制转换成int型数值并将结果压入栈顶
0x8f d2l 将将栈顶double型数值强制转换成long型数值并将结果压入栈顶
0×90 d2f 将将栈顶double型数值强制转换成float型数值并将结果压入栈顶
0×91 i2b 将将栈顶int型数值强制转换成byte型数值并将结果压入栈顶
0×92 i2c 将将栈顶int型数值强制转换成char型数值并将结果压入栈顶
0×93 i2s 将将栈顶int型数值强制转换成short型数值并将结果压入栈顶
0×94 lcmp 比较栈顶两long型数值大小,并将结果(1,0,-1)压入栈顶
0×95 fcmpl 比较栈顶两float型数值大小,并将结果(1,0,-1)压入栈顶;当其中一个数值为NaN时,将-1压入栈顶
0×96 fcmpg 比较栈顶两float型数值大小,并将结果(1,0,-1)压入栈顶;当其中一个数值为NaN时,将1压入栈顶
0×97 dcmpl 比较栈顶两double型数值大小,并将结果(1,0,-1)压入栈顶;当其中一个数值为NaN时,将-1压入栈顶
0×98 dcmpg 比较栈顶两double型数值大小,并将结果(1,0,-1)压入栈顶;当其中一个数值为NaN时,将1压入栈顶
0×99 ifeq 当栈顶int型数值等于0时跳转
0x9a ifne 当栈顶int型数值不等于0时跳转
0x9b iflt 当栈顶int型数值小于0时跳转
0x9c ifge 当栈顶int型数值大于等于0时跳转
0x9d ifgt 当栈顶int型数值大于0时跳转
0x9e ifle 当栈顶int型数值小于等于0时跳转
0x9f if_icmpeq 比较栈顶两int型数值大小,当结果等于0时跳转
0xa0 if_icmpne 比较栈顶两int型数值大小,当结果不等于0时跳转
0xa1 if_icmplt 比较栈顶两int型数值大小,当结果小于0时跳转
0xa2 if_icmpge 比较栈顶两int型数值大小,当结果大于等于0时跳转
0xa3 if_icmpgt 比较栈顶两int型数值大小,当结果大于0时跳转
0xa4 if_icmple 比较栈顶两int型数值大小,当结果小于等于0时跳转
0xa5 if_acmpeq 比较栈顶两引用型数值,当结果相等时跳转
0xa6 if_acmpne 比较栈顶两引用型数值,当结果不相等时跳转
0xa7 goto 无条件跳转
0xa8 jsr 跳转至指定16位offset位置,并将jsr下一条指令地址压入栈顶
0xa9 ret 返回至本地变量指定的index的指令位置(一般与jsr, jsr_w联合使用)
0xaa tableswitch 用于switch条件跳转,case值连续(可变长度指令)
0xab lookupswitch 用于switch条件跳转,case值不连续(可变长度指令)
0xac ireturn 从当前方法返回int
0xad lreturn 从当前方法返回long
0xae freturn 从当前方法返回float
0xaf dreturn 从当前方法返回double
0xb0 areturn 从当前方法返回对象引用
0xb1 return 从当前方法返回void
0xb2 getstatic 获取指定类的静态域,并将其值压入栈顶
0xb3 putstatic 为指定的类的静态域赋值
0xb4 getfield 获取指定类的实例域,并将其值压入栈顶
0xb5 putfield 为指定的类的实例域赋值
0xb6 invokevirtual 调用实例方法
0xb7 invokespecial 调用超类构造方法,实例初始化方法,私有方法
0xb8 invokestatic 调用静态方法
0xb9 invokeinterface 调用接口方法
0xba –
0xbb new 创建一个对象,并将其引用值压入栈顶
0xbc newarray 创建一个指定原始类型(如int, float, char…)的数组,并将其引用值压入栈顶
0xbd anewarray 创建一个引用型(如类,接口,数组)的数组,并将其引用值压入栈顶
0xbe arraylength 获得数组的长度值并压入栈顶
0xbf athrow 将栈顶的异常抛出
0xc0 checkcast 检验类型转换,检验未通过将抛出ClassCastException
0xc1 instanceof 检验对象是否是指定的类的实例,如果是将1压入栈顶,否则将0压入栈顶
0xc2 monitorenter 获得对象的锁,用于同步方法或同步块
0xc3 monitorexit 释放对象的锁,用于同步方法或同步块
0xc4 wide 当本地变量的索引超过255时使用该指令扩展索引宽度。
0xc5 multianewarray create a new array of dimensions dimensions with elements of type identified by class reference in constant pool index (indexbyte1 << 8 + indexbyte2); the sizes of each dimension is identified by count1, [count2, etc.]
0xc6 ifnull If value is null, branch to instruction at branchoffset (signed short constructed from unsigned bytes branchbyte1 << 8 + branchbyte2)
0xc7 ifnonnull If value is not null, branch to instruction at branchoffset (signed short constructed from unsigned bytes branchbyte1 << 8 + branchbyte2)
0xc8 goto_w Goes to another instruction at branchoffset (signed int constructed from unsigned bytes branchbyte1 << 24 + branchbyte2 << 16 + branchbyte3 << 8 + branchbyte4)
0xc9 jsr_w Jump to subroutine at branchoffset (signed int constructed from unsigned bytes branchbyte1 << 24 + branchbyte2 << 16 + branchbyte3 << 8 + branchbyte4) and place the return address on the stack
0xca breakpoint Reserved for breakpoints in Java debuggers; should not appear in any class file
0xcb-0xfd 未命名 These values are currently unassigned for opcodes and are reserved for future use
0xfe impdep1 Reserved for implementation-dependent operations within debuggers; should not appear in any class file
0xff impdep2 Reserved for implementation-dependent operations within debuggers; should not appear in any class file
以上指令集表,方便查看Java字节码文件。
浙公网安备 33010602011771号