Java虚拟机如何运行Java字节码?
一、Java的class文件的内容
1.首先编写一个简单的代码
public class StringDemo {
public static void main(String[] args) {
String s=new String("hello world");
System.out.println(s);
}
}
2.运行程序编译成class文件,class文件的查看可以用Java class文件分析工具 -- Classpy
3.这种字节码文件我们肯定看不懂,所以我们可以通过指令Javap来转换成我们人类可以看懂的
4.打开StringDemo.txt文件,即下图的内容
public class com.dada.demo.controller.StringDemo
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #8.#24 // java/lang/Object."<init>":()V
#2 = Class #25 // java/lang/String
#3 = String #26 // hello world
#4 = Methodref #2.#27 // java/lang/String."<init>":(Ljava/lang/String;)V
#5 = Fieldref #28.#29 // java/lang/System.out:Ljava/io/PrintStream;
#6 = Methodref #30.#31 // java/io/PrintStream.println:(Ljava/lang/String;)V
#7 = Class #32 // com/dada/demo/controller/StringDemo
#8 = Class #33 // java/lang/Object
#9 = Utf8 <init>
#10 = Utf8 ()V
#11 = Utf8 Code
#12 = Utf8 LineNumberTable
#13 = Utf8 LocalVariableTable
#14 = Utf8 this
#15 = Utf8 Lcom/dada/demo/controller/StringDemo;
#16 = Utf8 main
#17 = Utf8 ([Ljava/lang/String;)V
#18 = Utf8 args
#19 = Utf8 [Ljava/lang/String;
#20 = Utf8 s
#21 = Utf8 Ljava/lang/String;
#22 = Utf8 SourceFile
#23 = Utf8 StringDemo.java
#24 = NameAndType #9:#10 // "<init>":()V
#25 = Utf8 java/lang/String
#26 = Utf8 hello world
#27 = NameAndType #9:#34 // "<init>":(Ljava/lang/String;)V
#28 = Class #35 // java/lang/System
#29 = NameAndType #36:#37 // out:Ljava/io/PrintStream;
#30 = Class #38 // java/io/PrintStream
#31 = NameAndType #39:#34 // println:(Ljava/lang/String;)V
#32 = Utf8 com/dada/demo/controller/StringDemo
#33 = Utf8 java/lang/Object
#34 = Utf8 (Ljava/lang/String;)V
#35 = Utf8 java/lang/System
#36 = Utf8 out
#37 = Utf8 Ljava/io/PrintStream;
#38 = Utf8 java/io/PrintStream
#39 = Utf8 println
{
public com.dada.demo.controller.StringDemo();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/dada/demo/controller/StringDemo;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=2, args_size=1
0: new #2 // class java/lang/String
3: dup
4: ldc #3 // String hello world
6: invokespecial #4 // Method java/lang/String."<init>":(Ljava/lang/String;)V
9: astore_1
10: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
13: aload_1
14: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
17: return
LineNumberTable:
line 5: 0
line 6: 10
line 7: 17
LocalVariableTable:
Start Length Slot Name Signature
0 18 0 args [Ljava/lang/String;
10 8 1 s Ljava/lang/String;
}
SourceFile: "StringDemo.java"
5.上面的内容我们可以根据jvm指令码表,就知道什么意思啦
二、Java运行时数据区
1.方法区
Jvm用来存储加载类的信息、常量、静态变量、编译后的代码等数据
2.堆内存
Jvm启动时创建,存放对象的实例。垃圾回收器主要就是管理堆内存。
3.虚拟机栈
虚拟机栈,每个线程都在这个空间有一个私有的空间。线程栈由多个栈帧组成,一个线程执行一个或多个方法,一个方法对应一个栈帧。栈帧的内容包含:局部变量表、操作数栈、动态链接、方法返回地址、附加信息等。栈内存默认最大1M,超出则抛出StackOverflowError。
4.本地方法栈
和虚拟机栈功能类似,虚拟机栈是为虚拟机执行JAVA方法而准备的,本地方法栈是为虚拟机使用Native本地方法而准备的。
5.程序计数器
程序计数器记录当前线程执行字节码的位置,存储的是字节码指令地址,如果执行Native方法,则计数器值为空。每个线程在这个空间有一个私有的空间,占用内存空间很少。CPU同一时间,只会执行一条线程中的指令。JVM多线程会轮流切换并分配CPU执行时间的方式。为了线程切换后,需要通过程序计数器,来恢复正确的执行位置。
在了解上面的基础知识后,我们开始了解具体的执行过程。
三、Java虚拟机如何运行Java字节码?
标准JDK中的HotSpot是如何运行Java字节码的呢?(常见的虚拟机分三种1.HotSpot 2. JRockit 3.IBM的jvma)
需要从两个角度考虑:
虚拟机角度
底层硬件角度
1.1虚拟机角度
1.1.1从虚拟机角度是如何运行Java字节码的?
①首先会将Java代码编译成的class文件加载到JVM
②加载后的Java类会存放到方法区中。
③实际运行时,JVM会执行方法区内的代码。
④运行时,每当调用进入一个Java方法,JVM会在当前线程的Java方法栈中生成一个栈帧。
⑤退出执行的Java方法时,无论是不是正常返回,JVM都会弹出并舍弃掉当前线程的当前栈帧。
1.1.2栈帧是什么?
①用于存放局部变量、字节码的操作数
②栈帧的大小是提前计算好的。
③JVM不要求栈帧在内存空间内连续分布。
1.1.3JVM中内存组成部分有哪些?线程共享的是哪些部分?线程私有的是哪些部分?
线程共享:
①方法区:存放加载后的Java类
②堆:创建的对象实例
线程私有:
①Java方法栈: 面向Java方法
②本地方法栈: 面向C++写的native方法
③PC寄存器: 存放各个线程执行位置。
2.1硬件角度
2.1.1从硬件角度角度是如何运行Java字节码的?
①Java字节码无法直接执行
②JVM需要将字节码翻译成机器码
2.1.2JVM(如HotSpot)如何将字节码翻译成机器码?
两种方法:
①解释执行-逐条将字节码翻译成机器码执行
②即时编译(Just-In-Time compilation, JIT)-将一个方法中的字节码都编译成机器码后再执行。
总结:
Hello.java-->被编译成Hello.class,然后被加载到jvm的虚拟机里,存放到方法区内。实际运行代码时执行方法区的代码,每当调用进入一个Java方法,JVM会在当前线程的Java方法栈中生成一个栈帧,在栈帧内操作数栈根据指令码的顺序操作本地变量表来实现代码的执行,退出执行的Java方法时,无论是不是正常返回,JVM都会弹出并舍弃掉当前线程的当前栈帧。
讲解的部分可能不是很明白,大家可以多查阅一些资料,反复按照这个执行过程理解,我相信你很快就会形成自己的理解的。