要分析JVM的源码,结合资料直接阅读是一种方式,可是遇到一些想不通的场景,必需要结合调试,查看执行路径以及参数具体的值,才能搞得明白。因此咱们先来把JVM的源码进行编译,并可以使用GDB进行调试。html
本文使用的JDK版本:OpenJDK7,分支b147
下载页面:https://download.java.net/openjdk/jdk7
下载地址:http://download.java.net/openjdk/jdk7/promoted/b147/openjdk-7-fcs-src-b147-27_jun_2011.zip
MD5:c284c89a104f64a95afde3a96138ef0fjava
其余环境说明:node
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 http://repos.fedorapeople.org/repos/dchen/apache-maven/epel-apache-maven.repo -O /etc/yum.repos.d/epel-apache-maven.repo yum -y install ant
编译JVM,须要使用到更早以前一个版本的JDK,好比咱们编译的是7,就须要安装OracleJDK6:
下载地址:https://www.oracle.com/java/technologies/javase-java-archive-javase6-downloads.html
http://gcdncs.101.com/v0.1/static/test_mzb/jdk-6u38-linux-x64-rpm.bin
下载:jdk-6u38-linux-x64-rpm.binlinux
$ sh jdk-6u38-linux-x64-rpm.bin $ sudo rpm -ivh jdk-6u38-linux-amd64.rpm
解压openjdk:c++
unzip openjdk-7-fcs-src-b147-27_jun_2011.zip
在openjdk目录下添加一个build.sh脚本:git
#!/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 进行编译。若是编译过程当中遇到问题,能够查阅下文的编译问题解决的部分。github
编译成功后,编译的输出默认在openjdk/build
目录下。HotSpot的编译输出在openjdk/build/linux-amd64-debug/hotspot/outputdir/linux_amd64_compiler2/jvmg
目录下。shell
使用HotSpot提供的命令执行测试:apache
cd build/linux-amd64-debug/hotspot/outputdir/linux_amd64_compiler2/jvmg # test_gamma是HotSpot提供的一个测试程序,能够成功执行说明编译成功 ./test_gamma # 使用hotspot脚本进行GDB调试 ./hotspot -gdb HelloWorld
./hotspot
是一个脚本,查阅代码能够看出他作了一些简单的事情,主要是会设置环境变量:ubuntu
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" ./gamma HelloWorld
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" gdb ./gamma HelloWorld
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" gdbserver :8011 ./gamma HelloWorld
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 with link time reference #解决 这里是有一个坑的,为了不你们踩坑,请提早安装好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 with link time reference 下载传送门可能须要登录Oracle,没有账号的童鞋请注册一下。笔者下载安装的是jdk-6u38-linux-x64-rpm.bin。 若是上一个传送门失效,请继续传送!找到此页面上的Java SE 6进入传送哦~若是这个传送也失效了(T_T),那接着传送,拉到页面最下方,找到Java Archive栏,点击右侧DOWNLOAD按钮自行传送。再不行就只能找baidu了~~~ # 对下载到的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/shared/Compiler-gcc.gmk 在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行的参数
本文独立博客地址:JVM源码分析-JVM源码编译与调试 | 木杉的博客