曹工力荐:调试 jdk 中 rt.jar 包部分的源码(可自由增加注释,修改代码并debug)
背景
大家知道,jdk安装的目录下,一般会有个src.zip包,这个包基本对应了rt.jar这个包。rt.jar这个包里面,就放了jdk中,jdk采用java实现的那部分类库代码,比如java.lang包下面的,什么ArrayList之类的。
如何才能调试这部分代码呢,这里的调试,是说,能够修改源代码、加注释、直接debug。
步骤
经过一番思考和探索后,可以这样:
解压src.zip包,因为解压后,里面有8000多个文件,比较大,我们也不需要调试所有的代码,我就挑了这个包下面的代码:
上面看到,类比较多,我们不需要那么多,只用下面这部分:
新建一个普通的maven工程,然后把上面的java包下面的,拷贝到自己的工程的src目录下
因为awt、applet之类的,现在都没人用了,我也就没拷贝那部分。
pom.xml真的没东西,不过还是贴一下:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <groupId>org.learnjdk</groupId> <modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>
<version>4.7.0</version> <artifactId>jdk-debug</artifactId>
<name>jdk-debug</name> <properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties> <dependencies> </dependencies>
</project>
最后,工程大概就是这样的。
然后,自己在test文件夹下,我建了一个HelloWorld:
import java.util.ArrayList; public class HelloWorld {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("abc");
}
}
理论上来说,就可以调试了吗?naive!
F:\gitee-ckl\rocketmq-all-4.7.0-source>java -version
java version "1.8.0_11"
Java(TM) SE Runtime Environment (build 1.8.0_11-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.11-b03, mixed mode)我这边是jdk的版本,会有个报错,是源码不完全匹配class导致的,我这边会报找不到java.lang.Iterable#iterator方法,大家直接反编译一下jdk的这个class,就能看到缺了啥了,我这边加上这个方法就好了:
public interface Iterable<T> {
/**
* Returns an iterator over elements of type {@code T}.
*
* @return an Iterator.
*/
public abstract Iterator<T> iterator(); ...
}
测试demo有什么问题
大家运行就知道了,根本走不到我们工程里定义的class
其实整体来说,那个maven工程是没问题的。走不到那个class,是因为,classloader的问题。
在运行上面的helloWorld时,当前classloader是sun.misc.Launcher.AppClassLoader,它的父类是
sun.misc.Launcher.ExtClassLoader,而sun.misc.Launcher.ExtClassLoader的父类,就是BootStrap类加载器了。
因为AppClassLoader是遵循双亲委派的,所以,在运行下面这个代码的时候:
import java.util.ArrayList;
public class HelloWorld {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("abc");
}
}
看到ArrayList,AppClassLoader会交给ExtClassLoader去加载,ExtClassLoader会交给BootStrapClassloader去加载,BootStrapClassloader本身负责加载jdk下的rt.jar等核心jar包,而Arraylist正好就是在jdk下面的rt.jar中,所以,最终,Arraylist是由BootStrapClassloader加载的。
那就和我们的工程里的代码没关系了,根本不加载你的。
怎么让BootStrap优先加载我们的类
核心其实就是变成了,让BootStrapClassLoader优先加载我们的类,在我的知识理解里,BootStrapClassLoader默认就是加载rt.jar的东西,怎么才能加载我们的呢?只能求助互联网了。
然后我查到了这篇文章,-Xbootclasspath
里面说,用这个参数可以改变BootStrapClassLoader的加载路径,于是我试了一下:
-Xbootclasspath/p:"F:\gitee-ckl\jdk-debug\target\classes"
idea里,就加在这里面:
然后,大家直接run的话,可以发现,已经没问题了。
因为我改了工程里的源码的:
public boolean add(E e) {
System.out.println("xxxxx");
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
所以我这边运行的时候,会打印xxxxx:
但是,如果你debug,行号应该是对不上的,所以,我们还要这么操作一波:
idea里,在左侧的项目树立,对着module按F4,或者右键-》Open Module Settings,会打开如下窗口:
然后debug,就可以了:
然后,这个方案,本来昨晚尝试的时候,是有问题的,不知道今天为啥就可以了,大家也可以试试。
另一种可行的方案
因为昨晚尝试上面方案的时候,不知道为啥,没生效;于是找出了下面的方法。
在helloWorld.java里,修改如下:
public class HelloWorld {
public static void main(String[] args) {
System.out.print(System.getProperty("sun.boot.class.path"));
ArrayList<String> list = new ArrayList<>();
list.add("abc");
}
}
我们打印了sun.boot.class.path的值,我这边打印出来后如下:
C:\Program Files\Java\jdk1.8.0_11\jre\lib\resources.jar;
C:\Program Files\Java\jdk1.8.0_11\jre\lib\rt.jar;
C:\Program Files\Java\jdk1.8.0_11\jre\lib\sunrsasign.jar;
C:\Program Files\Java\jdk1.8.0_11\jre\lib\jsse.jar;
C:\Program Files\Java\jdk1.8.0_11\jre\lib\jce.jar;
C:\Program Files\Java\jdk1.8.0_11\jre\lib\charsets.jar;
C:\Program Files\Java\jdk1.8.0_11\jre\lib\jfr.jar;
C:\Program Files\Java\jdk1.8.0_11\jre\classes
这个参数啥意思,差不多就是BootStrap加载class时候,要去查找的路径。大家也可以参考这两篇文章:
https://www.cnblogs.com/ahudyan-forever/p/6007458.html
https://blog.csdn.net/briblue/article/details/54973413
所以,我的最终方案就是,把我们的class路径,放到最前面,大家根据自己的路径进行修改就行。
大家要注意的是,这里的路径,要仔细,粘错一个字符都不行:
-Dsun.boot.class.path="F:\gitee-ckl\jdk-debug\target\classes;C:\Program Files\Java\jdk1.8.0_11\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_11\jre\lib\rt.jar;C:\Program Files\Java\jdk1.8.0_11\jre\lib\sunrsasign.jar;C:\Program Files\Java\jdk1.8.0_11\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_11\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_11\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_11\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_11\jre\classes"
最好直接用下面这个代码来拼好路径,免得人工出错:
public class HelloWorld {
public static void main(String[] args) {
String property = System.getProperty("sun.boot.class.path");
System.setProperty("sun.boot.class.path","F:\\gitee-ckl\\jdk-debug\\target\\classes;" + property);
System.out.println(System.getProperty("sun.boot.class.path"));
}
}
运行方式和前面一样:
该方案为什么可行
稍微拓展一点,因为我也就知道这么一点,在sun.misc.Launcher类中:
public class Launcher {
private static URLStreamHandlerFactory factory = new Launcher.Factory();
private static Launcher launcher = new Launcher();
// 1
private static String bootClassPath = System.getProperty("sun.boot.class.path");
1处,这里定义了一个field,就是去获取我们前面用到的那个属性。
这个类里,有另一个方法来解析这个field。
sun.misc.Launcher.BootClassPathHolder
private static class BootClassPathHolder
{
// 0
static final URLClassPath bcp = new URLClassPath(arrayOfURL, Launcher.factory);
static
{
URL[] arrayOfURL;
if (Launcher.bootClassPath != null) {
arrayOfURL = (URL[])AccessController.doPrivileged(new PrivilegedAction()
{
public URL[] run() {
// 1
File[] arrayOfFile = Launcher.getClassPath(Launcher.bootClassPath);
int i = arrayOfFile.length;
HashSet localHashSet = new HashSet();
for (int j = 0; j < i; j++) {
// 2
File localFile = arrayOfFile[j];
if (!localFile.isDirectory()) {
localFile = localFile.getParentFile();
}
if ((localFile != null) && (localHashSet.add(localFile))) {
MetaIndex.registerDirectory(localFile);
}
}
// 3
return Launcher.pathToURLs(arrayOfFile);
}
});
}
else
arrayOfURL = new URL[0];
}
}
- 1处,把那个属性,用分隔符分开,解析为一个文件数组
- 2处,遍历数组
- 3处,解析为URL,赋值给arrayOfURL。因为这里是一个匿名内部类,所以第三步的return,只是return了匿名内部类中的方法
- 0处,使用arrayOfURL,定义了一个static变量
然后在另一个方法中,会去获取那个bcp:
sun.misc.Launcher#getBootstrapClassPath
public static URLClassPath getBootstrapClassPath() {
return Launcher.BootClassPathHolder.bcp;
}
上面这个方法在哪被调用?
java.lang.ClassLoader#getBootstrapClassPath
// Returns the URLClassPath that is used for finding system resources.
static URLClassPath getBootstrapClassPath() {
return sun.misc.Launcher.getBootstrapClassPath();
}
被同属于ClassLoader类的下列方法调用:
java.lang.ClassLoader#getBootstrapResource
/**
* Find resources from the VM's built-in classloader.
*/
private static URL getBootstrapResource(String name) {
URLClassPath ucp = getBootstrapClassPath();
Resource res = ucp.getResource(name);
return res != null ? res.getURL() : null;
}
再往上找,就是:
java.lang.ClassLoader#getResource
public URL getResource(String name) {
URL url;
if (parent != null) {
url = parent.getResource(name);
} else {
url = getBootstrapResource(name);
}
if (url == null) {
url = findResource(name);
}
return url;
}
其他就不多分析,大家也比较熟悉了。
两种方案的差异
第一种方案,加了-Xbootclasspath,这个是在jvm层面去做修改,因为这个-X是虚拟机参数;
第二种方案,上面大家也看到了,是在rt.jar中,java层面的classloader去做修改。
总结
希望大家调试愉快。谢谢大家。有帮助的话,点个推荐。
曹工力荐:调试 jdk 中 rt.jar 包部分的源码(可自由增加注释,修改代码并debug)的更多相关文章
- maven下载的jar包可以查看源码
1:Maven命令下载源码和javadocs 当在IDE中使用Maven时如果想要看引用的jar包中类的源码和javadoc需要通过maven命令下载这些源码,然后再进行引入,通过mvn命令能够容易的 ...
- JDK中rt.jar、tools.jar和dt.jar作用
dt.jar和tools.jar位于:{Java_Home}/lib/下,而rt.jar位于:{Java_Home}/jre/lib/下,其中: rt.jar是JAVA基础类库,也就是你在java d ...
- JDK中的BitMap实现之BitSet源码分析
前提 本文主要内容是分析JDK中的BitMap实现之java.util.BitSet的源码实现,基于JDK11编写,其他版本的JDK不一定合适. 文中的图比特低位实际应该是在右边,但是为了提高阅读体验 ...
- maven中下载jar包源码和javadoc
1:Maven命令下载源码和javadocs 当在IDE中使用Maven时如果想要看引用的jar包中类的源码和javadoc需要通过maven命令下载这些源码,然后再进行引入,通过mvn命令能够容易的 ...
- 【原】解决Debug JDK source 无法查看局部变量的问题方案(重新编译rt.jar包)
一.问题阐述 首先我们要明白JDK source为什么在debug的时候无法观察局部变量,因为在jdk中,sun对rt.jar中的类编译时,去除了调试信息,这样在eclipse中就不能看到局部变量的值 ...
- rt.jar包添加源文件只需要关联到已安装对应jdk目录下source.zip源码文件即可
项目中配置的JRE System Libriry下的rt.jar包,需要关联源文件时候,只需要点击“Attach Source...“按钮,选择"External File..." ...
- SSH中的jar包讲解(1)
我们在搭建SSH框架的时候,需要引入各自的一些jar包,相信很多初学者跟我一样,搜个资料,照搬过来(当然版本还得对应),至于为什么要引入这些个jar包,引入它们的作用是啥子,一头雾水,今天我就来跟这些 ...
- 如何制作Jar包并在android中调用jar包
android制作jar包: 新建android工程,然后右击,点击导出,选择导出类型为Java下的JAR file,在java file specification 中不要选择androidmani ...
- SSH中的jar包讲解
我们在搭建SSH框架的时候,需要引入各自的一些jar包 首先,先来看一下我们使用的SSH的各自版本及引入的jar包. struts2.3.1.2: struts2-core-2.3.1.jar j ...
随机推荐
- 并发——抽象队列同步器AQS的实现原理
一.前言 这段时间在研究Java并发相关的内容,一段时间下来算是小有收获了.ReentrantLock是Java并发中的重要部分,所以也是我的首要研究对象,在学习它的过程中,我发现它是基于抽象队列 ...
- IO 模型知多少
1. 引言 同步异步I/O,阻塞非阻塞I/O是程序员老生常谈的话题了,也是自己一直以来懵懵懂懂的一个话题.比如:何为同步异步?何为阻塞与非阻塞?二者的区别在哪里?阻塞在何处?为什么会有多种IO模型,分 ...
- python通俗讲解闭包
通俗理解闭包 先来看看什么是闭包吧 闭包是引用了自由变量的函数.这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外.所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合 ...
- 从JDK源码学习HashSet和HashTable
HashSet Java中的集合(Collection)有三类,一类是List,一类是Queue,再有一类就是Set. 前两个集合内的元素是有序的,元素可以重复:最后一个集合内的元素无序,但元素不可重 ...
- Python库-Matplotlib
Matplotlib官网https://matplotlib.org,Matplotlib是一个Python的2D绘图库. 可视化是整个数据分析的关键辅助工具,可以清晰的理解数据. 折线图(用于显示数 ...
- Elasticsearch系列---聚合查询原理
概要 本篇主要介绍聚合查询的内部原理,正排索引是如何建立的和优化的,fielddata的使用,最后简单介绍了聚合分析时如何选用深度优先和广度优先. 正排索引 聚合查询的内部原理是什么,Elastich ...
- 好消息,vue3.0 进入 beta 阶段!
昨天,4 月 16 日,vue 3 正式进入 beta 阶段.同日,尤大参加了 State of Vue 的线上活动,以下是他上传到 google docs 上的 slides : State of ...
- Xcode 6.3.1Mac版 V6.4.Beta3免费下载
Xcode for mac是Mac OS系统以及IOS系统开发者专用于构建 Mac OS X 及 iOS 应用程序的完整工具集 - Xcode 5 的工具经过重新设计,它们的性能更优秀.使用更容易,能 ...
- AJ学IOS(40)UI之核心动画_抖动效果_CAKeyframeAnimation
AJ分享,必须精品 效果: 效果一: 效果二: 代码: // // NYViewController.m // 图片抖动 // // Created by apple on 15-5-8. // Co ...
- 软件包管理rpm和yum
rpm的使用: 安装的包相关包信息会保存在/var/lib/rpm目录下的文件中 安装参数: -i install安装 -v 显示详细信息 -h 打印####号 -V 校验软件包,会到/var/lib ...