深入理解JVM虚拟机-Ubuntu中安装openJDK

1,252 阅读4分钟

最近利用闲暇时间看了看《深入理解java虚拟机》来提高自身得知识储备,再这里准备将读书学习到得知识和个人的心得记录下来。首先调整好自己的心态,不要浮躁,因为编译阶段会无限踩坑。

1.安装VMware和Ubuntu系统

这本书得里用得是Ubuntu系统,在这里我为了少踩坑放弃了之前一直使用得Centos7改为使用Ubuntu。VMware和Ubuntu系统安装比较简单,这里我也是参照一篇博客安装得,按照步骤安装就可以了,这里我用的乌班图版本为ubuntu-14.04.6-desktop-amd64,这里没有使用跟本书写着用的Ubuntu-10版本的,因为10版本的更新源实在太难找了。
VMware上安装乌班图系统参照此篇博客
当然,这里面还是略微有些坑。

  1. 安装好系统后只有用户的账号密码设置,但是你在很多操作的时候都会显示权限不足,这时候你需要修改root账户的密码,使用命令sudo passwd(打开命令行操作界面快捷键为Ctrl+Alt+T),然后输入你要设置的root密码就可以了。
  2. VMware Tools安装。乌班图系统安装好后你会发现在WMware的界面上显示的窗口非常小,这时候你需要在系统上装上WMware tools。
    点击WMware控制台上的虚拟机-安装WMware Tools后,会在你的乌班图系统中装载,右键将压缩包提取到桌面,右键解压好的文件夹在终端打开,执行命令sudo ./vmware-install.pl,第一个提示输入yes后一路回车,安装好之后重启就可以了。

2.编译openJDK

2.1 ANT等编译工具下载

首先打开终端,输入指令:

#依赖脚本
sudo apt-get install build-essential gawk m4 libasound2-dev libcups2-dev libxrender-dev xorg-dev xutils-dev x11proto-print-dev binutils libmotif3 libmotif-dev libxt-dev ant libxtst-dev
#opendjdk7的依赖
sudo apt-get build-dep openjdk-7

这一步有可能会报错

错误图片
这时需要先执行sudo apt-get update,再执行上面的命令就可以了。

2.2 手动下载包

  1. openjdk7的源包 下载地址,下载后进行解压即可。
  2. oracle的jdk6的源包,由于ubuntu系统自带了openjdk8,而我要用oracleJDK1.7,所以需要先卸载系统自带的openjdk,卸载如下:
如果安装了OpenJDK,可用如下方法全部卸载:
$sudo apt-get purge openjdk*

卸载后去oracle官网下载jdk,我下载的是jdk7u80。下载完成后传输到Ubuntu中,放入事先创建好的文件夹,我的openjdk和jdk都是放在/home/java路径下,然后进行解压,解压后需要配置一下jdk的环境配置。输入sudo gedit ~/.bashrc,新增:

export JAVA_HOME=/home/java/jdk1.7.0_80
export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
export PATH=$JAVA_HOME/bin:$PATH

最后使配置生效:

source ~/.bashrc

2.3 配置ubuntu的JDK和JRE的位置

$ sudo update-alternatives --install "/usr/bin/java" "java" "/home/java/jdk/jdk1.7.0_80/bin/java" 1
$ sudo update-alternatives --install "/usr/bin/javac" "javac" "/home/java/jdk/jdk1.7.0_80/bin/javac" 1
$ sudo update-alternatives --install "/usr/bin/javaws" "javaws" "/home/java/jdk/jdk1.7.0_80/bin/javaws" 1

2.4 配置Oracle为系统默认JDK/JRE

$ sudo update-alternatives --set java /home/java/jdk/jdk1.7.0_80/bin/java
$ sudo update-alternatives --set javac /home/java/jdk/jdk1.7.0_80/bin/javac
$ sudo update-alternatives --set javaws /home/java/jdk/jdk1.7.0_80/bin/javaws

3. build.sh文件的配置及编译

再解压好的openjdk文件夹中新建一个build.sh,将配置写入。此处没有采用本书的做法,但方法都一样,只需要将配置放入/etc/profile中,然后source /etc/profile即可。

# 语言选项,必须设置,否则编译好后会出现一个 HashTable 的 NPE错
export LANG=C
 
# Bootstrap JDK 解压路径,必须设置
export ALT_BOOTDIR=/home/java/jdk1.7.0_80
 
# 允许自动下载
export ALLOW_DOWNLOADS=true
 
# 并行编译线程数
export HOTSPOT_BUILD_JOBS=4
export ALT_PARALLEL_COMPILE_JOBS=4
 
# 比较本次 build 出来的映像与先前版本的差异,对我们没有意义
# 必须设置为 false,否则 sanity 检查为报缺少先前版本 JDK 的映像的错误提示
export SKIP_COMPARE_IMAGE=false
 
# 使用预编译头文件,不加这个编译会变慢
export USE_PRECOMPILED_HEADER=true
 
# 要编译的内容 这里我们全编译其实只要前三个就可以了自行注释
export BUILD_LANGTOOLS=true
export BUILD_HOTSPOT=true
export BUILD_JDK=true
#export BUILD_JAXWS=true
#export BUILD_JAXP=true
#export BUILD_CORBA=true
 
# 要编译的版本 
# export SKIP_DEBUG_BUILD=false
# export SKIP_FASTDEBUG_BUILD=true
# export DEBUG_NAME=debug
 
# 把它设置为 false 可以避开 javaws 和浏览器 Java 插件之类的部分的 build
BUILD_DEPLOY=false
 
# 把它设置为 false 就不会 build 出安装包,因为安装包里有奇怪的依赖
# 但即使不 build 出它也能得到完整的 JDK 映像,所以还是别 build
BUILD_INSTALL=false
 
# 编译结果所存放的路径
export ALT_OUTPUTDIR=/home/java/openjdk/build

export CFLAGS="-Wno-error"
export CXXFLAGS="-Wno-error"
 
# 这两个环境变量必须去掉,不然会发生奇怪的事情
# Makefile 检查到这两个变量就会提示警告
unset JAVA_HOME
unset CLASSPATH

make 2>&1 | tee $ALT_OUTPUTDIR/build.log

然后赋予build.sh权限并运行,如下:

chmod 755 build.sh
#此命令是直接启动程序进行编译
./build.sh

4. 踩坑阶段(WTFK)

好了,轻松愉快的阅读和复制粘贴工作结束,下面就是最开心的踩坑阶段,调整好自己的心情,打开百度和Google,准备开始一场烧脑的网络冲浪吧。

4.1 echo “* This OS is not supported:” uname -a; exit 1

* This OS is not supported: Linux pgc-virtual-machine 3.11.0-12-generic #19-Ubuntu SMP Wed Oct 9 16:12:00 UTC 2013 i686 i686 i686 GNU/Linux 
2.3 ERROR: echo “* This OS is not supported:” ‘uname -a‘; exit 1; 

这需要注释掉hotspot/make/linux/Makefile里面的checkOS。

check_os_version:
#ifeq ($(DISABLE_HOTSPOT_OS_VERSION_CHECK)$(EMPTY_IF_NOT_SUPPORTED),)
# $(QUIETLY) >&2 echo "*** This OS is not supported:" `uname -a`; exit 1; 
#endif

也可以最好的办法是在make参数后面添加 :
DISABLE_HOTSPOT_OS_VERSION_CHECK=OK

4.2 BUILD FAILED

/home/pgc/Downloads/openjdk/build/jaxws/build/xml_generated/build-drop-jaf_src.xml:96: Redirection detected from https to http. Protocol switch unsafe, not allowed. 

在这个build-drop-jaf_src.xml的96行中获取包下载的地址失效或有问题,就会导致改问题发生,这时你需要把那些包自己下载,主要是jdk7-jaf-2010_08_19.zip这个包下载不了。
需要自己下载依赖的jaxp、jaf、jaxws
jaxp145_01.zip
jdk7-jaxws2_2_4-b03-2011_05_27.zip
jdk7-jaf-2010_08_19.zip
包下载好后进入openjdk目录,创建drop文件夹,然后,把这下载到的三个包放到drop文件夹中。再执行:

export ALT_DROPS_DIR=/home/java/openjdk/openjdk/drop

上面的路径需要根据自己的情况进行修改。

4.3 .ed.hpp:36

/home/pgc/Downloads/openjdk/hotspot/src/share/vm/runtime/interfaceSupport.hpp:430:0: error: “__LEAF” redefined [-Werror] 
#define __LEAF(result_type, header) \ 
^ 
![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/3/25/169b54f02337c7c8~tplv-t2oaga2asx-image.image)
In file included from /usr/include/features.h:374:0, 

这是cdefs.h中定义的宏“LEAF”与interfaceSupport.hpp冲突。可以在interfaceSupport.hpp中增加一个“#undef LEAF”语句来解决冲突,interfaceSupport.hpp的文件地址在错误日志中找到,或者可以使用find命令来进行查找。

// LEAF routines do not lock, GC or throw exceptions
#ifdef __LEAF
#undef __LEAF
#define __LEAF(result_type, header) \
TRACE_CALL(result_type, header) \
debug_only(NoHandleMark __hm;) \
/* begin of body */
#endif 

4.4 error: converting ‘false’ to pointer type ‘methodOop’

修改/openjdk/hotspot/src/share/vm/oops/constantPoolOop.cpp 第272行 return false改为return (methodOop)false; 或者 return NULL。

4.5 error: converting ‘false’ to pointer type ‘Node*

修改openjdk/hotspot/src/share/vm/opto/loopnode.cpp: 第896行 return false改为return (Node*)false; 或者 return NULL;

4.6 ERROR gcc: error: unrecognized command line option ‘-mimpure-text’

这个-mimpure-text是gcc给Solaris的编译选项,所以注释掉或删掉即可。 文件在./jdk/make/common/shared/Compiler-gcc.gmk

4.7 Error: time is more than 10 years from present: 1136059200000

修改CurrencyData.properties(路径:jdk/src/share/classes/java/util/CurrencyData.properties)

修改108行
AZ=AZM;2018-12-31-20-00-00;AZN
修改381行
MZ=MZM;2018-06-30-22-00-00;MZN
修改443行
RO=ROL;20189-06-30-21-00-00;RON
修改535行
TR=TRL;2018-12-31-22-00-00;TRY
修改561行
VE=VEB;2018-01-01-04-00-00;VEF

上面的行数有可能有略微偏差,找到行数的附近然后根据关键字查找就可以。

4.8 collect2: error: ld returned 1 exit status

make[5]: * [/home/pgc/Downloads/openjdk/build/lib/i386/libjsoundalsa.so] Error 1
make[5]: Leaving directory `/home/pgc/Downloads/openjdk/jdk/make/javax/sound/jsoundalsa’
make[4]: * [build] Error 1 

遇到和”javax/sound/jsoundalsa”相关的一个错误,需要修改openjdk/jdk/make/javax/sound/jsoundalsa目录下的Makefile文件,找到 LDFLAGS += -lasound 修改为 OTHER_LDLIBS += -lasound

4.9 ./test_gamma错误

这需要去掉或者注释掉hotspot/make/linux/Makefile 文件中所有包含test_gamma的整行!!!一定要是整行!

5 总结

以上的问题应该还不是编译openjdk所有的坑,可能有些问题我还没有遇到,但这些坑就已经让我搞了三天的时间才搞定,如果遇到其他比较奇葩的坑,那只能自行baidu和Google了,最终顺利的话静静等待10分钟左右(依每人各电脑性能不同,时间可能不一样)的漫长等待,最终就编译好了,编译完成后会打印一下信息。


如此,就大功告成了。

6 编译HotSpot

jdk编译完成后需要编译HotSpot,下面来一段HotSpot的介绍

提起HotSpot VM,相信所有Java程序员都知道,它是Sun JDK和OpenJDK中所带的虚拟机,也是目前使用范围最广的Java虚拟机。
但不一定所有人都知道的是,这个目前看起来“血统纯正”的虚拟机在最初并非由Sun公司开发,而是由一家名为“Longview Technologies”的小公司设计的;
甚至这个虚拟机最初并非是为Java语言而开发的,它来源于Strongtalk VM,
而这款虚拟机中相当多的技术又是来源于一款支持Self语言实现“达到C语言50%以上的执行效率”的目标而设计的虚拟机,
Sun公司注意到了这款虚拟机在JIT编译上有许多优秀的理念和实际效果,在1997年收购了Longview Technologies公司,从而获得了HotSpot VM。

HotSpot VM既继承了Sun之前两款商用虚拟机的优点(如前面提到的准确式内存管理),也有许多自己新的技术优势,
如它名称中的HotSpot指的就是它的热点代码探测技术(其实两个VM基本上是同时期的独立产品,HotSpot还稍早一些,HotSpot一开始就是准确式GC,
而Exact VM之中也有与HotSpot几乎一样的热点探测。
为了Exact VM和HotSpot VM哪个成为Sun主要支持的VM产品,在Sun公司内部还有过争论,HotSpot打败Exact并不能算技术上的胜利),
HotSpot VM的热点代码探测能力可以通过执行计数器找出最具有编译价值的代码,然后通知JIT编译器以方法为单位进行编译。
如果一个方法被频繁调用,或方法中有效循环次数很多,将会分别触发标准编译和OSR(栈上替换)编译动作。
通过编译器与解释器恰当地协同工作,可以在最优化的程序响应时间与最佳执行性能中取得平衡,而且无须等待本地代码输出才能执行程序,
即时编译的时间压力也相对减小,这样有助于引入更多的代码优化技术,输出质量更高的本地代码。

在2006年的JavaOne大会上,Sun公司宣布最终会把Java开源,并在随后的一年,陆续将JDK的各个部分(其中当然也包括了HotSpot VM)在GPL协议下公开了源码,
并在此基础上建立了OpenJDK。这样,HotSpot VM便成为了Sun JDK和OpenJDK两个实现极度接近的JDK项目的共同虚拟机。

在2008年和2009年,Oracle公司分别收购了BEA公司和Sun公司,这样Oracle就同时拥有了两款优秀的Java虚拟机:JRockit VM和HotSpot VM。
Oracle公司宣布在不久的将来(大约应在发布JDK 8的时候)会完成这两款虚拟机的整合工作,使之优势互补。
整合的方式大致上是在HotSpot的基础上,移植JRockit的优秀特性,譬如使用JRockit的垃圾回收器与MissionControl服务,
使用HotSpot的JIT编译器与混合的运行时系统。

废话不多少,盘他。 首先按照书上步骤是进入编译输出目录,./build/j2sdk-image中,说里面有一个gamma的运行文件,恕我眼拙,找了半天只看到了test_gamma,这是什么鬼?阉割版?好吧,按照书上继续来,进行env.sh的配置,但我发现env.sh里面已经配置好了,emmmmm...........好智能,下一步把,运行./test_gamma -version,发现报错了,果然是阉割版靠不住。经过一番百度和google之后发现,原来还要编译HotSpot,好吧,看来我对书上讲的还不是特别理解。 ####6.1 编译 编译HotSpot非常简单,将刚刚创建的build.sh复制一份到/openjdk/hotspot/make中,如果是64位系统需要加上export ARCH_DATA_MODEL=64,否则回报错,然后改一下编译后的输出路径,如果都放到build中比较难找,所以我再openjdk目录下新建了一个hotbuild目录作为输出目录,最后直接运行就可以了。 执行成功后,修改env.sh,如果有就不用添加,没有添加下面两行

LD_LIBRART_PATH=.:{JAVA_HOME}/jre/lib/amd64/native_threads:%{JAVA_HOME}/jre/lib/amd64:
export LD_LIBRART_PATH

然后运行source ./env.sh使配置生效,最后找到正版的gamma,执行./gamma -version,正确会输出一下日志。

6.2 问题(OMG)

如果有人按照我的步骤做非常完美的成功了,那么恭喜你。
在上一步./gamma -version这一步我遇到了一个问题,忘记截图了,大致的意思就是在$JAVA_HOME/jre/lib/amd64这个里面找不到东西(emmm...大概就是这个意思,原谅我比较low的英文水平)。这时候就需要反思一下前面的步骤,这时我突然想起了书上的一句话,大概意思时”将/build/j2sdk-image中的文件复制到JAVA_HOME路径下就可以作为完整的JDK使用“,那么我们前面的JAVA_HOME路径指向的是Oracle JDK,是不是这个地方出了问题。so,我将JAVA_HOME路径下的所有文件删除,然后将j2sdk-image中的文件都复制过去,然后运行./gamma -version,果然就成功了。

7 安装netBeans

搞完上面两步,距离成功只剩一点点了,最后来下载安装netBeans,直接官网下载就可以,我安装的是8.0版本的,要选择支持C/C++开发的那个版本。 安装成功后打开netBeans,新建项目。选择基于现有源代码的C/C++项目。

下一步,指定文件夹/home/java/openjdk/hotspot,选择定制,下一步。
选择Makefile路径,在hotspot/make中,下一步。

这里需要修改构建命令(其中ALT_BOOTDIR为你编译后的jdk路径):

${MAKE} -f Makefile clean jvmg ALT_BOOTDIR=/usr/lib/jvm/jdk1.7.0_80 ARCH_DATA_MODEL=64 LANG=C

一直下一步,然后等待build成功。成功后在项目上右键点击属性,这里修改三个参数。
运行命令修改:

/home/java/openjdk/hotspot/build/linux/linux_amd64_compiler2/jvmg/gamma -XX:StopInterpreterAt=1 -version /home/java/openjdk/hotspot/build/linux/linux_amd64_compiler2/jvmg/Queens

运行目录定位到/home/java/openjdk/hotspot/build/linux/linux_amd64_compiler2/jvmg
环境修改为jvmg目录下的env.sh中的参数 CLASS_PATH JAVA_HOME LD_LIBRART_PATH

最后就可以进行运行调试了,启动的main入口见图。