JVM源码分析-JVM源码编译与调试
要分析JVM的源码,结合资料直接阅读是⼀种⽅式,但是遇到⼀些想不通的场景,必须要结合调试,查看执⾏路径以及参数具体的值,才能搞得明⽩。所以我们先来把JVM的源码进⾏编译,并能够使⽤GDB进⾏调试。
编译环境
其他环境说明:
CentOS 7.4 64位
gcc version 4.8.5 20150623 (Red Hat 4.8.5-39) (GCC)
GNU Make 3.82
安装依赖
yum -y install gcc gcc-c++ make
yum -y install alsa-lib-devel
yum -y install cups-devel
yum -y install libX*
yum -y install gcc gcc-c++
yum -y install libstdc++-static
wget /repos/dchen/po -O /pos.po
yum -y install ant
$ sh jdk-6u38-linux-x64-rpm.bin
$ sudo rpm -ivh jdk-6u38-linux-amd64.rpm
编写编译脚本
解压openjdk:
unzip openjdk-7-fcs-src-b147-27_jun_2011.zip
在openjdk⽬录下添加⼀个build.sh脚本:
#!/bin/bash
export LANG=C
#将⼀下两项设置为你的BootstrapJDK安装⽬录
export ALT_BOOTDIR=/usr/java/jdk1.6.0_38
export ALT_JDK_IMPORT_PATH=/usr/java/jdk1.6.0_38
#允许⾃动下载依赖包
export ALLOW_DOWNLOADS=true
#使⽤预编译头⽂件,以提升便以速度
export USE_PRECOMPILED_HEADER=true
#要编译的内容,我只选择了LANGTOOLS、HOTSPOT以及JDK
export BUILD_LANGTOOLS=true
export BUILD_JAXP=false
export BUILD_JAXWS=false
export BUILD_CORBA=false
export BUILD_HOSTPOT=true
export BUILD_JDK=true
#要编译的版本
export SKIP_DEBUG_BUILD=false
export SKIP_FASTDEBUG_BUILD=true
export DEBUG_NAME=debug
#避免javaws和浏览器Java插件等的build
BUILD_DEPLOY=false
#不build安装包
BUILD_INSTALL=false
#包含全部的调试信息
export ENABLE_FULL_DEBUG_SYMBOLS=1
#调试信息是否压缩,如果配置为1,libjvm.debuginfo会被压缩成libjvm.diz,将不能被debug。
export ZIP_DEBUGINFO_FILES=0
#⽤于编译线程数
export HOTSPOT_BUILD_JOBS=3
#设置存放编译结果的⽬录
#export ALT_OUTPUTDIR=/root/jvm/output
unset CLASSPATH
unset JAVA_HOME
make sanity
DEBUG_BINARIES=true make 2>&1
然后执⾏ sh build.sh 进⾏编译。如果编译过程中遇到问题,可以查阅下⽂的编译问题解决的部分。
运⾏HotSpot
编译成功后,编译的输出默认在openjdk/build⽬录下。HotSpot的编译输出在openjdk/build/linux-amd64-debug/hotspot/outputdir/linux_amd64_compiler2/jvmg⽬录下。
java设置环境变量的方法代码使⽤HotSpot提供的命令执⾏测试:
cd build/linux-amd64-debug/hotspot/outputdir/linux_amd64_compiler2/jvmg
# test_gamma是HotSpot提供的⼀个测试程序,可以成功执⾏说明编译成功
./test_gamma
# 使⽤hotspot脚本进⾏GDB调试
./hotspot -gdb HelloWorld
./hotspot是⼀个脚本,查阅代码可以看出他做了⼀些简单的事情,主要是会设置环境变量:
JAVA_HOME=/usr/java/jdk1.6.0_38
LD_LIBRARY_PATH=/root/jvm/openjdk/build/linux-amd64-debug/hotspot/outputdir/linux_amd64_compiler2/jvmg:/usr/java/jdk1.6.0_38/jre/lib/amd64
然后⽣成GDB参数脚本,并运⾏GDB命令:
gdb -x /tmp/hsl.26037
/tmp/hsl.26037是脚本⽣成的gdb参数,我们可以看看都设置了什么:
cd /root/jvm/openjdk/build/linux-amd64-debug/hotspot/outputdir/linux_amd64_compiler2/jvmg
handle SIGUSR1 nostop noprint
handle SIGUSR2 nostop noprint
set args HelloWorld
file /root/jvm/openjdk/build/linux-amd64-debug/hotspot/outputdir/linux_amd64_compiler2/jvmg/gamma
directory /root/jvm/openjdk/build/linux-amd64-debug/hotspot/outputdir/linux_amd64_compiler2/jvmg
# Get us to a point where we can set breakpoints in libjvm.so
break InitializeJVM
run
# Stop in InitializeJVM
delete 1
# We can now set breakpoints wherever we like
可以看出设置了源码⽬录,设置了⼀个默认断点。分析了hotspot脚本后,我们可以根据需要⽤最原始的⽅式来启动hotspot和gdb来实现更复
杂的调试需求,⽐如远程调试。
直接执⾏的⽅式:
JAVA_HOME=/usr/java/jdk1.6.0_38 LD_LIBRARY_PATH="/root/jvm/openjdk/build/linux-amd64-debug/hotspot/outputdir/linux_amd64_compiler2/jvmg:/usr/java/jdk1.6.0_38/jre/lib/amd64" ./g GDB调试:
JAVA_HOME=/usr/java/jdk1.6.0_38 LD_LIBRARY_PATH="/root/jvm/openjdk/build/linux-amd64-debug/hotspot/outputdir/linux_amd64_compiler2/jvmg:/usr/java/jdk1.6.0_38/jre/lib/amd64" gd
GDB远程调试:
JAVA_HOME=/usr/java/jdk1.6.0_38 LD_LIBRARY_PATH="/root/jvm/openjdk/build/linux-amd64-debug/hotspot/outputdir/linux_amd64_compiler2/jvmg:/usr/java/jdk1.6.0_38/jre/lib/amd64" gd
Hotspot代码调试技巧
GDB的使⽤和技巧这⾥就不说了,我⾃⼰也是遇到问题现查资料的,这⾥列⼏个常⽤的和HotSpot有关的调试技巧。
如何打印HotSpot内部符号对象Symbol对应的字符串?
Symbol是⼀个⾮常常见的类,所有的符号引⽤对应的字符串,都会⽤Symbol来表⽰,⽐如类名、⽅法名、⽅法签名等等,可以⽤⼀下⽅法
输出Symbol对应字符串:
p *name._body@name._length
如何打印KlassHandle对应的类名?
p Klass::cast(current_klass.obj())->external_name()
添加加载特定类时的断点
break ClassFileParser::parseClassFile if strncmp(class_name._body, "XXX", 3) == 0
break SystemDictionary::load_instance_class if strncmp(class_name._body, "XXX", 3) == 0
遇到的问题
BootstrapJDK⼀开始设置为JDK8会失败,要改为JDK6
#错误
echo "*** This OS is not supported:" `uname -a`; exit 1;
#解决
sudo vim openjdk/hotspot/make/linux/Makefile
注释掉以下三⾏
238 #ifeq ($(DISABLE_HOTSPOT_OS_VERSION_CHECK)$(EMPTY_IF_NOT_SUPPORTED),)
239 # $(QUIETLY) >&2 echo "*** This OS is not supported:" `uname -a`; exit 1;
240 #endif
error:"__LEAF"redefined [-Werror]
#解决
ubuntu12的glibc⽐较新,在linux的头⽂件cdefs.h⾥,有个__LEAF的宏,
这个和hotspot/src/share/vm/runtime/interfaceSupport.hpp
这个头⽂件中的宏定义有冲突,我们在428⾏下⾯增加⼀个#undef __LEAF如下:
428 // 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
#错误
Error:/openjdk/hotspot/src/share/vm/oops/constantPoolOop.cpp:272:39:
error: converting 'false' to pointer type 'methodOop' [-Werror=conversion-null]
#解决
vi hotspot/src/share/vm/oops/constantPoolOop.cpp
将272⾏ return false 改为 return NULL
#错误
/usr/openjdk/hotspot/src/share/vm/opto/loopnode.cpp:896:49:
error: converting 'false' to pointer type 'Node*' [-Werror=conversion-null]
#解决
vi hotspot/src/share/vm/opto/loopnode.cpp
将896⾏ return false 改为 return NULL
#错误
Using java runtime at: /usr/lib/jvm/java-1.6.0/jre
./gamma: relocation error: /usr/lib/jvm/java-1.6.0-openjdk-1.6.0.41.x86_64/jre/lib/amd64/libjava.so: symbol JVM_FindClassFromCaller, version SUNWprivate_1.1 not defined in file libjvm.so w
#解决
这⾥是有⼀个坑的,为了避免⼤家踩坑,请提前安装好Oracle JDK 1.6。
# ALT_BOOTDIR ⽤到的是 OpenJDK 1.6.0 会有此报错, OpenJDK 的bug,需要使⽤ Oracle JDK
# 见到类似下⽅的报错了,恭喜童鞋您⼊坑了
# ./gamma: relocation error: /usr/lib/jvm/java-1.6.0-openjdk-1.6.0.41.x86_64/jre/lib/amd64/libjava.so: symbol JVM_FindClassFromCaller, version SUNWprivate_1.1 not defined in file libjvm.so 下载传送门
可能需要登陆Oracle,没有帐号的童鞋请注册⼀下。笔者下载安装的是jdk-6u38-linux-x64-rpm.bin。
如果上⼀个传送门失效,请继续传送!到此页⾯上的Java SE 6进⼊传送哦~如果这个传送也失效了(T_T),那接着传送,拉到页⾯最下⽅,到Java Archive栏,点击右侧DOWNLOAD按钮⾃⾏
# 对下载到的bin动动⼿脚(不要想多,释放⾥⾯的rpm包⽽已)
$ sh jdk-6u38-linux-x64-rpm.bin
# 查看下得到的rpm包
$ ll *.rpm
# 安装Oracle JDK
$ sudo rpm -ivh jdk-6u38-linux-amd64.rpm
# OK ⾄此已完成Oracle JDK安装
# 查安装的Oracle JDK⽬录
# 查jdk安装名称
$ rpm -qa | grep ^jdk-1.6.0
jdk-1.6.0_38-fcs.x86_64
# 根据安装名称查安装到本地的⽂件列表
$ rpm -ql jdk-1.6.0_38-fcs.x86_64
...
/usr/java/jdk1.6.0_38 # Oracle JDK HOME
...
# 以上查到的⽬录后⾯会⽤到
错误:
gcc: error: unrecognized command line option '-mimpure-text'
解决:
vi jdk/make/common/k
在70⾏remove the command "-mimpure-text" in the code:
Error: time is more than 10 years from present: 1136059200000
解决:
# 修改以下⽂件,将⽇期改为⼗年以内,JDK的Bug。
vi jdk/src/share/classes/java/util/CurrencyData.properties
# line: 108 377 439 529 555
错误:
../../../src/share/classes/sun/management/jmxremote/ConnectorBootstrap.java:661: error: no suitable constructor found for SslRMIServerSocketFactory(SSLContext,String[],String[],boolean)
解决:
vi ./jdk/src/share/classes/sun/management/jmxremote/ConnectorBootstrap.java
注释掉662⾏的参数
资料
《深⼊理解Java虚拟机》
openjdk7之编译和debug | YDDMAX
centos7编译openjdk7常见问题 - clover灬 - OSCHINA
How to build and package OpenJDK 7 on Linux · hgomez/obuildfactory Wiki
ubuntu16.04编译JDK7 - 简书
Building OpenJDK7 with CentOS7 – Rtfsc8
CentOS上编译OpenJDK8源码以及在eclipse上调试HotSpot虚拟机源码 - tjiyu的博客 - CSDN博客
本⽂独⽴博客地址:
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论