0.前言
Java 是当今世界使用最广泛的技术平台之一。使用 Java 或 JVM 的一些技术包括:
- Apache spark用于大数据处理,数据分析在JVM上运行;
- 用于数据流的Apache NiFi在内部使用的也是 JVM;
- 现代 Web 和移动应用程序开发中使用的React native使用 的也包含JVM 和 java 线程。
1.虚拟化
JVM 基于虚拟化技术(应用程序级虚拟化),它基本上是裸机硬件之上的抽象层。
JVM 是应用程序级虚拟化的一个例子,它基本上是主机操作系统上的一个额外层。我们可以在操作系统内的沙箱中编译和运行 Java 程序,而不是直接使用操作系统例程。
2.代码编译与字节码
Java 代码在执行前要经过一系列转换。第一个编译过程从 java 编译器或javac开始,javac 将 java 代码转换为中间字节码,可以使用javap 等反汇编工具读取,字节码独立于计算机的平台架构,这是 java 编程语言具有可移植性的根本原因。
3.Java类结构
以下是使用 javap 反汇编后类文件的组成部分。
组成部分 | 说明 |
---|---|
魔数(Magic Number) | 0xCAFEBABE |
类文件的版本(Magic Number) | 类文件的次要和主要版本 |
常量池(Constang Pool) | 类的常量池 |
访问标记(Access Flags) | 例如类是否为抽象、静态等等 |
当前类(This Class) | 当前类的名称 |
超类(Super Class) | 超类的名称 |
接口(Inrerfaces) | 类中所有的接口 |
方法(Methods) | 类中所有的方法 |
每个类文件都以一个魔法数字开头,用于确认给定文件是类文件,接下来是类格式的版本,用于检查 JVM 版本与用于编译类文件的版本的兼容性。如果存在任何不匹配,则会抛出UnsupportedClassVersionError 。
其他组件用于存储类中使用的常量,访问标志存储访问说明符(公共、私有、抽象等)。类似地,其他组件存储类中使用的超类名称、接口和方法。
public class HelloWorld {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
System.out.println("Hello World");
}
}
}
反汇编上面的 hello world 程序,结果如下是运行 hello world 程序,结果如下:
Classfile /home/xxxxxxxx/Documents/learning/java/jvm/HelloWorld.class
Last modified 7 Oct 2021; size 478 bytes
SHA-256 checksum 93080c0483aa97ce4c226f31f22ed95c633e0da00b997fe229bfa676f6fb53c0
Compiled from "HelloWorld.java"
public class HelloWorld
minor version: 0
major version: 52
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #5 // HelloWorld
super_class: #6 // java/lang/Object
interfaces: 0, fields: 0, methods: 2, attributes: 1
相应的机器语言指令将会是:
0: iconst_0
1: istore_1
2: iload_1
3: bipush 10
5: if_icmpge 22
8: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
11: ldc #3 // String Hello World
13: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
16: iinc 1, 1
19: goto 2
22: return
源码解读:将常量存储到JVM堆栈中加载并与10进行比较,如果数字大于或等于10,则停止并退出函数,否则打印“ Hello world ”,直到满足条件。
4.解释和类加载
将给定的源代码转换为字节后,JVM 将开始逐行解释类文件。JVM 是基于堆栈的解释器机器,因此 JVM 将使用内部堆栈来存储执行结果,而不是使用CPU 寄存器。此过程从将执行所需的所有类加载到方法缓存中开始。
4.1.类加载
代码执行中的三个重要过程:
-
加载:加载执行所需的所有类。
-
链接:验证类文件并解析所有符号引用。
-
初始化:初始化程序中定义的所有静态变量。
4.1.1.类加载器的类型
-
启动类加载器: Bootstrap 类加载器将加载源代码中的所有核心类,包括包含 main 函数的类。
-
扩展类加载器:扩展类加载器以引导类作为其父类加载器。如果核心 Java 类中的任何方法被重写,则扩展类加载器将加载该类而不是原始类。
-
应用程序类加载器:加载类路径中找到的所有类。如果类路径中未找到某个类,则会抛出classnotfound异常。
5.热点编译
C++ 实现遵循零开销原则:您不用的东西就不用付费。而且,您用的东西,您编写的代码再好不过了。Bjarne Stroustrup
ava 是一种蓝领语言。它不是博士论文材料,而是一种工作语言。James Gosling
热点编译是一种使 Java 代码执行速度比 C 和 C++ 等语言更快、更高效的方法。热点编译是一种方法,在解释过程进行时,分析器将开始收集有关执行的信息,基于此信息,即时 ( JIT)编译器将应用一组优化。
6.Java内存分配及内存管理
JVM 中运行的所有线程都会有一个公共堆,运行程序所需的所有内存都会从这个堆中分配,如果所需内存大于当前 JVM 堆内存,则会引发内存超出范围的异常。默认情况下,对象是可变的,除非它们由 final 关键字定义。
一个线程创建的任何对象都可以被另一个线程访问,可以通过一种称为互斥锁或互斥锁的技术来避免异常情况(一个线程引用的内存被另一个线程改变)。
7.JIT编译
此过程也称为配置文件引导优化。配置文件将跟踪当前正在运行的子系统的信息,当值达到某个阈值时将应用一组优化。一些配置文件引导优化策略如下:
- 基于计数器的优化: Profiler 将保留方法调用次数的计数。如果计数达到大于某个阈值的值,则将缓存该方法,下次无需再次解释该方法,而是直接从缓存中获取该值;
- 堆栈上替换:缓存不经常使用但包含循环的方法;
- 内联:例如,用更高效的代码替换的过程。