JVM 源码分析
搭建源码调试环境
源码下载地址:unofficial-openjdk/openjdk
遇到的问题
- g++和 gcc 版本不匹配
- 一些 Warning 被当作错误报错了
大部分 Warning 不会影响最终结果,可以关掉:https://blog.csdn.net/desiyonan/article/details/80802066
阅读
编译 openjdk
- 下载
代码的下载见 building 文档,使用一个 Mercurial 工具进行源码控制。 - 准备工具
找到这种开放源码的软件,第一步当然是查看 README 了,openjdk 的 README 中告知了 building 文档的位置,相当详细。
硬件要求
操作系统要求
编译器要求,需要 gcc 和 clang,他们的区别见Clang 比 GCC 好在哪里?
JDK 要求,虽然挺矛盾的,但是一部分模块是用 JDK 写的,所以 Java 编译器也是需要的,一般编译 JDK8 时需要的 JDK 版本是 7 就够了,但是 openjdk 需要 8 才行(不知道为什么),这个 jdk 可以直接使用 apt-get 安装。
外部库要求,
其他工具,make、bash、autoconf - configure
准备所有配置文件 - make
编译源码,默认下只会编译一些必要的部分,如果编译成功可以在 openjdk/build 文件夹下找到目标文件,我的成品所在目录是${openjdk}/build/linux-x86_64-normal-server-release/jdk/bin
。 - run test
openjdk 使用了一个叫 jtreg 的测试框架,文档有这里还有这里,我设置了一个愚蠢的环境变量 JAVA18HOME 然后尝试着去make -C make
编译它,但是还是提示缺少什么org.testing
,可能还有什么依赖库没装,这个框架可能是给 oracle 的内部员工或者外星人用的吧?
定位 Object 的 native 方法
- 搜索“Object”打开 jdk 下的 Object.c 文件
看到下面这个方法数组:1
2
3
4
5
6
7static JNINativeMethod methods[] = {
{"hashCode", "()I", (void *)&JVM_IHashCode},
{"wait", "(J)V", (void *)&JVM_MonitorWait},
{"notify", "()V", (void *)&JVM_MonitorNotify},
{"notifyAll", "()V", (void *)&JVM_MonitorNotifyAll},
{"clone", "()Ljava/lang/Object;", (void *)&JVM_Clone},
}; - 然后搜索“JVM_MonitorWait”打开 hotspot 下的 jvm.cpp 文件
里面有下面这条语句:我们暂时不知道这几个宏到底什么意思,不过核心大概是最后的那个1
2
3
4
5
6
7
8
9
10
11
12
13
14
15JVM_ENTRY(void, JVM_MonitorWait(JNIEnv* env, jobject handle, jlong ms))
JVMWrapper("JVM_MonitorWait");
Handle obj(THREAD, JNIHandles::resolve_non_null(handle));
JavaThreadInObjectWaitState jtiows(thread, ms != 0);
if (JvmtiExport::should_post_monitor_wait()) {
JvmtiExport::post_monitor_wait((JavaThread *)THREAD, (oop)obj(), ms);
// The current thread already owns the monitor and it has not yet
// been added to the wait queue so the current thread cannot be
// made the successor. This means that the JVMTI_EVENT_MONITOR_WAIT
// event handler cannot accidentally consume an unpark() meant for
// the ParkEvent associated with this ObjectMonitor.
}
ObjectSynchronizer::wait(obj, ms, CHECK);
JVM_ENDObjectSynchronizer::wait
吧,这个类定义在 C++文件 synchronizer.cpp 中。 - 综上所述
可以看到如果一个类定义了 native 方法,那么基本可以在 jdk 目录下找到该类的定义(类所在包结构和其在 openjdk 中的定义的路径有什么关联???),至于其实现,从知乎上可以找到一段说明,我不知道对错:如果间接调用了 hotspot 的实现(jvm 会以动态库的形式被加载,prims 文件夹里定义了 hotspot 与其他模块的接口及其实现),那么从 hotspot/src/share/vm/prims/jvm.cpp 中可以找到 JVM_Xxx 函数的实现。
为 Object 类添加 native 方法 nop()
根据这里的说法,openjdk 有两种方式来定义 native 函数,一种是按一种特别的方式命名函数,这样加载后就可以和 Java 中的 native 方法对上,另一种是使用 registerNatives()的方式,观察 Object.c 文件可以发现,这种方法先定义了一个 JNINativeMethod 类型的静态数组来做 Java 方法和本地函数的对应,然后使用 JNI 方法注册了一个叫 Java_java_lang_Object_registerNatives 的本地方法来实施这种映射。
当使用第一种方式在 Object.c 中定义一个特殊命名的 nop()函数时,编译不通过,报错显示Incompatible definition of java.lang.Object
,我定义的 nop 函数:
1
2
3
4JNIEXPORT void JNICALL
Java_java_lang_Object_nop(JNIEnv *env, jobject this)
{
}
然后下面是我使用第二种方式定义 nop()函数的过程(虽然也不行):
修改 Object.c
methods 数组中添加一行:1
"nop", "()V", (void*)&
这种定义方法参照了这里
修改 Object.java
其所在目录为${openjdk}/jdk/src/java.base/share/classes/java/lang/Object.java
,为其添加一个 nop()方法:1
2
3
4
5/**
*
*/
@HotSpotIntrinsicCandidate
public native void nop();为什么要加注释?为什么不能加 final?
重编译 make
虽然只是添加了一个方法,但是几乎所有包下的都要重新编译了,所以编译过程会稍微有点长。测试
使用javap
命令反编译可以看到 Object 类中确实已经有了一个 nop()方法:1
javap ${openjdk}/build/linux-x86_64-normal-server-release/jdk/modules/java.base/java/lang/Object.class
但是实际编译时却报错了,错误大概是Object类不兼容???
native 方法的实现机制
参见 JNI 原理。
JNI
生成.h头文件
使用 javah 命令,在项目的 src 目录下运行,其中-d 指定目标目录,-jni 是默认选项,生成 jni 头文件,可以加-classpath 指定 class 路径,但必须是绝对路径
1 | javah -d ../jni -jni com.tallate.HelloJNI |
接下来编译.c 文件,-c 可以生成目标文件(未链接),-I 可以指定#include 查找的目录,不然 jni 有些头文件找不到
1 | gcc -c -I "/usr/lib/jvm/java-8-openjdk-amd64/include" -I "/usr/lib/jvm/java-8-openjdk-amd64/include/linux" HelloJNI.c |
接下来编译成一个.so 链接库
1 | gcc HelloJNI.c -I "/usr/lib/jvm/java-8-openjdk-amd64/include" -I "/usr/lib/jvm/java-8-openjdk-amd64/include/linux" -fPIC -shared -o libhello.so |
接下来可以在Java中加载
1 | // 在linux下动态库必须有前缀lib |
调用 native 方法的流程
- native 方法编译后多一个 ACC_NATIVE 标志
- loadLibrary 方法调用 JVM 的 load 本地方法
- load 方法最终调用系统调用 dlopen 加载动态链接库
- 调用本地方法时,实际上调用了 JVM 中加载的函数,栈帧压入本地方法栈中(HotSpot 中本地方法栈=虚拟机栈)
修改 Object 类,为其添加一个 native 方法 nop()
锁(内置锁)
- 锁的存储结构?
- 获取锁的时机?
- 释放锁的时机?
线程
和 JDK 的线程操作相关的类有 Object 和 Thread,Object 中包括 wait、notify 和 notifyAll,Thread 中包括 yield、sleep、exit、interrupt、join、start,run 的主要任务是执行用户定义的逻辑,还有一些方法已经被 Deprecated,不必再提
源码中和线程操作相关的一些数据结构
ObjectMonitor
对象监视器,定义和声明在 objectMonitor.*中,主要包含ObjectWaiter
ObjectSynchronizer
wait
wait 是 native 方法,实现在 synchronizer.cpp 中,ObjectSynchronizer::wait:
通过
ObjectSynchronizer::inflate
方法找到 object 中的ObjectMonitor monitor
调用 monitor.wait
wait 方法比较复杂,但是核心过程只有 3 步:- ObjectMonitor::AddWaiter(..)将新建立的 ObjectWaiter 对象插入_WaitSet 队列的末尾
- ObjectMonitor::exit(..)释放锁
- Self->_ParkEvent->park等待