OpenJDK新手教程之编译HotSpot

Stella981
• 阅读 983

前言

讲OpenJDK/HotSpot编译的文章比较多了,玩Linux的朋友自是不在话下,Windows下的也有那么几篇:

  1. 《深入理解Java虚拟机》作者周志明的《自己动手编译Windows版的OpenJDK 7》(http://icyfenix.iteye.com/blog/1097344)
  2. ZZH2009写的《在Windows中构建OpenJDK8u已经很简单了》(http://hllvm.group.iteye.com/group/topic/41271)

本文是按照ZZH2009的思路,对一些细节作的扩充,就算是Java刚入门,没有任何构建编译方面的知识,照着下面的步骤,也可以毫无困难的编译出属于你的HotSpot。

工具准备

安装Visual Studio

在Microsoft网站下载安装免费的Express版就可以了,设置为Visual C++风格。我这里安装的是Visual Studio 2013 Express。路径D:\Develop\VS2013。没有管理员权限的,可以拷贝一份Windows SDK和VC目录,真正需要的只是它的编译器和链接器而已,然后设置编译环境变量即可。

安装Cygwin

Cygwin的安装也比较简单,从官网down下安装器后,选择速度快的下载源,配置好所需的package就可以自动下载了。我用的是Windows7 64位系统,JDK也用的是64位,所以也下载64位的Cygwin,放在默认路径C:\cygwin64。 OpenJDK新手教程之编译HotSpot 在README-builds.html文件里,有完整的依赖包清单,见下表。其实这个是最权威的的文档了,英文好的单看这个文件就够了。这些命令在make以及创建VS项目文件的脚本中会被调用,缺少的话会造成build中断。如果对Mikefile很熟的话,当然可以通过修改Makefile,比如显示指定系统平台及架构等make变量绕过一些错误。但我们这里要保证照顾的make零基础的朋友,还是请乖乖的安装好所有这些包。要注意安装的最小单位是package,不是exe可执行文件。

Binary Name

Category

Package

Description

ar.exe

Devel

binutils

The GNU assembler, linker and binary utilities

make.exe

Devel

make

The GNU version of the 'make' utility built for CYGWIN.

m4.exe

Interpreters

m4

GNU implementation of the traditional Unix macro processor

cpio.exe

Utils

cpio

A program to manage archives of files

gawk.exe

Utils

awk

Pattern-directed scanning and processing language

file.exe

Utils

file

Determines file type using 'magic' numbers

zip.exe

Archive

zip

Package and compress (archive) files

unzip.exe

Archive

unzip

Extract compressed files in a ZIP archive

free.exe

System

procps

Display amount of free and used memory in the system

配置环境变量

可能你用的电脑没有Windows管理员权限,可能你希望保持操作系统的清洁,不希望让一大堆环境变量打架,那么下面的脚本是你需要的。当然脚本也不能藏得太深,最好放在cmd home目录下,在我的电脑上是放在C:\Users\fw8899\env.bat,每次进入cmd的时候直接敲一下env就都搞定了。这不是一劳永逸的做法,每次关闭后开启新的cmd你都得重复这个步骤。还是嫌麻烦吗,那么请看一下本段前面的两个前提。

@echo off
echo Start setting environment ...

echo Setting Java ...
set JAVA_HOME=D:\JavaTools\jdk1.7.0_75
set PATH=%JAVA_HOME%\bin;%PATH%

echo Setting NASM...
set NASM_Home=D:\Develop\nasm
set PATH=%NASM_Home%;%PATH%

set USE_CYGWIN=true
if "%USE_CYGWIN%"=="true" goto CYGWIN
:MINGW
echo Setting MinGW...
set MINGW_HOME=D:\Develop\CodeBlocks\MinGW
set PATH=%MINGW_HOME%\bin;%PATH%
goto VC

:CYGWIN
echo Setting Cygwin ...
set CYGWIN_HOME=C:\cygwin64
set PATH=%CYGWIN_HOME%\bin;%PATH%

:VC
echo Setting Visual Studio ...
set VC_HOME=D:\Develop\VS2013

echo Setting Mercurial ...
set MERCURIAL_HOME=D:\Develop\Mercurial
set PATH=%MERCURIAL_HOME%;%PATH%

echo Setting environment finished.
@echo on

下载源码

OpenJDK的源码管理用的是非主流的Mecurial,跟Git极为相似,有Git基础的话绝对能毫无障碍的驾驭。啊~你连Git也没用过!没关系,只需按照下面的步骤,保证能把整个OpenJDK完整的下载下来。

安装Mecuriail

这种开源的软件当然是要到官网下载啦(google的除外,比如AngularJS,你懂的),这里是Mercurial下载页面(https://mercurial.selenic.com/downloads)。直接上最新版,当前是3.5.1,下载Mercurial WIndows x64免管理员权限版。不要下载要管理员权限的MSI安装包,以及长像貌似TortoiseSVN的TortoiseHg,这两个家伙会污染Windows系统环境变量或者右键菜单,在我里那是绝对不能容许的。当然你要是准备用它作为你的主力源码控制系统的话,那就没啥说的了(话说你也跟Merucial一样非主流麽)。

Clone源码

首先是给我们的OpenJDK在本地硬盘找个窝,在我本机是D:\workspace\cpp。我的习惯是所有开发相关源码放在一个workspace,然后按照编程语言创建子目录,比如asm, c, conf, cpp, java, js, python, scala, shell。然后IDE的配置文件目录和workspace就定位到相应目录,比如Eclipse默认workspace就是D:\workspace\java, CodeBlocks的是D:\workspace\cpp。这样做的好处是所有源码和配置都集中在一起,便于在不同电脑上和不同操作系统下同步。当然这样的代码组织方式仅适合个人学习,在公司里还是乖乖按照规矩来吧。

闲话少叙,OpenJDK的核心是HotSpot,HotSpot的核心部分是C++,放在cpp目录下也是自然的。接下来进入OpenJDK官网下载地址(http://openjdk.java.net/install/index.html)。选择多了也不是好事,左边这么多版本,选择哪个好呢?我们就下载编译跟所用的Oracle JDK一样的版本好了(这不是最佳实际,具体见后文)。OK,那先看一下自己的JDK版本。

D:\workspace\cpp>java -version
java version "1.7.0_75"
Java(TM) SE Runtime Environment (build 1.7.0_75-b13)
Java HotSpot(TM) 64-Bit Server VM (build 24.75-b04, mixed mode)

我的JDK是1.7.0_75-b13,对应到Open JDK就是OpenJDK7 update build13。所以代码仓库repo分支就是JDK 7u,不是JDK7。关于Open JDK分支进化的详细情况,可以参考《OpenJDK源码阅读导航》(http://rednaxelafx.iteye.com/blog/1549577)

进入jdk7u的代码仓库页面(http://hg.openjdk.java.net/jdk7u/),再次碰到选择题,各路jdk7u,jdk7u-dev,jdk7u-osx,jdk7u4,jdk7u40...都是神马啊? 在你没有足够耐心去了解的情况下,这里给你做个选择:选jdk7u(不带任何后缀)。 OpenJDK新手教程之编译HotSpot

翻看它的tags(http://hg.openjdk.java.net/jdk7u/jdk7u/tags),可以发现基本上跟Oracle官方发布的JDK是同步的。据RedNaxelaFX大神上所说,Oracle的JDK开发就是直接在这个库上搞,这下再也不用担心JDK版本的一致性了:D。 OpenJDK新手教程之编译HotSpot

接下来开始克隆代码

cd D:\workspace\cpp
# clone远程仓库到本地jdk7u文件夹
hg clone http://hg.openjdk.java.net/jdk7u/jdk7u jdk7u



D:\workspace\cpp>hg clone http://hg.openjdk.java.net/jdk7u/jdk7u jdk7u
正在请求全部修改
正在增加修改集
正在增加清单
正在增加文件改变
已增加 1242 个修改集,包含 1210 个改变,修改了 34 个文件
updating to branch default
33 files updated, 0 files merged, 0 files removed, 0 files unresolved

分分钟clone完毕,安静的躺在jdk7u目录里了。真的clone完了吗?OpenJDK的代码怎么说也有几百M,哪是这么快就能搞定的。进入jdk7u目录,发现有个get_source.sh,运行之。这是Linux shell脚本,咱这可是Windows,能行吗?你忘了我们之前装过的Cygwin,这货就可以干这个。当然我们没必要先进入cygwin的shell再运行,直接Windows的命令行走起。

cd jdk7u
sh get_source.sh

这下等的时间就长了,足够吃顿饭喝杯茶。下载的时候,hg命令不会提示当前下载进度,看上去像没有响应,有点坑,需要有些耐心。这段时间里我们看一下get_source.sh的内容。

# Get clones of all nested repositories
sh ./make/scripts/hgforest.sh clone $*

# Update all existing repositories to the latest sources
sh ./make/scripts/hgforest.sh pull -u

get_source.sh实际上调用了jdk7u\make\scripts\hgforest.sh脚本,这个脚本里定义了jdk7u的subrepo,也就是它的子代码仓库: subrepos="corba jaxp jaxws langtools jdk hotspot"。这个列表是不是有些面熟?对了,上边jdk7u图里,jdk7u下方几行所列正是这几个subrepo。

get_source.sh干的活,就是把这几个子仓库一个个都clone下来,然后pull拉取代码。

漫长的等待结束,运气好的话,所有subreo都会提示当前下载文件数量,并返回ErrorCode 0,表示下载正常搞定。如果出现网络故障,hg会给出ErrorCode 255并终止当前subrepo的下载,回滚删除subrepo目录,继续下一个subrepo的下载。出现这种情况只需要再次运行get_source.sh,它会重试下载失败的subrepo。

代码下载完毕,默认是最新的commit,我们需要checkout所需版本号,在每个subrepo下通过tags找到对应的版本号,hotspot的jdk7u75-b13 Changeset是5557,迁出它。 OpenJDK新手教程之编译HotSpot

cd hotspot
# checkout jdk7u575-b13的hotspot源码
hg update -r 5557

下载源码包

可能你不想这么麻烦,为了下个源码还得安装一套源码工具,那么你可以直接下载已经打包好的OpenJDK源码。这里有坑要注意一下: OpenJDK 7,网上比较容易找到的,有两个官网下载地址:

从时间和版本号上就可以看出,这两个版本都比较旧。第一个版本的编译Bug比较多,网上随处都能找到。第二个版本,当我用编译后的hotspot7u40联合本地JDK 1.7.0_75-b13进行调试,加载dll的时候,特码的提示JVM_findClassFromCaller函数找不到。经过检查源码,发现7u40的hotspot\src\share\vm\prims\jvm.cpp中,根本就没有这个函数。这个问题也被作为Bug 8015256在后续版本进行修复。所以经验是:

  • 尽量下载最新的OpenJDK源码,避免踩坑,
  • 另外BOOTSTRAP_JDK(用来编译OpenJDK的Oracle JDK)的版本不能高于当前要编译的JDK版本,最好是最低于它且接近它。比如我的BOOTSTRAP_JDK是7u75-b13,用来编译7u78的就比较合适。本文为了得到一个相同版本的OpenJDK比较差异,才选了一样的源码来build号。
  • 编译OpenJDK 8/8u时,最好用高build号的JDK 7作为BOOTSTRAP_JDK(这个在README-builds.html中有提及)。

手动下载正确的做法是,直接去repo下载对应的zip包。当然你只下载jdk7u的源码包是没用的,还得下载是它下边的所有子仓库的包,相当于把Mercurial干的活都自己手动完成。在hotspot的tags下找到7u75-b13对应的commit,点进去后就可以下载该版本的源码包,然手手动解压放到jdk7u目录下。其他几个subrepo类似。

编译HotSpot

生成Visual Studio项目文件

源码就位,开始编译。进入jdk7u\hotspot\make\windows文件夹,建立一个批处理文件vs2010.bat,因为我们要利用它生成Visual Studio 2010的项目文件。

set VC_HOME=D:\Develop\VS2013\VC
REM 设置VC相关的环境变量,指定参数x64会配置64位的编译环境变量
REM 这样就可以使用64位的Windows SDK,和64位的编译器
%VC_HOME%\vcvarsall.bat x64

REM make工具链所在路径
set HOTSPOTMKSHOME=C:\cygwin64\bin

REM VC的版本,1600对应VC2010,jdk7u目前支持的最高版本
set FORCE_MSC_VER=1600

REM 调用OpenJDK提供的工具自动创建VS2010项目文件
create.bat %JAVA_HOME%

这里需要等上几分钟,create脚本会根据上边的配置,选定系统平台(Patform)和架构(Arch)创建相应的build目录,用系统JDK的javac编译并运行jdk7u\hotspot\src\share\tools\ProjectCreator下对应的java文件,创建出Visual Studio项目文件。

OpenJDK新手教程之编译HotSpot

这个阶段出现问题,肯定是Cygwin, Visual Studio, 或之前的JDK路径没有正确设置,导致脚本找不到相应命令。按照上面的配置,创建完成后,在jdk7u\hotspot\build\vs-amd64目录下,就会发现jvm.vcxproj,那正式我们要的。

推荐有兴趣的伙伴翻看一遍hotspot的Makefile,这样对hotspot组织结构有一个详细的了解,build过程中出现问题时也能快速解决。

编译源码

Microsoft的工具正式出场,对于这样易用的工具没什么好说的,打开生成的jvm.vcxproj文件,直接build就可以了。build完成后,在\hotspot\build\vs-amd64\compiler1\debug下赫然躺着hotspot.exe,运行一下,跟Oracle JDK版本一模一样,只是多了个internal。

D:\workspace\cpp\jdk7u\hotspot\build\vs-amd64\compiler1\debug>hotspot -version
Using java runtime at: D:\JavaTools\jdk1.7.0_75\jre
java version "1.7.0_75"
Java(TM) SE Runtime Environment (build 1.7.0_75-b13)
OpenJDK 64-Bit Client VM (build 24.75-b04-internal-debug, mixed mode)

常见问题

这里列出我碰到的几个小问题,仅供参考。

  1. MSB8020, Platform Toolset not found 我们生成的是VS2010项目,默认的Platform Toolset是Visual Studio 2010(v100),自然找不到只需要在项目[Properties] -> [Configuration Prperties] -> [General] -> [Platform Toolset]设置成,这里设成Visual Studio 2013 (v120)。

  2. C2220: warning treated as error HotSpot源码中,个别函数的定义,与Windows SDK的文件不完全相同, 例如

    // C标准库math.h _CRTIMP double __cdecl copysign(In double _X, In double _Y);

    // hotspot\src\share\vm\runtime\sharedRuntimeTrans.cpp中的实现 double copysign(double x, double y) { __HI(x) = (__HI(x)&0x7fffffff)|(__HI(y)&0x80000000); return x; }

这种警告不会影响我们的编译,只要不让警告作为错误就可以了,在项目[Properties] -> [C/C++] -> [General] -> [Treat Warning As Error]中设置。

  1. error C2011: '_DISPATCHER_CONTEXT' : 'struct' type redefinition 这是真正的冲突了,最简单直接的办法,注释掉HotSpot里的注释掉这段代码。

    // Windows SDK 8.1 \include\um\winnt.h typedef struct _DISPATCHER_CONTEXT { DWORD64 ControlPc; DWORD64 ImageBase; PRUNTIME_FUNCTION FunctionEntry; DWORD64 EstablisherFrame; DWORD64 TargetIp; PCONTEXT ContextRecord; PEXCEPTION_ROUTINE LanguageHandler; PVOID HandlerData; PUNWIND_HISTORY_TABLE HistoryTable; DWORD ScopeIndex; DWORD Fill0; } DISPATCHER_CONTEXT, *PDISPATCHER_CONTEXT;

    // hotspot\src\os_cpu\windows_x86\vm\unwind_windows_x86.hpp typedef struct _DISPATCHER_CONTEXT { ULONG64 ControlPc; ULONG64 ImageBase; PRUNTIME_FUNCTION FunctionEntry; ULONG64 EstablisherFrame; ULONG64 TargetIp; PCONTEXT ContextRecord; // PEXCEPTION_ROUTINE LanguageHandler; char * LanguageHandler; // double dependency problem PVOID HandlerData; } DISPATCHER_CONTEXT, *PDISPATCHER_CONTEXT;

调试

关于调试的技巧,内容就多了,有空再来补充。

总结

再次总结一下编译HotSpot的步骤:

  1. 安装Cygwin, Visual Studio 2013 Express, Mecurial
  2. 设置环境变量
  3. 下载OpenJDK源码
  4. 生成VS2013项目文件
  5. 在VS2013中编译,调试
点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Stella981 Stella981
2年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Easter79 Easter79
2年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这