虚拟机
1.1 发展历程
1.1.1 java 往事
Java 诞生在一群懒惰、急躁而傲慢的程序天才之中。
1990 年 12 月,Sun 的工程师 Patrick Naughton 被当时糟糕的 Sun C++ 工具折磨的快疯了。他大声抱怨,并威胁要离开 Sun 转投当时在 Steve Jobs 领导之下的 NeXT 公司。领导层为了留住他,给他一个机会,启动了一个叫做 Stealth(秘密行动)的项目。
随着 James Gosling 等人的加入,这个项目更名为 Green。其目标是使用 C++ 为嵌入式设备开发一种新的基础平台技术,James Gosling 本人负责开发一个编辑器。正如人们事后分析的那样,这位天才的程序员太懒惰,所以没有把 C++ 学好,开发中碰了一头包。于是他决定开发一种新的编程语言。他把这种语言命名为 C++++--,意思是 C++ “加上一些好东西,减去一些坏东西”。显然这个糟糕的名字不可能长久,于是很快这种颇受同伴喜爱的小语言被命名为 Oak。
到了 1992 年 9 月,Oak 语言连同 Green OS 和一些应用程序一起发布在称做 Start 7 的小设备上,有了第一次精彩的亮相。随后,Sun 开了一家名为 FirstPerson 的公司,整个团队被转移到这家公司里研发机顶盒,以投标时代华纳公司的一个项目。这帮天才被技术狂热所鼓舞,开发出了一个高交互性的设备,结果没想到时代华纳公司和有线电视服务商并不愿意用户拥有那么大的控制权,从而在竞标之战中败给了 SGI。
Sun 无奈地关闭了 FirstPerson,召回了整个团队,java 的出路却没有因此而断送,随着互联网发展的涌动,java 开始离开嵌入式小设备,往互联网倾斜。1994 年,Oak 被命名为 Java,回到了激情澎湃的 IT 产业,抓住互联网的大潮,从此一发不可收拾。
剩下的事情,大家都知道了……
1.1.2 版本迭代
-
1991 年,James Gosling 博士发布产品 Oak( 橡树),这是 Java 语言的前身。
-
1995 年,Oak 语言改名为 Java。
-
1996 年,JDK(Java 开发所使用的工具包)1.0 发布,提供了纯解释执行的 Java 虚拟机实现:Sun Classic VM。
-
1997 年,JDK1.1 发布,代表技术有:JDBC、JavaBeans、内部类、反射。
-
1998 年,JDK1.2 发布,Java 技术体系被拆分为 J2SE、J2EE、J2ME 三大体系。
- 2000 年,JDK1.3 发布,默认的 Java 虚拟机由 Sun Classic VM 改为 HotSopt。
-
2002 年,JDK1.4 发布,Java 真正走向成熟,代表技术有:正则表达式、NIO 等。
-
2004 年,JDK5.0 发布,对语法易用性做了很大改进,新增了泛型、枚举等,代表技术有:并发包等。
-
2006 年,JDK6.0 发布,将 J2EE/J2SE/J2ME 的命名方式改为 Java SE 6、Java EE 6、Java ME 6。
-
2009 年,Sun 公司因为经营不善被 Oracle 公司收购。
-
2011 年,JDK7 发布。
-
2013 年,JDK8(LTS) 发布,函数式编程,lamda 表达式。
-
2017 年,JDK9
-
2018 年,JDK 10,11(LTS)正式发布
-
2019 年,JDK 12,13
-
2020 年,JDK 14,15
-
2021 年,JDK 16,17(LTS)
附:sun与微软的轶事
java诞生的1995年,正是微软在软件产业地位达到巅峰的时代。但是这个初出茅庐的毛头小子硬是引起了微软帝国的关注。所以96年微软就向sun申请了java认证。
微软的加持确实推动了人们对java的信心和兴趣。
但是好景不长,从1997年发布Visual J++的第一个版本开始,微软就开始在Java中掺入自己的私有扩展。这毫无疑问引起Sun的高度重视。
1997年10月,Sun向美国加州地方法院起诉微软公司违反两公司就微软使用Java技术所签定的合同,指控微软公司在自己的Java产品中做了“不恰当的修改”,违反了合同中承诺向用户提供Java兼容产品的条款。
这一官司一直打到了2001年1月双方达成和解。
到了2001年7月,微软公布新版的Windows XP将不再支持Sun的JVM,并且推出了.NET平台与Java分庭抗礼。
当然目前.net用的人少了,这是后话。
1.1.3 两种 jdk
openjdk vs oraclejdk:
- Oracle JDK 将更多地关注稳定性,它重视更多的企业级用户,而 OpenJDK 经常发布以支持其他特性,不太稳定。
- Oracle JDK 支持长期发布的更改(LTS),而 Open JDK 仅支持计划和完成下一个发行版。
- Oracle JDK 根据二进制代码许可协议获得许可,而 OpenJDK 根据 GPL v2 许可获得许可。
- 2019 年 1 月之后发布的 Oracle Java SE 8 的公开更新将无法用于商业,但是,OpenJDK 是完全开源的,可以自由使用。
- Oracle JDK 的构建过程基于 OpenJDK,因此 OpenJDK 与 Oracle JDK 之间没有技术差异。
- 顶级公司正在使用 Oracle JDK,Open JDK 不太受欢迎。
- Oracle JDK 具有良好的 GC 选项和更好的渲染器,而 OpenJDK 具有更少的 GC 选项
- 在响应性和 JVM 性能方面,Oracle JDK 提供了更好的性能。
- Oracle JDK 在运行 JDK 时不会产生任何问题,而 OpenJDK 有时会产生一些问题。
- Oracle JDK 将从其 10.0.X 版本将收费,用户必须付费或必须依赖 OpenJDK 才能使用其免费版本。
- Oracle JDK 完全由 Oracle 公司开发,而 Open JDK 项目由 IBM,Apple,SAP AG,Redhat 等顶级公司加入和合作。
1.2 JVM 体系
-
JDK(Java Development Kit)是 Java 语言的软件开发工具包,也是整个 java 开发的核心,它包含了 JRE 和开发工具包
-
JRE(Java Runtime Environment),Java 运行环境,包含了 JVM 和 Java 的核心类库(Java API)
-
JVM(Java Virtual Machine),Java 虚拟机,它是运行在操作系统之上的,它与硬件没有直接的交互
所谓 “一次编码,随处运行 “正是基于不同系统下的 jvm 帮你掩盖了系统之间接口的差异:
总结
jdk 是开发人员的工具包,它包含了 java 的运行环境和虚拟机,而一次编写到处运行就是基于 jvm
1.3 各种虚拟机
1.3.1 清单
1、Sun Classic VM
世界上第一款商用 Java 虚拟机。
1996 年随着 Java1.0 的发布而发布,JDK1.4 时完全被淘汰
2、BEA JRockit
专注于服务端应用,号称是世界上最快的JVM
后来被 Oracle 收购;Oracle JRockit (原来的 Bea JRockit)
3、IBM 公司的 J9VM
全称:IBM Technology for Java Virtual Machine,简称 IT4J,内部代号:J9
是 IBM 自己开发的一款 JVM
市场定位于 HotSpot 接近,服务器端、桌面应用、嵌入式等多用途 VM
4、HotSpot VM(现在最常用)
它是 Sun JDK 和 OpenJDK 中所带的虚拟机,也是目前使用范围最广的 Java 虚拟机。
5、其他
(TaobaoJVM 、Graal VM、Azul VM、Liquid VM、Apache Harmony、)虚拟机
1.3.2 查看
shawn@macpro:~ > java -version
java version "1.8.0_181"
Java(TM) SE Runtime Environment (build 1.8.0_181-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.181-b13, mixed mode)
-
hotspot 虚拟机
-
Client VM 是专门为快速启动和小内存 (small footprints) 而优化的,像 GUI 就很适合
-
Server VM 是专门为高性能应用而优化的,如服务器应用
-
版本是基于 tag 为 1.8.0_181
1.4 jvm 整体架构
1.4.1 java 运行过程
1. 源码编译:通过 Java 源码编译器将 Java 代码编译成 JVM 字节码(.class 文件)
2. 类加载:通过 ClassLoader 及其子类来完成 JVM 的类加载
3. 类执行:字节码被装入内存,进入 JVM 虚拟机,被解释器解释执行
1.4.2 jvm 模型
由上面的图可以看出,JVM 虚拟机中主要是由三部分构成,分别是类加载子系统、运行时数据区、执行引擎。
类加载子系统
Java 虚拟机把描述类的数据从 Class 文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的 Java 类型。
运行时数据区
Java 虚拟机在执行 Java 程序的过程中会把它所管理的内存划分为若干个不同的数据区域。
这些区域有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而一直存在,有些区域则是依赖用户线程的启动和结束而建立和销毁。
执行引擎
执行引擎用于执行 JVM 字节码指令,主要有两种方式,分别是解释执行和编译执行,区别在于,解释执行是在执行时翻译成虚拟机指令执行,而编译执行是在执行之前先进行编译再执行。
解释执行启动快,执行效率低。编译执行,启动慢,执行效率高。
垃圾回收器就是自动管理运行数据区的内存,将无用的内存占用进行清除,释放内存资源。
本地方法库、本地库接口
在 jdk 的底层中,有一些实现是需要调用本地方法完成的(使用 c 或 c++ 写的方法),就是通过本地库接口调用完成的。比如:System.currentTimeMillis () 方法。
2、类文件结构
了解 jvm 后续的一切动作,先从字节码开始。它是一切发生的源头。
2.1 测试案例
2.1.1 源代码
package com.itheima.jvm.demo;
public class ClassStruct {
private static String name = "JVM";
public static void main(String[] args) {
System.out.println("Hello " + name);
}
}
2.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
2.2 字节码结构
2.2.1 二进制概览
1)vscode 打开
2)class 文件是一个二进制文件,转化后是 16 进制展示,实际上 class 文件就是一张表,它由以下数据项构成,这些数据项从头到尾严格按照以下顺序排列:
类型 | 名称 | 数量 | 描述 |
---|---|---|---|
u4 | magic | 1 | 魔数 |
u2 | minor_version | 1 | 次版本号 |
u2 | major_version | 1 | 主版本号 |
u2 | constant_pool_count | 1 | 常量个数 |
cp_info | constant_pool | constant_pool_count - 1 | 具体常量 |
u2 | access_flags | 1 | 访问标志 |
u2 | this_class | 1 | 类索引 |
u2 | super_class | 1 | 父类索引 |
u2 | interfaces_count | 1 | 接口索引 |
u2 | interfaces | interfaces_count | 具体接口 |
u2 | fields_count | 1 | 字段个数 |
field_info | fields | fields_count | 具体字段 |
u2 | methods_count | 1 | 方法个数 |
method_info | methods | methods_count | 具体方法 |
u2 | attributes_count | 1 | 属性个数 |
attribute_info | attributes | attributes_count | 具体属性 |
3)图示如下:
2.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 最好一致,不要尝试这个坑。
区分和理解两个环境:编译环境,运行环境
2.2.3 常量池
再往下遵从相同的规律: 计数器(标注后面有多少个) + 对应个数的结构体
我们以常量池为例:
1)位置
2)结构说明
常量池记录了 jvm 内的一堆常量信息,这部分由 【2 个字节计数】 + 【n 个 cp_info 结构】组成
其中 cp_info 有多种类型:
- 直接类型,存的就是当前值,这种像 Integer,Long 等长度都是确定的
- 引用类型,存的是指向其他位置的指针
附:绿色代表指针,橙色代表直接类型
3)案例
下面以 String 为例,String 是一种引用类,它会指向一个 utf8 类型来存储真实的信息
jdk 提供了一个工具,javap,可以查看常量列表的详细内容:
javap -v ClassStruct.class
2.2.4 其他信息
1)说明
常量池之后,是紧挨的一系列信息,这些信息大同小异,无非就是值、或者引用
(参考上面 2.3.3 里的表格和图例)
- 访问标记:public abstract 等信息
- 类索引,class 类型,最终指向一个 utf8,标记当前类的名字
- 父类,同上
- 接口,2 字节记录数量,后面记录多个接口类型
- 接下来是字段、方法、属性,都是 2 字节记录后面多少个,后面紧跟对应的结构体类型
2)注意事项
要看懂 javap 后的格式,明白这些格式,可以轻松看懂 class 结构
| 类型 | 标识符 | 案例 | 说明 | | -------- | ------------ | ------------------ | ---------- | | 数组 | [ | [Ljava.lang.String | String 数组 | | 对象 | L | Lcom.test.Demo | | | 基本类型 | 大写字母开头 | B=byte,I=int…… | | | | | | |
组合类型
类型 | 案例 | 说明 |
---|---|---|
类里的属性、字段、方法等 | com.test.Demo.name:Ljava.lang.String | 英文点号隔开 |
标识什么类型 | com.test.Demo.getName:()Ljava.lang.String | 英文冒号隔开 |
方法 | (参数类型) 返回值类型 | 英文括弧,后面是返回值类型 |
3)实例分析
本文由
传智教育博学谷
教研团队发布。如果本文对您有帮助,欢迎
关注
和点赞
;如果您有任何建议也可留言评论
或私信
,您的支持是我坚持创作的动力。转载请注明出处!