JVM那些事儿之执行原理(二)
案例
当一个java程序员把第一个helloworld程序敲出来后,它是怎么在console里打印出“helloworld”的?
编写Java程序
public class HelloWorld {
public static void main(String[] args) {
System.out.println(“hello world!”);
}
}
通过javac将.java文件编译成.class字节码文件
> javac HelloWorld.java
使用 javap查看.class字节码文件
> javap -verbose HelloWorld.class
此时可以看到这样一个字符串。这个字符串就是编译后的内容
Classfile /jvmtest/HelloWorld.class
Last modified 2017-8-12; size 426 bytes
MD5 checksum 4efac412ef483c8a3fe7489c87d15c8c
Compiled from "HelloWorld.java"
public class HelloWorld
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #6.#15 // java/lang/Object."<init>":()V
#2 = Fieldref #16.#17 // java/lang/System.out:Ljava/io/PrintStream;
#3 = String #18 // hello world!
#4 = Methodref #19.#20 // java/io/PrintStream.println:(Ljava/lang/String;)V
#5 = Class #21 // HelloWorld
#6 = Class #22 // java/lang/Object
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 main
#12 = Utf8 ([Ljava/lang/String;)V
#13 = Utf8 SourceFile
#14 = Utf8 HelloWorld.java
#15 = NameAndType #7:#8 // "<init>":()V
#16 = Class #23 // java/lang/System
#17 = NameAndType #24:#25 // out:Ljava/io/PrintStream;
#18 = Utf8 hello world!
#19 = Class #26 // java/io/PrintStream
#20 = NameAndType #27:#28 // println:(Ljava/lang/String;)V
#21 = Utf8 HelloWorld
#22 = Utf8 java/lang/Object
#23 = Utf8 java/lang/System
#24 = Utf8 out
#25 = Utf8 Ljava/io/PrintStream;
#26 = Utf8 java/io/PrintStream
#27 = Utf8 println
#28 = Utf8 (Ljava/lang/String;)V
{
public HelloWorld();
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 1: 0
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String hello world!
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 3: 0
line 4: 8
}
SourceFile: "HelloWorld.java"
执行.class文件
> java HelloWorld
此时控制台会打印出
> hello world!
执行原理
如何生成.class文件咱们先不讲,反正文件是有了。咱们就来讲讲拿到这个文件后,JVM在执行这个文件时的过程。
先说明一下JVM到底是什么语言开发的?Java的解释器肯定不是Java开发了,其实,JVM主要是C/C++开发的,里面还有很多汇编语言。我们在执行.class文件时采用的方式是解释执行。
我们先说一个最精简的原理。
前面我们已经看到.class文件里面,会分成不同的方法。JVM在解释的时候,先把内存什么的规划好(具体怎么规划的后面讲),然后大概看一下有哪些方法,把这些方法都转换成机器码保存起来。当JVM自检结束后,将会先从main方法开始执行。
具体执行依赖的是C/C++的函数指针技术,这个技术让JVM可以直接执行一段机器码,而前面我们已经把Java的方法都转换成机器码保存起来了,这时候利用这个技术直接执行机器码就好了。
大家注意了,前面说的这段算是JVM最精彩的部分,为什么呢?刚开始的JVM可不是这样的,所以Java刚出来的时候,由于执行速度慢,被大家诟病,后来改了这个方案后,再加上将字节码转换成机器码的过程不断改进、优化,现在的Java程序在某些方面已经达到甚至超越了C/C++的速度。
以后再有人跟你说java速度慢,你就可以怼回去了。
体系结构
类装载子系统
负责把类从文件系统中装入内存
GC子系统
垃圾收集器的主要工作室自动回收不再运行的程序引用对象所占用的内存,此外,它还可能负责那些还在使用的对象,以减少的堆碎片。
内存区
用于存储字节码,程序运行时创建的对象,传递给方法的参数,返回值,局部变量和中间计算结果。
执行引擎
1、最简单的:一次性解释字节码。
2、快,但消耗内存的:“即时编译器”,第一次被执行的字节码会被编译成机器代码,放入缓存,以后调用可以重用。
3、自适应优化器,虚拟机开始的时候会解释字节码,但是会监视运行中程序的活动,并记录下使用最频繁的代码段。程序运行的时候,虚拟机只把使用最频繁的代码编译成本地代码,其他的代码由于使用的并不频繁,继续保留为字节码–由虚拟机继续解释他们。一般可以使java虚拟机80%~90%的时间里执行被优化过的本地代码,只需要编译10%~20%对性能优影响的代码。
4、由硬件芯片组成,他用本地方法执行java字节码,这种执行引擎实际上是内嵌在芯片里的。

本文探讨了JVM执行Java程序的过程,从编译 HelloWorld 程序到JVM如何解释执行字节码。JVM主要由C/C++编写,采用函数指针技术执行机器码。早期因速度慢受批评,但现在通过优化已能与C/C++媲美。文章还提到了类装载子系统、GC子系统、内存区和执行引擎的工作原理。
1201

被折叠的 条评论
为什么被折叠?



