JVM源碼分析-JVM源碼編譯與調試

要分析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

  • 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 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

運行HotSpot

編譯成功後,編譯的輸出默認在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

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 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行的參數

資料

  • 《深刻理解Java虛擬機》
  • openjdk7之編譯和debug | YDDMAX https://yddmax.github.io/2017/06/11/openjdk7%E4%B9%8B%E7%BC%96%E8%AF%91%E5%92%8Cdebug/
  • centos7編譯openjdk7常見問題 - clover灬 - OSCHINA https://my.oschina.net/zhangdq/blog/2250314
  • How to build and package OpenJDK 7 on Linux · hgomez/obuildfactory Wiki https://github.com/hgomez/obuildfactory/wiki/How-to-build-and-package-OpenJDK-7-on-Linux
  • ubuntu16.04編譯JDK7 - 簡書 https://www.jianshu.com/p/32dc1a850e23
  • Building OpenJDK7 with CentOS7 – Rtfsc8 http://blog.rtfsc8.top/2018/07/07/building-openjdk7-with-centos7/
  • CentOS上編譯OpenJDK8源碼 以及 在eclipse上調試HotSpot虛擬機源碼 - tjiyu的博客 - CSDN博客

本文獨立博客地址:JVM源碼分析-JVM源碼編譯與調試 | 木杉的博客

相關文章
相關標籤/搜索