想要探究JVM的内部实现原理,最直接的方式就是编译一套自己的JDK,这样我们可以通过阅读和跟踪JDK源码来了解它的运行机理
除此之外,JDK中很多方法都是本地方法(Native),因此也可以通过这样的方式知道这些方法的运行机理
由于在Windows下编译稍微有些麻烦,因此选择在Liunx系统上编译
系统截图如下:
由于该系统自带的gcc/g++版本过低(4.8.5),因此需要升级gcc/g++
这里给出一个便捷的方法:
bash> yum install centos-release-scl
bash> yum install devtoolset-9-gcc* # 注:这里9是gcc的大版本号,可以换成其他版本
bash> scl enable devtoolset-9 bash
bash> mv /usr/bin/gcc /usr/bin/gcc-4.8.5
bash> ln -s /opt/rh/devtoolset-9/root/bin/gcc /usr/bin/gcc # 替换原来的gcc
bash> mv /usr/bin/g++ /usr/bin/g++-4.8.5
bash> ln -s /opt/rh/devtoolset-9/root/bin/g++ /usr/bin/g++ # 替换原来的g++
bash> gcc --version
bash> g++ --version
注意:要编译大版本号为N的JDK,需要另外准备大版本号至少为N-1的已编译好的JDK
笔者的yum源更新之后,JDK版本也不过是1.8,因此,我们需要准备一个JDK11
JDK11也是LTS版本,使用率仅次于1.8,因此选择12作为编译目标是比较好的
要升级JDK,首先卸载自带的JDK
bash> rpm -qa | grep java # 查看已有jdk版本
bash> yum -y remove java-1.7.0-openjdk* # 删除1.7
bash> yum -y remove java-1.8.0-openjdk* # 删除1.8
bash> java -version # 检查是否删除干净
然后,去Oracle官网下载一个JDK11,压缩包版
下载需要Oracle账号,如果没有Oracle账号,注册一个或者网上搜一个,基本上网上搜的都能用
然后安装这个JDK,笔者选择在/usr/local下放置这个JDK
bash> cd /usr/local
bash> mkdir java
bash> mv 你的压缩包路径 /usr/local/java
bash> tar zvxf xxx.tar.gz #解压
bash> gedit .bashrc
bash> source .bashrc
会熟练使用vim的可以更换gedit为vim,但是相比之下,还是gedit更亲切一些(可视化界面中)
一般来说,服务器都是只有命令行的,熟练使用vim是必不可少的技能
这里有一个坑,就是网上的教程一般会把环境变量写在/etc/profile这个文件下,但是这并不是永久有效的,想要让它永久有效,就得写在.bashrc文件中
环境变量如下:
export JAVA_HOME=/usr/local/java/jdk-11.0.10 #注意更换为你自己的版本
export PATH=$JAVA_HOME/bin:$PATH
首先需要获取源码,这里选择的是OpenJDK12,下面是笔者的百度网盘下载链接:
链接: https://pan.baidu.com/s/1LOT2VUgWK52gE00w1vDi0w 提取码: bwtx 复制这段内容后打开百度网盘手机App,操作更方便哦
注意:下载完解压的路径不要有中文,否则编译到一半会报错(笔者亲历)
cd到解压目录下,切换为root用户,就可以开始编译了
bash> bash configure
运行该命令,会检查你的环境是否配置好
笔者初次运行时有部分依赖没有安装,该命令会提示你去安装这些包,并且给出了具体的安装命令(还会根据Linux发行版的不同而给出不同的命令),需要多次运行该命令
最后,使用如下命令即可开始编译:
bash> make clean
bash> make images
但是,这样做会在编译期遇到错误:
/home/wzy/OpenJdk12/src/hotspot/share/runtime/arguments.cpp: In static member function ‘static jint Arguments::parse_each_vm_init_arg(const JavaVMInitArgs*, bool*, JVMFlag::Flags)’:
/home/wzy/OpenJdk12/src/hotspot/share/runtime/arguments.cpp:2472:29: error: ‘char* strncpy(char*, const char*, size_t)’ output truncated before terminating nul copying as many bytes from a string as its length [-Werror=stringop-truncation]
char* name = strncpy(NEW_C_HEAP_ARRAY(char, len + 1, mtArguments), tail, len);
~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/home/wzy/OpenJdk12/src/hotspot/share/runtime/arguments.cpp:2471:44: note: length computed here
size_t len = (pos == NULL) ? strlen(tail) : pos - tail;
~~~~~~^~~~~~
cc1plus: all warnings being treated as errors
出现这种错误的原因是gcc版本太高,gcc对这些行为进行了警告,为了继续编译,需要重新指定configure选项,忽略这些警告
这种特性从gcc8开始,如果你是更早的符合要求的gcc,可能不会出现这样的问题
bash> make clean
bash> make dist-clean
bash> bash configure --disable-warnings-as-errors
bash> make images
当出现上图的情况时,说明已编译结束,编译的产物在build文件夹下,其名字在上图中已有提示,即’linux-x86_64-server-release’
在这个文件夹中,有名为jdk的文件夹,cd进去,结构如下:
这就是我们熟悉的jdk的目录,使用下面的命令查看该jdk的版本:
bash> ./bin/java -version
openjdk version "12.0.2-internal" 2019-07-16
OpenJDK Runtime Environment (build 12.0.2-internal+0-adhoc.wzy.OpenJDK12)
OpenJDK 64-Bit Server VM (build 12.0.2-internal+0-adhoc.wzy.OpenJDK12, mixed mode)
这样编译就结束了
要使用CLion查看并调试,需要在编译之前的bash configure命令后加一个参数,完整命令如下:
bash> bash configure --enable-debug --with-jvm-variants=server --disable-warnings-as-errors
然后,在OpenJDK12,也就是根目录中,创建CMakeLists.txt:
cmake_minimum_required(VERSION 3.7)
project(hotspot)
include_directories(
src/hotspot/cpu
src/hotspot/os
src/hotspot/os_cpu
src/hotspot/share
src/hotspot/share/precompiled
src/hotspot/share/include
src/java.base/unix/native/include
src/java.base/share/native/include
src/java.base/share/native/libjli
)
file(GLOB_RECURSE SOURCE_FILES "*.cpp" "*.hpp" "*.c" "*.h")
add_executable(hotspot ${SOURCE_FILES})
在CLion中,选择OpenJDK12根目录下的CMakeList.txt,选择作为项目打开即可:
src/java.base/share/native/libjli/java.c的JavaMain函数就是HotSpot虚拟机的入口,我们可以设置一个断点:
在运行之前,还需要做一件事,就是写一个Java程序并编译成class文件在jdk的bin目录下:
public class Hello {
public static void main(String[] args) {
System.out.println("Hello World!");
}
}
然后,在CLion运行配置中选择CMake应用程序,配置如下:
点击确定后,选择调试启动,最后结果如下:
这里省略了几个断点的结果