文章目录

  • 1. 走进Java
  • 1.1 前言
  • 1.2 实战:自己编译JDK
  • 1. 获取源码
  • 2. 解压zip文件
  • 3. 安装上一个JDK版本
  • 4. 构造编译环境
  • 5. 开始源码编译
  • 6. 检查编译成功与否
  • 7. configure参数
  • 1.3 在IDE工具中进行源码调试(待完成)


1. 走进Java

1.1 前言

从广义上讲,Kotlin、Clojure、JRuby、Groovy等运行于Java虚拟机上的编程语言及其相关的程序都属于Java技术体系中的一员
从传统意义上来看,JCP(Java社区)官方所定义的Java技术体系包括:Java程序设计语言、各种硬件平台上的Java虚拟机实现、Class文件格式、Java类库API、来自商业机构和开源社区的第三方Java类库

Java前身:Oak,Sun公司

  • JDK
    Java程序设计语言、Java虚拟机、Java类库这三部分统称为JDK(Java Development Kit)
    JDK是用于支持Java程序开发的最小环境
  • JRE
    Java类库API中的Java SE API子集和Java虚拟机这两部分统称为JRE(Java Runtime Environment)
    JRE是支持Java程序运行的标准环境
  • 产品线
    (1)Java Card:支持Java小程序(Applets)运行在小内存设备(如智能卡)上的平台
    (2)Java ME(Micro Edition):支持Java程序运行在移动终端(手机、PDA)上的平台,对Java API有所精简,注意手机端Java语言开发程序的Android并不属于Java ME
    (3)Java SE(Standard Edition):支持面向桌面级应用(如Windows下的应用程序)的Java平台,提供了完整的Java核心API
    (4)Java EE(Enterprise Edition):支持使用多层架构的企业应用(如ERP、MIS、CRM应用)的Java平台,除了提供Java SE API外,还对其做了大量有针对性的扩充(JDK后被捐赠给eclipse,改名为Jakarta EE)
  • 基本概念
  • 机器码:二进制编码方式表示的指令,叫做机器指令码,CPU直接读取运行,执行速度最快
  • 指令:把机器码中特定的0和1序列,简化成对应的指令,如mov,inc等
  • 指令集:不同的硬件平台所支持的指令,称之为对应平台的指令集; x86指令集,对应的是x86架构的平台,ARM指令集,对应的是ARM架构的平台
  • 汇编语言:在汇编语言中,用助记符(Mnemonice)代替机器指令的操作码,用地址符号(Symbol)或标号(Label)代替指令或操作数的地址;用汇编语言编写的程序还必须翻译成机器指令码
  • 高级语言:需要把程序解释和编译成机器的指令码(指令集中的指令的代码)
  • 字节码:字节码是一种中间状态(中间码)的二进制代码(文件),可以在装有JVM虚拟机的电脑上运行;
    字节码的实现方式是通过编译器和虚拟机器。编译器将源码编译成字节码,特定平台上的虚拟机器将字节码转译为可以直接执行的指令
  • Java编译器、解释器、即时编译器
  • 编译器:将编写的Java源文件即(.java)编译成字节码文件即(.class)
  • 即时编译器(JIT):虚拟机将使用频繁的代码,编译成与本地平台相关的机器码,能直接执行,效率更高
    java有2种编译方式,一是编译成和机器平台无关的字节码文件;二是编译成和机器平台有关的机器码
  • 解释器:解释执行字节码class文件
  • HotSpot虚拟机
    默认的Java虚拟机
    热点代码探测能力可以通过执行计数器找出最具有编译价值的代码,然后通知即时编译器以方法为单位进行编译
    如果一个方法被频繁调用,或方法中有效循环次数很多,将会分别触发标准即时编译和栈上替换编译(On-Stack Replacement,OSR)行为
    除此之外,还有诸多其他虚拟机,如IVM J9 VM、KVM等
    Graal VM:新兴虚拟机,基于HotSpot发展而来,旨在可以作为“任何语言”的运行平台使用
    HotSpot虚拟机中含有两个即时编译器和一个新的Graal编译器
  • 客户端编译器 C1:编译耗时短但输出代码优化程度较低
  • 服务端编译器 C2:编译耗时长但输出代码优化质量也更高
  • Graal编译器:C2复杂难以维护,Graal作为其替代品登场,目前仍处于实验阶段,前途可期

1.2 实战:自己编译JDK

想要窥探Java虚拟机内部的实现原理,最直接的一条路径就是编译一套自己的JDK

1. 获取源码

有OpenJDK和OracleJDK两个版本,但现在它们的代码大多相同了
以OpenJDK为例:

实验条件:
JDK版本:OpenJDK12
操作系统:Centos 7

  • 方法一:官网下载(很慢)
    网址:https://hg.openjdk.java.net/jdk/jdk12,进入后点击左侧“browse”,再点击“zip”即可下载
  • 方法二:github(快,推荐)
    网址:https://github.com/openjdk/jdk,在“master”那切换至“tags”,选择jdk的最后一次提交版本“jdk-12-ga”,下载zip文件
  • 方法三:git命令

2. 解压zip文件

进入安装位置,这里选择 /home,查看磁盘空间,并新建一个jdk12文件夹(需要切换至root权限)

cd /home
df -h
mkdir jdk12
cd /home/jdk12

借助工具将之前下载的jdk-jdk-12-ga.zip传送到CentOS虚拟机里面的 /home/jdk12目录下,解压zip文件

unzip jdk-jdk-12-ga.zip

3. 安装上一个JDK版本

确保已经安装gcc(4.8版本及以上),检查命令

gcc --version

JDK少部分是C/C++编写,更多是java编写,运行这些java代码,需要用到另一个编译期可用的JDK,官方称这个JDK为“Bootstrap JDK”;
因此,编译OpenJDK 12时,Bootstrap JDK必须使用JDK 11及之后的版本

yum install -y java-11-openjdk-devel
//检查的话用
java -version

4. 构造编译环境

进入解压后的jdk文件夹,里面有一个configure文件,执行该文件

cd jdk-jdk-12-ga
bash configure

如果成功执行,提示语句最后不会显示 configure:error 等字样

(1)报错一:如果出现报错,如

arm架构 适配x86架构的虚拟机_java


就按照提示安装缺失的包,安装完成之后再次执行bash configure

yum install libXtst-devel libXt-devel libXrender-devel libXrandr-devel libXi-devel
bash configure

(2)仍然报错,如下

arm架构 适配x86架构的虚拟机_java_02

照提示安装缺失的包,安装完成之后再次执行bash configure(这个流程重复几遍,最终还安装了以下组件)

yum install cups-devel
yum install fontconfig-devel
yum install alsa-lib-devel
bash configure

最终终于不报error了

5. 开始源码编译

configure命令:配置好的 JVM虚拟机 的参数
make命令:进行配置好的整个OpenJDK编译

  • configure 命令

所有的参数都通过 bash configure [options] 的形式使用(关于参数,见下节)
譬如,编译FastDebug版、仅含Server模式的HotSpot虚拟机,命令应为

bash configure --enable-debug --with-jvm-variants=server

如果报错,就按照提示下载缺失文件(这里没有报错)

顺利安装后,可以看到调试级别,Java虚拟机的模式、特性,使用的编译器版本等配置摘要信息

arm架构 适配x86架构的虚拟机_Java_03

  • make命令

依赖检测通过后便可以输入“make”执行整个OpenJDK编译了

make

如果出现以下报错

arm架构 适配x86架构的虚拟机_centos_04

则需要指定版本,重新make。重新make前要clean上次make的信息

make clean CONF=linux-x86_64-server-fastdebug
make CONF=linux-x86_64-server-fastdebug

make会比较慢,在半小时左右

有的地方也用 make images CONF=linux-x86_64-server-fastdebug,这里“images”是“product images”编译目标(Target)的简写别名,这个目标的作用是编译出整个JDK镜像,除了“product images”以外,其他编译目标还有:

arm架构 适配x86架构的虚拟机_java_05

6. 检查编译成功与否

(1)make成功之后,进入安装目录/build/配置名称,可以看到如下结构,我这里是

cd linux-x86_64-server-fastdebug/

arm架构 适配x86架构的虚拟机_centos_06

有些目录例如images是没有的,因为之前采用的是make而不是make images

(2)进入 jdk/bin,输入以下命令

./java -version

可以看到编译好的openjdk12信息

arm架构 适配x86架构的虚拟机_java_07

JDK编译可以通过设置configure参数,来配置一个自定义化的JVM虚拟机
除此之外,JVM虚拟机开发也需要对源码进行研究

7. configure参数

可以使用以下命令查看所有参数

bash configure --help

比较重要的有以下参数

--with-debug-level=<level>
设置编译的级别,可选值为release、fastdebug、slowde-bug,越往后进行的优化措施就越少,带的调试信息就越多。还有一些虚拟机调试参数必须在特定模式下才可以使用。默认值为release

--enable-debug
等效于 --with-debug-level=fastdebug

--with-native-debug-symbols=<method>
确定调试符号信息的编译方式,可选值为none、internal、external、zipped

--with-version-string=<string>
设置编译JDK的版本号,譬如java -version的输出就会显示该信息。这个参数还有--with-version-<part>=<value>的形式,其中part可以是pre、opt、build、major、minor、security、patch之一,用于设置版本号的某一个部分

--with-jvm-variants=<variant>[,<variant>...]
编译特定模式(Variants)的HotSpot虚拟机,可以多个模式并存,可选值为server、client、minimal、core、zero、custom

--with-jvm-features=<feature>[,<feature>...]
针对--with-jvm-variants=custom时的自定义虚拟机特性列表(Features),可以多个特性并存,由于可选值较多,请参见help命令输出。

--with-target-bits=<bits>:指明要编译32位还是64位的Java虚拟机,在64位机器上也可以通过交叉编译生成32位的虚拟机

--with-<lib>=<path>
用于指明依赖包的具体路径,通常使用在安装了多个不同版本的Bootstrap JDK和依赖包的情况。其中lib的可选值包括boot-jd、freetype、cups、x、alsa、libffi、jtreg、libjpeg、giflib、libpng、lcms、zlib

--with-extra-<flagtype>=<flags>
用于设定C、C++和Java代码编译时的额外编译器参数,其中flagtype可选值为cflags、cxxflags、ldflags,分别代表C、C++和Java代码的参数

--with-conf-name=<name>
指定编译配置名称,OpenJDK支持使用不同的配置进行编译,默认会根据编译的操作系统、指令集架构、调试级别自动生成一个配置名称,譬如“linux-x86_64-serverrelease”,如果在这些信息都相同的情况下保存不同的编译参数配置,就需要使用这个参数来自定义配置名称

1.3 在IDE工具中进行源码调试(待完成)

待实验