Please enable Javascript to view the contents

无处不在的 JNI - 《面向技术宅的 JVM 内幕》

 ·  ☕ 6 分钟

image-20240902230442134

《面向技术宅的 JVM 内幕》

不知不觉,以 Java 程序员的名义在职场混了 20 有余个年头。年纪渐长,就养成了一个年轻人最讨厌的毛病:总喜欢回望过去。我一直写书和博客,都在思考一个问题。我为什么,为谁在写东西? 总的回答是,我想留下和分享点东西,意义在于留下和分享的过程。读者当然越多越好,但从不为读者多少而主导自己的写作范围。

大部分时间,我的职业是一名 Java 程序员。但从毕业到现在,我一直想摆脱 Java。我喜欢 C/C++ 那种控制一切,真男人不过度包装(封装),无所不能,有很多奇技*巧,学习门槛高,又容易出错,难入门又可以卖弄炫技,容易实现欺负新人愿望的编程技术。直到,最近有点时间去研究一下 OpenJDK Hotspot JVM。事情好像发生了点戏剧变化。用底层开发视角去看 JVM 实现,不但相当有趣,正好迎合自己多年的底层技术开发的所谓初心,还可以为 20 年的 Java 路划上一个不太完满的句号。毕竟,在神州, 40 岁的 Java 程序员大概已经走了这条赛道的尽头了。人生大概准备进入另外一条赛道了,那么在此之前,记录一下以作回忆。起码将来在墓志铭上,可以写上那么一句:“xxx书的作者”,而不只是 xyz 的老爸那么无趣。

我职场前半段,为生继而学和用 Java。而 Java 这个词如果你搜索一下网上资讯,很大程度上网上资讯都是教人如何:学习 -> 应对面试 -> 找到理想工作 -> 成为行业专家 -> 成为技术 leader -> 成为 CTO/投资人 -> 远离技术细节搞大格局 。 自从民间传说程序员的收入有多高后,会发觉现在的 Java 程序员,更多是职业型的。这类型的人材会把工作任务出色完成。不过他们本身其实对技术没什么打磨式的所谓工匠追求。注意,这里无褒贬,每个行为的健康发展,都需要人材的多样性。

在准备进入职场后半段前,我想换一下视角看技术。从 “有为“ 到 ”无为“ 。于是,我想写一本书:《面向技术宅的 JVM 内幕》 英文名叫: JVM Insider For Nerds 。

如果有人问我 Java 本身已经很老旧了,新语言很多。学习 Golang、 Rust 不好? 还有,都 AI 时代了,研究那么底层的 JVM 内部还有意义吗? 不都是问一下 GPT 就有答案了? 这些问题我没答案。因为这本书名字就叫 “面向技术宅” 。

本书主要以 openjdk hotspot VM 源码的调试和文档为依据写成。写作风格和我之前写的书 《Istio & Envoy 内幕》 一样:

这本不是一本《深入 xyz 源码》类型的书。甚至可以说,我尽了最大的努力少在书中直接贴源码。看源码是掌握实现细节必须的一步,但在书中浏览源码的体验一般非常糟糕。反而,一个源码的导航图可能更来得实用。

Yet another JVM book?

Yet another JVM book? 的确,已经太多优秀的深入 JVM 类型的书籍和文章了。我希望这本书有一些不同:

  • 内容(包括文字和图片)尽量提供到源码或文档的溯源链接,以供读者 Fact check 和扩展知识
  • 基于 draw.io 的高清互动图片,有链接,有 hover tips。读者可以用 draw.io 修改原图来定制自己的笔记,建立自己的知识体系。
  • 所有内容均开源
  • 以当前较新的 Java 21(Hotspot Open JDK 21) 为参考
  • 内容信息源尽量控制在源码和官方文档中,尽量避免互联网路边社的 Fake Docs
  • 实现优先于规范。Java 世界最麻烦的一点是,抽象说得太多,实现说得太少。本书尽量从实现切入抽象,而不是相反,这样比较符合人类学习知识的习惯。
  • 保留原味术语,毕竟,英文术语方便溯源和国际化的交流

这本书适合什么读者

这不是一本从入门到精通类型的书。我写得也比较随性,有点像笔记。所以,随性随缘吧。当然,假设读者有一些年的 Java 经验、饱满的好奇心,当然,最最重要一点是对作者各种错误的指正和包容 :)

JNI

本节适合什么读者

本节不是从入门到精通类型的。假设读者已经了解过 JNI 的基本使用方法和阅读过相关官方文档。

JNI 无处不在

我以前一真觉得,JNI 不过是在 Pure Java 解决不了的底层操作或性能瓶颈时,才会使用到的技术。实现方式是写个动态链接库(*.so),写个 java native method 就完了。最近研究了一下,发现只要使用 openjdk,基本就直接或间接使用了 JNI 了。 openjdk 内部使用 JNI 的情况后面会分析。

JNI 相关数据结构

Bad programmers worry about the code. Good programmers worry about data structures and their relationships.
– Linus Torvalds

先来看看 JNI 相关的数据结构。

JNI Data Structure
JNI Data Structure

用 Draw.io 打开

以上内容参考了: Java Native Interface Specification

上图有一些文字补充:

  • C/C++ 实现的 JNI 代码主要交互 API 是 JNIInvokeInterface_JNINativeInterface_
  • 所有线程理论上都可以 Attach 到 JVM 上成为一个 jvm thread。参与到 Stop The World 等机制当中。
  • JNIEnv_ 实例是与线程挂钩的,不应该在线程间共享。
  • 注意不同的着色代表不同的分层范畴
  • 其中牵涉到引用记数和 GC

暂时是引用 Java Native Interface Specification 和小部分代码整理。未再深入研究其内部的结构。

JVMTI 相关数据结构

说到 JNI,对于 Java 开发工具及其实现有兴趣的同学,不得不提一下基于 JNI 基础架构上的 JVMTI。

JVMTI Data Structure
JVMTI Data Structure

用 Draw.io 打开

以上内容参考了: JVMTM Tool Interface

What is the JVM Tool Interface?

The JVMTM Tool Interface (JVM TI) is a programming interface used by development and monitoring tools. It provides both a way to inspect the state and to control the execution of applications running in the JavaTM virtual machine (VM).

JVM TI is intended to provide a VM interface for the full breadth of tools that need access to VM state, including but not limited to: profiling, debugging, monitoring, thread analysis, and coverage analysis tools.

JVM TI may not be available in all implementations of the JavaTM virtual machine.

JVM TI is a two-way interface:

  • A client of JVM TI, hereafter called an agent,
  • can be notified of interesting occurrences through events.

JVM TI can query and control the application through many functions, either in response to events or independent of them.

Agents run in the same process with and communicate directly with the virtual machine executing the application being examined. This communication is through a native interface (JVM TI). The native in-process interface allows maximal control with minimal intrusion on the part of a tool. Typically, agents are relatively compact. They can be controlled by a separate process which implements the bulk of a tool’s function without interfering with the target application’s normal execution.

Architecture

Tools can be written directly to JVM TI or indirectly through higher level interfaces. The Java Platform Debugger Architecture includes JVM TI, but also contains higher-level, out-of-process debugger interfaces. The higher-level interfaces are more appropriate than JVM TI for many tools. For more information on the Java Platform Debugger Architecture, see the Java Platform Debugger Architecture website.

Java Laucher 的 JNI 使用

下面以 jstack 应用程序为例,说明一下 Java Laucher 的 JNI 使用。

Java Laucher 示例: jstack.exe
Java Laucher 示例: jstack.exe

用 Draw.io 打开

当我了解到 jstack.exe 甚至是 java.exe 本身其实都是利用 JNI API 去嵌入 JVM(libjvm.so) 时,是有点开眼界的。其实 Java 设计之初就是支持桌面应用,Native Application 可以轻松嵌入 JVM。只是作为现在主流的后端开发界来说,已经很少提这个“初心”了。

JVM Attach 机制

既然已经说了 JStack 那么就以 jstack 为例,顺便说说 JVM Attach 机制吧。其实 JVM Attach 机制也是 JVMTI 的一部分。

JVM attach 机制
jJVM attach 机制

用 Draw.io 打开

JVM attach 机制(Java 10 之前)
JVM attach 机制(Java 10 之前)

用 Draw.io 打开

Java 调试支持技术栈

学习 Java 技术栈时,有一个很容易让人混乱的东西:一堆 Java JEP(JDK Enhancement Proposals) 技术名称和规范,叠加上 Java 几十年的历史变化,过期文档。对于我们这种非英语母语的人,面对一堆英文已经吃力,还要记住一堆缩写,简直想放弃了。很多 JEP 就是定义规范接口或数据结构,只是说明了Java 技术栈的一个水平切面。实现上联系不上技术规范与技术规范之间、实现与实现之间的全景关系图。让人很难看到技术栈全景关系。

Java 调试支持技术栈也有这个问题。下图尝试总结一下技术栈的依赖关系。

Java 调试支持技术栈
Java 调试支持技术栈

用 Draw.io 打开

分享

Mark Zhu
作者
Mark Zhu
An old developer