文章目录
- 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)报错一:如果出现报错,如
就按照提示安装缺失的包,安装完成之后再次执行bash configure
yum install libXtst-devel libXt-devel libXrender-devel libXrandr-devel libXi-devel
bash configure
(2)仍然报错,如下
照提示安装缺失的包,安装完成之后再次执行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虚拟机的模式、特性,使用的编译器版本等配置摘要信息
- make命令
依赖检测通过后便可以输入“make”执行整个OpenJDK编译了
make
如果出现以下报错
则需要指定版本,重新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”以外,其他编译目标还有:
6. 检查编译成功与否
(1)make成功之后,进入安装目录/build/配置名称,可以看到如下结构,我这里是
cd linux-x86_64-server-fastdebug/
有些目录例如images是没有的,因为之前采用的是make而不是make images
(2)进入 jdk/bin,输入以下命令
./java -version
可以看到编译好的openjdk12信息
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工具中进行源码调试(待完成)
待实验