文章内容是学习过程中的知识总结,如有纰漏,欢迎指正
文章目录
以下是本篇文章正文内容
1. java运行过程
1.源码编译:通过Java源码编译器将Java代码编译成JVM字节码(.class文件)
2.类加载:通过ClassLoader及其子类来完成JVM的类加载
3.类执行:字节码被装入内存,进入JVM虚拟机,被解释器解释执行
2. jvm模型
由上面的图可以看出,JVM虚拟机中主要是由三部分构成,分别是类加载子系统、运行时数据区、执行引擎。
2.1. 类加载子系统
Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型。
2.2. 运行时数据区
Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。
这些区域有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而一直存在,有些区域则是依赖用户线程的启动和结束而建立和销毁。
2.3. 执行引擎
执行引擎用于执行JVM字节码指令,主要有两种方式,分别是解释执行和编译执行,区别在于,解释执行是在执行时翻译成虚拟机指令执行,而编译执行是在执行之前先进行编译再执行。
解释执行启动快,执行效率低。编译执行,启动慢,执行效率高。
垃圾回收器就是自动管理运行数据区的内存,将无用的内存占用进行清除,释放内存资源。
2.4. 本地方法库、本地库接口
在jdk的底层中,有一些实现是需要调用本地方法完成的(使用c或c++写的方法),就是通过本地库接口调用完成的。比如:System.currentTimeMillis()方法。
3. 类文件结构
想了解jvm后续的一切动作,先从字节码开始。它是一切发生的源头。
3.1. 测试案例
3.1.1. 源代码
/*
* 基本类结构
* */
public class ClassStruct {
private static String name = "JVM";
private static final int age = 18;
public static void main(String[] args) {
System.out.println("Hello " + name);
}
}
3.1.2. 编译
1)maven定义编译的版本
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
2)编译
mvn clean compile
3.2. 字节码结构
3.2.1. 二进制概览
1)vscode打开
2)class文件是一个二进制文件,转化后是16进制展示,实际上class文件就是一张表,它由以下数据项构成,这些数据项从头到尾严格按照以下顺序排列:
3)图示如下:
3.2.2. 魔数与版本
1)魔数:
CAFEBABE,咖啡宝宝,固定的。
2)版本号:
34,换成10进制就是52
jdk的版本标记映射关系:
说明编译用的是jdk8,我们改成1.6,重新执行 mvn clean compile ,再来查看class文件试试:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
</plugins>
</build>
扩展
在开发中,经常会遇到类似Unsupported major.minor version 51.0的错误,一般情况下都是JDK版本不匹配造成的。 虽然jdk代码在执行时基本上向下兼容,但是!开发环境和服务器环境jdk最好一致,不要尝试这个坑。
区分和理解两个环境:编译环境,运行环境
3.2.3. 常量池
再往下遵从相同的规律: 计数器(标注后面有多少个) + 对应个数的结构体
我们以常量池为例:
1)位置
2)结构说明
常量池记录了jvm内的一堆常量信息,这部分由 【2个字节计数】 + 【n个cp_info结构】组成
常量池中主要存放两大类常量:字面量(Literal)和符号引用(Symbolic References)。 字面量比较接近于 Java 语言层面的常量概念,如文本字符串、声明为 final 的常量值等。 而符号引用则属于编译原理方面的概念,包括了下面三类常量: 类和接口的全限定名(Fully Qualified Name)、字段的名称和描述符(Descriptor)、方法的名称和描述符
其中cp_info有多种类型:
- 直接类型,存的就是当前值,这种像Integer,Long等长度都是确定的
- 引用类型,存的是指向其他位置的指针
3)案例
下面以String为例,String是一种引用类,它会指向一个utf8类型来存储真实的信息
jdk提供了一个工具,javap,可以查看常量列表的详细内容:
javap -v ClassStruct.class
3.2.4. 其他信息
1)说明
常量池之后,是紧挨的一系列信息,这些信息大同小异,无非就是值、或者引用
(参考上面2.3.3里的表格和图例)
- 访问标记:public abstract 等信息
- 类索引,class类型,最终指向一个utf8,标记当前类的名字
- 父类,同上
- 接口,2字节记录数量,后面记录多个接口类型
- 接下来是字段、方法、属性,都是2字节记录后面多少个,后面紧跟对应的结构体类型
2)注意事项
要看懂javap后的格式,明白这些格式,可以轻松看懂class结构
组合类型
3)实例分析
总结
1.源码编译:通过Java源码编译器将Java代码编译成JVM字节码(.class文件)
2.类加载:通过ClassLoader及其子类来完成JVM的类加载
3.类执行:字节码被装入内存,进入JVM虚拟机,被解释器解释执行
4.JVM虚拟机中主要是由三部分构成,分别是类加载子系统、运行时数据区、执行引擎。