走进JVM

 

1从编译到运行

JVM虚拟机笔记(1) 随笔 第1张

SRE实战 互联网时代守护先锋,助力企业售后服务体系运筹帷幄!一键直达领取阿里云限量特价优惠。

 

1.1 java

源码编译器:jdk中包含的编译工具(javac),将java源码编译为class字节码文件。

JVM执行引擎运行字节码文件指令,有下面两种方式:

2) 解释运行

    解释一句后就提交计算机执行一句,即对字节码逐条解释执行,并不形成目标程序

1) 编译运行

  java 程序最初是仅仅通过解释器解释执行的,这种方式的执行速度相对会比较慢,尤其当某个方法或代码块运行的特别频繁时,这种方式的执行效率就显得很低。于是后来在虚拟机中引入了 JIT 编译器(即时编译器),当虚拟机发现某个方法或代码块运行特别频繁时,就会把这些代码认定为“Hot Spot Code”(热点代码),为了提高热点代码的执行效率,在运行时,虚拟机将会把这些代码编译成与本地平台相关的机器码,并进行各层次的优化,完成这项任务的正是 JIT 编译器。

1.2 为什么说java语言一次编译,到处运行。为什么c++不行?

对于C/C++这类高级计算机语言,它们的编译器(例如Unix下的CC命令、Windows下的CL命令)都是可以把源码直接编译成计算机可以认识的机器码,如exe、dll之类的文件,然后直接运行

2 运行时数据区

2.1 初步认识

JVM虚拟机笔记(1) 随笔 第2张

1 类加载器

负责动态加载Java类到Java虚拟机的内存空间中

2 运行时数据区

 堆(内存大,共享,存放几乎所有的对象实例和数组,是gc主要管理区域)、

 方法区(共享,存放类信息,常量,静态变量,即时编译后的代码等)、

 java栈(线程私有,每个方法被执行的时候都会同时创建一个栈帧,栈它是用于支持续虚拟机进行方法调用和方法执行的数据结构)、

 程序计数器(线程私有,它是当前线程所执行的字节码的行号指示器,字节码解释器工作时通过改变该计数器的值来选择下一条需要执行的字节码指令,分支、跳转、循环等基础功能都要依赖它来实现)、

 本地方法栈(线程私有,该区域与虚拟机栈所发挥的作用非常相似,只是虚拟机栈为虚拟机执行 Java 方法服务,而本地方法栈则为使用到的本地操作系统(Native)方法服务。)

3 执行引擎

对比物理机的执行引擎是直接建立在处理器、硬件、指令集和操作系统层面上的,而虚拟机的执行引擎则是由自己实现的,因此可以自行制定指令集与执行引擎的结构体系,并且能够执行那些不被硬件直接支持的指令格式。

JVM虚拟机笔记(1) 随笔 第3张

 

4 垃圾收集器

 

2.2 认识虚拟机栈

2.2.1 概念

局部变量表

  用于存放方法参数和方法内部定义的局部变量。在Java程序编译为Class文件时,就在方法code属性的max_locals数据项中确定了该方法所需要分配的局部变量表的最大容量。容量以变量槽(Slot)为最小单位,(虚拟机规范没有明确指定一个slot应占内存空间大小,可以随处理器、操作心痛或虚拟机的不同而发送变化)

操作数栈

   操作数栈的最大深度也在编译时写入到Code属性的max_stacks数据项中,操作数栈的每一个元素可以是任意的java数据类型,包括long和double。32位数据类型所占的栈容量为1,64位所占的栈容量为2。概念模型中,两个栈帧作为虚拟机栈的元素,是完全相互独立的。但在大多数虚拟机的实现里,会做一些优化处理,令两个栈帧出现一部分重叠。

动态链接

  每个栈帧都包含一个指向运行时常量池中该栈帧所属的方法引用,为了支持方法调用过程中的动态链接。  

方法返回地址

  当一个方法被执行后,有两种方式退出该方法:执行引擎遇到了任意一个方法返回的字节码指令或遇到了异常,并且该异常没有在方法体内得到处理。无论采用何种退出方式,在方法退出之后,都需要返回到方法被调用的位置,程序才能继续执行。方法返回时可能需要在栈帧中保存一些信息,用来帮助恢复它的上层方法的执行状态。一般来说,方法正常退出时,调用者的 PC 计数器的值就可以作为返回地址,栈帧中很可能保存了这个计数器值,而方法异常退出时,返回地址是要通过异常处理器来确定的,栈帧中一般不会保存这部分信息。方法退出的过程实际上等同于把当前栈帧出站,因此退出时可能执行的操作有:恢复上层方法的局部变量表和操作数栈,如果有返回值,则把它压入调用者栈帧的操作数栈中,调整 PC 计数器的值以指向方法调用指令后面的一条指令。

 

2.2.2 示例

这个示例主要是让我们对java栈的一些结构有一个比较深刻的认识,涉及到一些java指令码以及方法调用的知识,会在后面详细的整理。

编译与反编译java代码

 java源文件:

 1 package cn.timyag.jvmlearn.cp;
 2 
 3 /**
 4  * @author yangjf
 5  * @version 0.1.0
 6  * @time 2019/2/27/027
 7  */
 8 public class HelloWorldDemo {
 9     /**
10      * 常量   静态变量
11      */
12     private final int i = 0;
13     private static int k = 0;
14 
15     /**
16      * 成员变量
17      */
18     private Object obj = new Object();
19     private int sss = 0;
20 
21     public int methodOne(int i) {//成员方法  this
22         int j = 0;
23         int sum = i + j;
24         Object acb = obj;
25         long start = System.currentTimeMillis();
26         methodTwo();
27         return sum;
28     }
29 
30     public void methodTwo() {
31         int s = 0;
32         int s2 = s + 3;
33         return;
34     }
35 
36 
37     public static void main(String[] args) {
38         HelloWorldDemo helloWorldDemo = new HelloWorldDemo();
39         int a = helloWorldDemo.methodOne(4);
40         System.out.println(a);
41     }
42 }

 

编译与反编译命令

# 编译
javac HelloWorldDemo.java -encoding utf8

# 反编译
javap -v HelloWorldDemo.class >> 0304.txt

 

反编译结果(java指令集)

... 
public int methodOne(int); descriptor: (I)I flags: ACC_PUBLIC Code: stack=2, locals=7, args_size=2 0: iconst_0 //将int型0推送至栈顶 1: istore_2 //将栈顶int型数值存入第2个本地变量 2: iload_1 //将第1个int型本地变量推送至栈顶 3: iload_2 //将第2个int型本地变量推送至栈顶 4: iadd //将栈顶两int型数值相加并将结果压入栈顶 5: istore_3 //将栈顶int型数值存入第3个本地变量 6: aload_0 //将第0个引用类型本地变量推送至栈顶 7: getfield #4 // Field obj:Ljava/lang/Object; //获取指定类的实例域,并将其值压入栈顶(#"号表示索引常量池中的第几项常量数据) 10: astore 4 //将栈顶引用型数值存入指定本地变量(第四个) 12: invokestatic #6 // Method java/lang/System.currentTimeMillis:()J //调用静态方法 15: lstore 5 //将栈顶int型数值存入指定本地变量 17: aload_0 //将第0个引用类型本地变量推送至栈顶 18: invokevirtual #7 // Method methodTwo:()V //调用实例方法 21: iload_3 22: ireturn //从当前方法返回void LineNumberTable: line 22: 0 line 23: 2 line 24: 6 line 25: 12 line 26: 17 line 27: 21 LocalVariableTable: Start Length Slot Name Signature 0 23 0 this Lcn/timyag/jvmlearn/cp/HelloWorldDemo; 0 23 1 i I 2 21 2 j I 6 17 3 sum I 12 11 4 acb Ljava/lang/Object; 17 6 5 start J MethodParameters: Name Flags i
...

 

栈结构

JVM虚拟机笔记(1) 随笔 第4张

 

 

 2.3 方法调用

这里先整理一下 类结构和类加载的知识,再来整理这里

 

问题

 

动态链接具体是怎么存的, 怎么使用的?

程序计数器如何工作?

调用一个方法时,是将方法对应的字节码复制了一份到本地线程,还是每次去方法区读取,读一行运行一行?

方法异常返回时,处理流程是如何?

 

 

参考

https://juejin.im/post/5aca2c366fb9a028c97a5609

http://wiki.jikexueyuan.com/project/java-vm/storage.html

《深入理解JAVA虚拟机第二版》--周志明

扫码关注我们
微信号:SRE实战
拒绝背锅 运筹帷幄