jdk8和hotspot
在本文中,我们将从虚拟机(VM)的角度以及与之交互的角度研究如何开始在HotSpot Java虚拟机上工作以及如何在OpenJDK开源项目中实现它。 Java类库。
让我们看一下JDK源代码以及其中包含的Java概念的实现。 检查源代码有两种主要方法:
两种方法都很有用,但重要的是要同时适应第二种方法和第一种方法。 OpenJDK源代码存储在Mercurial(类似于无处不在的Git版本控制系统的分布式版本控制系统。)中,如果您不熟悉Mercurial,则可以免费获得一本名为“ 版本控制示例 ”的书,其中涵盖了基础知识。
要检出OpenJDK 7源代码,请安装Mercurial命令行工具,然后:
汞克隆http://hg.openjdk.java.net/jdk7/jdk7 jdk7_tl
这将生成OpenJDK存储库的本地副本。 此仓库具有项目的基本布局,但尚未包含所有文件-因为OpenJDK项目分布在多个子存储库中。
初始克隆后,本地存储库将如下所示:
ariel-2:jdk7_tl boxcat$ ls -l
total 664
-rw-r--r-- 1 boxcat staff 1503 14 May 12:54 ASSEMBLY_EXCEPTION
-rw-r--r-- 1 boxcat staff 19263 14 May 12:54 LICENSE
-rw-r--r-- 1 boxcat staff 16341 14 May 12:54 Makefile
-rw-r--r-- 1 boxcat staff 1808 14 May 12:54 README
-rw-r--r-- 1 boxcat staff 110836 14 May 12:54 README-builds.html
-rw-r--r-- 1 boxcat staff 172135 14 May 12:54 THIRD_PARTY_README
drwxr-xr-x 12 boxcat staff 408 14 May 12:54 corba
-rwxr-xr-x 1 boxcat staff 1367 14 May 12:54 get_source.sh
drwxr-xr-x 14 boxcat staff 476 14 May 12:55 hotspot
drwxr-xr-x 19 boxcat staff 646 14 May 12:54 jaxp
drwxr-xr-x 19 boxcat staff 646 14 May 12:55 jaxws
drwxr-xr-x 13 boxcat staff 442 16 May 16:01 jdk
drwxr-xr-x 13 boxcat staff 442 14 May 12:55 langtools
drwxr-xr-x 18 boxcat staff 612 14 May 12:54 make
drwxr-xr-x 3 boxcat staff 102 14 May 12:54 test
接下来,您应该运行get_source.sh脚本,该脚本在初始克隆的一部分中已被下拉。 这将填充项目的其余部分,并克隆实际构建OpenJDK所需的所有文件。
在深入讨论源代码之前,重要的是要说:“不要害怕平台源代码”。 开发人员经常认为JDK源代码必须令人敬畏且难以理解。 毕竟,它是平台的核心。
JDK源代码是可靠的,经过全面审查和测试的; 但非常平易近人。 特别是,该源代码并不一定总是始终采用最新的Java语言功能。 因此,很常见的是在内部中找到仍未生成的类,这些类始终使用原始类型。
您应该熟悉JDK源代码的几个主要存储库:
杰克
这是类库所在的位置。 这些大多是Java(带有一些用于本机方法的C代码)。 这是进入OpenJDK源代码的一个很好的起点。 JDK的类在jdk / src / share / classes中
热点
HotSpot VM-这是C / C ++和汇编代码(带有一些基于Java的VM开发工具)。 它相当先进,如果您不是C / C ++的核心开发者,那么可能会有些困难。 稍后,我们将更详细地讨论其中的一些好的方法。
langtools
对于对编译器和工具开发感兴趣的人,可以在这里找到语言和平台工具。 大多数情况下是Java和C代码-不像jdk代码那样容易获得,但是大多数开发人员应该可以使用。
对于大多数开发人员来说,还有其他一些可能不太重要或不感兴趣的存储库,涵盖了诸如corba,jaxp和jaxws之类的东西。
Oracle最近启动了一个项目,对OpenJDK进行彻底的检查,并简化了构建基础结构。 这个名为“ build-dev”的项目现已完成,它是构建OpenJDK的标准方法。 对于基于Unix的系统上的许多用户而言,构建现在就像安装编译器和“ bootstrap JDK”,然后运行三个命令一样简单:
./configure
make clean
make images
有关构建自己的OpenJDK并开始对其进行黑客攻击的更多详细信息, AdoptOpenJDK程序 (由伦敦Java社区创建)是一个不错的起点-它是由将近100个草根开发人员组成的社区,他们从事警告清理,小型项目OpenJDK 8与主要开源项目的错误修复和兼容性测试。
OpenJDK提供的Java运行时环境包括HotSpot JVM和类库(这些类库大量捆绑到rt.jar中)。
由于Java是可移植的环境,因此需要调用操作系统的所有内容最终都由本机方法处理。 另外,某些方法需要JVM的特殊支持(例如,类加载)。 它们也可以通过本地调用传递给JVM。
例如,让我们看一下原始Object类的本机方法的C源代码。 Object的本地源包含在jdk / src / share / native / java / lang / Object.c中,它有六个方法。
Java本机接口(JNI)通常要求以一种非常特定的方式来命名本机方法的C实现。 例如,本机方法Object :: getClass()使用通常的命名约定,因此C实现包含在具有以下签名的C函数中:
Java_java_lang_Object_getClass(JNIEnv *env, jobject this)
JNI还有另一种加载本机方法的方式,由java.lang.Object的其余五个本机方法使用:
static 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},
};
这五个方法使用registerNatives()机制(允许开发人员将Java本机方法映射到C函数名称)映射到JVM入口点(由C方法名称上的JVM_前缀指定)。
总体情况是,Java运行时环境尽可能用Java编写,只有很少的地方需要使用JVM。 除了执行代码外,JVM的主要工作是对活动Java对象的运行时表示所在的环境(Java堆)进行内部维护和维护。
堆中的任何Java对象都由普通对象指针(OOP)表示。 OOP是C / C ++意义上的真正指针-指向Java堆中内存位置的机器字。 根据JVM进程的虚拟地址空间,将Java堆分配为单个连续的地址范围,然后由JVM进程本身纯粹在用户空间内管理内存,除非JVM由于任何原因需要调整堆大小。
这意味着Java对象的创建和收集通常不涉及分配或取消分配内存的系统调用。
OOP由标头的两个机器字组成,分别称为Mark和Klass字,后跟该实例的成员字段。 数组在字段之前有一个额外的标头字-数组的长度。
稍后我们将对Mark和Klass词有更多的话要说,但是它们的名称是故意暗示的-Mark词用于垃圾收集(在mark-and-sweep的mark部分),而Klass词用作指向类元数据的指针。
实例的字段以非常特定的顺序排列在OOP标头后面的字节中。 有关确切的详细信息,请阅读Nitsan Wakart的优秀博客文章“ Know thy Java Object Memory Layout ”。
基本字段和引用字段都放在OOP标头之后-对象引用当然也是OOP。 让我们看一个示例,Entry类(在java.util.HashMap中使用)
static class Entry implements Map.Entry {
final K key;
V value;
Entry next;
final int hash;
// methods...
}
现在,让我们计算一个Entry对象的大小(在32位JVM上)。
标头包含一个标记字和一个Klass字,因此OOP标头在32位上为8个字节(在64位HotSpot上为16个字节)。
使用OOP的定义,总大小为2个机器字+所有实例字段的大小
引用类型的字段显示为指针-这将是任何理智的处理器体系结构上的机器字大小。
因此,由于我们有一个int字段,两个引用字段(引用类型为K和V的对象)和一个Entry字段,所以总大小为2个单词(标题)+1个单词(int)+ 3个单词(指针)
这总共是24个字节(6个字),用于存储单个HashMap.Entry对象。
标头的分类词是OOP的最重要部分之一。 它是此类的元数据(表示为称为klassOop的C ++类型)的指针。 在此类元数据中,尤其重要的是此类的方法,这些方法表示为C ++虚拟方法表(“ vtable”)。
我们不希望每个实例都包含其方法的所有细节-这会非常低效-因此在klassOop上使用vtable是在实例之间共享该信息的好方法。
还需要注意的是,klassOops与类加载操作产生的Class对象不同。 两者之间的区别可以概括为:
记住这种区别的简单方法是将klassOop视为相关类的Class对象的JVM级“镜像”。
klassOops的vtable结构与Java的方法分派和单一继承直接相关。 请记住,默认情况下,Java的实例方法调度是虚拟的(因此,将使用要调用的实例对象的运行时类型信息来查找方法)。
这通过使用“恒定vtable偏移量”在klassOop vtables中实现。 这意味着覆盖方法在vtable中与被覆盖的父(或祖父母等)中的实现在相同的偏移量处。
通过简单地沿继承层次结构(从类到超类再到超超类的跟踪)并寻找始终在相同vtable偏移量处实现该方法的方法,就可以轻松实现虚拟调度。
例如,这意味着toString()方法对于每个类始终处于相同的vtable偏移量。 此vtable结构有助于单继承,并且在JIT编译代码时还可以进行一些非常强大的优化。
(点击图片放大)
OOP标头的Mark字是指向结构的指针(实际上只是一个位域集合,用于保存有关OOP的内务管理信息。
在正常情况下,在32位JVM上,标记结构的位域如下所示(有关更多详细信息,请参见hotspot / src / share / vm / oops / markOop.hpp):
哈希:25 —> | 年龄:4biased_lock:1锁:2
高25位包含对象的hashCode()值,后跟4位表示对象的使用期限(根据对象幸存的垃圾回收数量)。 其余3位用于指示对象的同步锁定状态。
Java 5引入了一种新的对象同步方法,称为“偏置锁定”(在Java 6中已成为默认方法)。 这个想法基于观察到的对象的运行时行为-在许多情况下,对象仅被一个线程锁定。
在偏置锁定中,对象被“偏向”锁定该对象的第一个线程-然后,该线程实现了更好的锁定性能。 获得偏差的线程记录在标记头中:
JavaThread *:23时代:2年龄:4biased_lock:1锁:2
如果另一个线程尝试锁定该对象,则将取消该偏向(并且不会重新获得该偏向)-从那时起,所有线程必须显式锁定和解锁该对象。
该对象的可能状态如下:
HotSpot源中相关OOP类型的层次结构相当复杂。 这些类型保存在:hotspot / src / share / vm / oops中,包括:
有一些稍微奇怪的历史事故-虚拟调度表(vtables)的内容与klassOops保持分开,并且markOop看起来与其他oops不同,但仍包含在同一层次结构中。
可以直接看到OOP的一个有趣地方是jmap命令行工具。 这提供了堆内容的快速快照,包括permgen中存在的所有oop(包括klassOops所需的子类和支持结构)。
$ jmap -histo 150 | head -18
num #instances #bytes class name
----------------------------------------------
1: 10555 21650048 [I
2: 272357 6536568 java.lang.Double
3: 25163 5670768 [Ljava.lang.Object;
4: 229099 5498376 com.jclarity.censum.dataset.CensumXYDataItem
5: 39021 5470944
6: 39021 5319320
7: 8269 4031248 [B
8: 3161 3855136
9: 119759 2874216 org.jfree.data.xy.XYDataItem
10: 3161 2773120
11: 2894 2451648
12: 34012 2271576 [C
13: 87065 2089560 java.lang.Long
14: 20897 2006112 [Lcom.jclarity.censum.CollectionType;
15: 33798 1081536 java.util.HashMap$Entry
尖括号中的条目是各种类型的oop,而诸如[I和[B]的条目分别指的是整数和字节的数组。
HotSpot是比开发人员通常更熟悉的简单的“ while循环切换”样式的解释器更高级的解释器。
而是HotSpot是模板解释器。 这意味着将构建一个优化的机器代码的动态调度表-特定于所使用的操作系统和CPU。 大多数字节码指令都实现为汇编语言代码,而只有更复杂的指令(例如,从类文件的常量池中查找条目)才委托给VM。
这提高了HotSpot的解释器性能,但付出的代价是使其更难以将VM移植到新的体系结构和操作系统上。 这也使新开发人员更难以理解解释器。
在入门方面,通常使开发人员对OpenJDK提供的运行时环境有一个基本的了解:
从那里,开发人员可以开始探索jdk存储库中的Java代码,或者寻求建立其C / C ++和汇编程序知识以更深入地研究HotSpot本身。
翻译自: https://www.infoq.com/articles/Introduction-to-HotSpot/?topicPageSponsorship=c1246725-b0a7-43a6-9ef9-68102c8d48e1
jdk8和hotspot