【图文并茂】这次一文讲透JVM架构、类文件结构、字节码结构!!

521 阅读12分钟

大家好,我是狂野君,前两天写了篇文章,小姐姐让我给标题,我无法拒接

让实习生搭个Redis集群,差点把我”搭“进去~~~

image.png

所以,今天的标题要改的更性感一些了

今天,更大家聊一下关于虚拟机的话题,由于内容太多,所以计划分成几篇,今天是第一篇

建议大家关注 收藏,下一篇文章明天准时奉上

今天的主要的内容有:

  • 虚拟机发展历程
  • JVM体系
  • JVM整体架构
  • 类文件结构
  • 字节码结构

下一篇预告:

  • 运行数据区
  • 程序计数器
  • 本地方法栈、堆、方法区
  • 类加载
  • 对象创建、销毁、调优 。。。

好了,开始今天的干货分享~~~

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用的人少了,这是后话。

image.png

1.1.3 两种jdk

openjdk vs oraclejdk:

  1. Oracle JDK将更多地关注稳定性,它重视更多的企业级用户,而OpenJDK经常发布以支持其他特性,不太稳定。
  2. Oracle JDK支持长期发布的更改(LTS),而Open JDK仅支持计划和完成下一个发行版。
  3. Oracle JDK根据二进制代码许可协议获得许可,而OpenJDK根据GPL v2许可获得许可。
  4. 2019年1月之后发布的Oracle Java SE 8的公开更新将无法用于商业,但是,OpenJDK是完全开源的,可以自由使用。
  5. Oracle JDK的构建过程基于OpenJDK,因此OpenJDK与Oracle JDK之间没有技术差异。
  6. 顶级公司正在使用Oracle JDK,Open JDK不太受欢迎。
  7. Oracle JDK具有良好的GC选项和更好的渲染器,而OpenJDK具有更少的GC选项
  8. 在响应性和JVM性能方面,Oracle JDK提供了更好的性能。
  9. Oracle JDK在运行JDK时不会产生任何问题,而OpenJDK有时会产生一些问题。
  10. Oracle JDK将从其10.0.X版本将收费,用户必须付费或必须依赖OpenJDK才能使用其免费版本。
  11. Oracle JDK完全由Oracle公司开发,而Open JDK项目由IBM,Apple,SAP AG,Redhat等顶级公司加入和合作。

1.2 JVM体系

image.png

  • JDK(Java Development Kit)是 Java语言的软件开发工具包,也是整个java开发的核心,它包含了JRE和开发工具包
  • JRE(Java Runtime Environment),Java运行环境,包含了JVM和Java的核心类库(Java API)
  • JVM(Java Virtual Machine),Java虚拟机,它是运行在操作系统之上的,它与硬件没有直接的交互

所谓“一次编码,随处运行“正是基于不同系统下的jvm帮你掩盖了系统之间接口的差异:

image.png

总结

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运行过程

image.png

1.源码编译:通过Java源码编译器将Java代码编译成JVM字节码(.class文件)

2.类加载:通过ClassLoader及其子类来完成JVM的类加载

3.类执行:字节码被装入内存,进入JVM虚拟机,被解释器解释执行

1.4.2 jvm模型

image.png

由上面的图可以看出,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打开

image.png 2)class文件是一个二进制文件,转化后是16进制展示,实际上class文件就是一张表,它由以下数据项构成,这些数据项从头到尾严格按照以下顺序排列:

类型名称数量描述
u4magic1魔数
u2minor_version1次版本号
u2major_version1主版本号
u2constant_pool_count1常量个数
cp_infoconstant_poolconstant_pool_count - 1具体常量
u2access_flags1访问标志
u2this_class1类索引
u2super_class1父类索引
u2interfaces_count1接口索引
u2interfacesinterfaces_count具体接口
u2fields_count1字段个数
field_infofieldsfields_count具体字段
u2methods_count1方法个数
method_infomethodsmethods_count具体方法
u2attributes_count1属性个数
attribute_infoattributesattributes_count具体属性

3)图示如下:

image.png

2.2.2 魔数与版本

1)魔数:

CAFEBABE,咖啡宝宝,固定的。

image.png

2)版本号:

34,换成10进制就是52

image.png

jdk的版本标记映射关系:

image.png

说明编译用的是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)位置

image.png

2)结构说明

常量池记录了jvm内的一堆常量信息,这部分由 【2个字节计数】 + 【n个cp_info结构】组成

image.png

其中cp_info有多种类型:

  • 直接类型,存的就是当前值,这种像Integer,Long等长度都是确定的
  • 引用类型,存的是指向其他位置的指针

image.png

附:绿色代表指针,橙色代表直接类型

3)案例

下面以String为例,String是一种引用类,它会指向一个utf8类型来存储真实的信息

jdk提供了一个工具,javap,可以查看常量列表的详细内容:

 javap -v ClassStruct.class

image.png

2.2.4 其他信息

1)说明

常量池之后,是紧挨的一系列信息,这些信息大同小异,无非就是值、或者引用

(参考上面2.3.3里的表格和图例)

  • 访问标记:public abstract 等信息
  • 类索引,class类型,最终指向一个utf8,标记当前类的名字
  • 父类,同上
  • 接口,2字节记录数量,后面记录多个接口类型
  • 接下来是字段、方法、属性,都是2字节记录后面多少个,后面紧跟对应的结构体类型

2)注意事项

要看懂javap后的格式,明白这些格式,可以轻松看懂class结构

image.png

类型标识符案例说明
数组[[Ljava.lang.StringString数组
对象LLcom.test.Demo
基本类型大写字母开头B=byte,I=int……

组合类型

类型案例说明
类里的属性、字段、方法等com.test.Demo.name:Ljava.lang.String英文点号隔开
标识什么类型com.test.Demo.getName:()Ljava.lang.String英文冒号隔开
方法(参数类型)返回值类型英文括弧,后面是返回值类型

3)实例分析

image.png

好了,今天的JVM手撕面试官,就先到这里了

都看到这里,掘友们,给点赞呗,不要吝啬你的小手,给我点儿动力,你们认可是我熬夜最大的动力!!!

时间不早了,该休息了

好像,谁还没有见过凌晨4点钟的太阳似的

往期精品:

OH MY GOD,原来微博、微信、购物车、抽奖小程序是这么用数据结构的!

Redis的安全策略、过期删除策略和内存淘汰策略是个啥?怎么玩?

让实习生搭个Redis集群,差点把我”搭“进去~~~

我用Redis分布式锁,抢了瓶茅台,然后GG了~~