要分析JVM的源码,结合资料直接阅读是一种方式,但是遇到一些想不通的场景,必须要结合调试,查看执行路径以及参数具体的值,才能搞得明白。所以我们先来把JVM的源码进行编译,并能够使用GDB进行调试。

编译环境

本文使用的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:c284c89a104f64a95afde3a96138ef0f

其他环境说明:

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

$ 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目录下。

使用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" ./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行的参数

资料

本文独立博客地址:JVM源码分析-JVM源码编译与调试 | 木杉的博客

JVM源码分析-JVM源码编译与调试的更多相关文章

  1. 大数据学习--day13(字符串String--源码分析--JVM内存分析)

    字符串String--源码分析--JVM内存分析 String 类的对象 , 是不可变的字符串对象呢 这个不可变很重要,之后要讲的intern()也离不开它的不可变性. https://www.cnb ...

  2. [源码分析] 从源码入手看 Flink Watermark 之传播过程

    [源码分析] 从源码入手看 Flink Watermark 之传播过程 0x00 摘要 本文将通过源码分析,带领大家熟悉Flink Watermark 之传播过程,顺便也可以对Flink整体逻辑有一个 ...

  3. k8s client-go源码分析 informer源码分析(2)-初始化与启动分析

    k8s client-go源码分析 informer源码分析(2)-初始化与启动分析 前面一篇文章对k8s informer做了概要分析,本篇文章将对informer的初始化与启动进行分析. info ...

  4. k8s client-go源码分析 informer源码分析(3)-Reflector源码分析

    k8s client-go源码分析 informer源码分析(3)-Reflector源码分析 1.Reflector概述 Reflector从kube-apiserver中list&watc ...

  5. k8s源码分析准备工作 - 源码准备

    本文原始地址:https://farmer-hutao.github.io/k8s-source-code-analysis/ 项目github地址:https://github.com/farmer ...

  6. JAVA源码分析-HashMap源码分析(二)

    本文继续分析HashMap的源码.本文的重点是resize()方法和HashMap中其他的一些方法,希望各位提出宝贵的意见. 话不多说,咱们上源码. final Node<K,V>[] r ...

  7. Android源码分析--CircleImageView 源码详解

    源码地址为 https://github.com/hdodenhof/CircleImageView 实际上就是一个圆形的imageview 的自定义控件.代码写的很优雅,实现效果也很好, 特此分析. ...

  8. 《k8s-1.13版本源码分析》-源码调试

    源码分析系列文章已经开源到github,地址如下: github:https://github.com/farmer-hutao/k8s-source-code-analysis gitbook:ht ...

  9. CBV源码分析+APIVIew源码分析

    {drf,resful,apiview,序列化组件,视图组件,认证组件,权限组件,频率组件,解析器,分页器,响应器,URL控制器,版本控制} 一.CBV源码分析准备工作: 新建一个Django项目 写 ...

随机推荐

  1. Python3 pip换源

    pip安装源 介绍 """ 1.采用国内源,加速下载模块的速度 2.常用pip源: -- 豆瓣:https://pypi.douban.com/simple -- 阿里: ...

  2. 机器学习回顾篇(14):主成分分析法(PCA)

    .caret, .dropup > .btn > .caret { border-top-color: #000 !important; } .label { border: 1px so ...

  3. 结合docker发布前端项目(基于npm包管理)的shell脚本

    结合docker发布前端项目(基于npm包管理)的shell脚本 本教程依据个人理解并经过实际验证为正确,特此记录下来,权当笔记. 注:基于linux操作系统 目前主流的前后端分离的项目中,常常在部署 ...

  4. 用户svn密码自定义

    由于在linux系统Apache+svn服务器,用户需要自定义密码怎么办呢? 1.创建脚本目录 mkdir -p /var/www/svn/svntools 2.创建apache配置文件 touch ...

  5. 简单聊一聊JS中的循环引用及问题

    本文主要从 JS 中为什么会出现循环引用,垃圾回收策略中引用计数为什么有很大的问题,以及循环引用时的对象在使用 JSON.stringify 时为什么会报错,怎样解决这个问题简单谈谈自己的一些理解. ...

  6. 爆破linux密码 $6$3uwqC9JI$d9iPRmTDAoXs/IbsplxS3iyeErHqw7fUycacXNHyZk1UCSwFEydl515/zXN7OEwHnyUaqYcNG

    #!/usr/bin/env python # -*- coding:UTF-8 -*- import crypt import sys # 哈希密码的前两位就是盐的前两位,这里我们假设盐只有两位. ...

  7. 2018铁人三项测评题 IOS99

    下面这一部分是我从网上复制过来的, 2.IOS 解题链接:http://ctf4.shiyanbar.com/web/IOS/index.php 这题页面中提示系统升级到了IOS99,我们可以想到修改 ...

  8. 在Winform界面中使用DevExpress的TreeList实现节点过滤查询的两种方式

    在我较早的一篇随笔<在DevExpress程序中使用TeeList控件以及节点查询的处理>中,介绍了在树形列表TreeList控件上面,利用SearchControl实现节点的模糊查询过滤 ...

  9. 理解TCP/IP协议栈之HTTP2.0

    1 前言 前面写了10多篇关于Redis底层实现.工程架构.实际应用的文章,感兴趣的读者可以进行阅读,如有问题欢迎交流: 1.Redis面试热点之底层实现篇-12.Redis面试热点之底层实现篇-23 ...

  10. Python学习中的“按位取反”笔记总结

    | 疑惑 最近在学习Python的过程中了解到位运算符,但对于按位取反有点迷糊,就比如说~9(按位取反)之后的结果是-10,为什么不是6呢?所以下面就来看看为什么不是6,正确结果是如何计算出来的呢? ...