背景

大家知道,jdk安装的目录下,一般会有个src.zip包,这个包基本对应了rt.jar这个包。rt.jar这个包里面,就放了jdk中,jdk采用java实现的那部分类库代码,比如java.lang包下面的,什么ArrayList之类的。

如何才能调试这部分代码呢,这里的调试,是说,能够修改源代码、加注释、直接debug。

步骤

经过一番思考和探索后,可以这样:

  1. 解压src.zip包,因为解压后,里面有8000多个文件,比较大,我们也不需要调试所有的代码,我就挑了这个包下面的代码:

    上面看到,类比较多,我们不需要那么多,只用下面这部分:

  2. 新建一个普通的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)的更多相关文章

  1. maven下载的jar包可以查看源码

    1:Maven命令下载源码和javadocs 当在IDE中使用Maven时如果想要看引用的jar包中类的源码和javadoc需要通过maven命令下载这些源码,然后再进行引入,通过mvn命令能够容易的 ...

  2. 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 ...

  3. JDK中的BitMap实现之BitSet源码分析

    前提 本文主要内容是分析JDK中的BitMap实现之java.util.BitSet的源码实现,基于JDK11编写,其他版本的JDK不一定合适. 文中的图比特低位实际应该是在右边,但是为了提高阅读体验 ...

  4. maven中下载jar包源码和javadoc

    1:Maven命令下载源码和javadocs 当在IDE中使用Maven时如果想要看引用的jar包中类的源码和javadoc需要通过maven命令下载这些源码,然后再进行引入,通过mvn命令能够容易的 ...

  5. 【原】解决Debug JDK source 无法查看局部变量的问题方案(重新编译rt.jar包)

    一.问题阐述 首先我们要明白JDK source为什么在debug的时候无法观察局部变量,因为在jdk中,sun对rt.jar中的类编译时,去除了调试信息,这样在eclipse中就不能看到局部变量的值 ...

  6. rt.jar包添加源文件只需要关联到已安装对应jdk目录下source.zip源码文件即可

    项目中配置的JRE System Libriry下的rt.jar包,需要关联源文件时候,只需要点击“Attach Source...“按钮,选择"External File..." ...

  7. SSH中的jar包讲解(1)

    我们在搭建SSH框架的时候,需要引入各自的一些jar包,相信很多初学者跟我一样,搜个资料,照搬过来(当然版本还得对应),至于为什么要引入这些个jar包,引入它们的作用是啥子,一头雾水,今天我就来跟这些 ...

  8. 如何制作Jar包并在android中调用jar包

    android制作jar包: 新建android工程,然后右击,点击导出,选择导出类型为Java下的JAR file,在java file specification 中不要选择androidmani ...

  9. SSH中的jar包讲解

    我们在搭建SSH框架的时候,需要引入各自的一些jar包 首先,先来看一下我们使用的SSH的各自版本及引入的jar包.   struts2.3.1.2: struts2-core-2.3.1.jar j ...

随机推荐

  1. 人生靠反省,Java靠泛型

    昨天有同事问 UserService.XxxService 都会调用 Dao 的 insert.update ... ...,这些重复的代码,有没有办法变得灵活一些? 巧了,和咱们分享的主题刚好碰上, ...

  2. ajax使用POST提交报错400

    并非BadRequest!! 在用ajax访问登录接口的时候出现了这个错误,查阅得到使用Ajax的Post需要添加 contentType: "application/x-www-form- ...

  3. Java 连接数据库总是报错

    mysql账号密码是正确的,但是一直报账号密码错误. 报错信息: java.sql.SQLException: Access denied for user 'root'@'localhost' (u ...

  4. JS中this指向问题和改变this指向

    首先必须要说的是,this的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定this到底指向谁,实际上this的最终指向的是那个调用它的对象(这句话有些问题,后面会解释为什么会有问题,虽然 ...

  5. stylus--安装及使用方法

    stylus介绍 Stylus 是一个CSS的预处理框架,2010年产生,来自Node.js社区,主要用来给Node项目进行CSS预处理支持,所以 Stylus 是一种新型语言,可以创建健壮的.动态的 ...

  6. MyBatis(六):SqlSession执行源码分析

    SqlSession执行源码分析 针对以下代码 public class MybatisUtils { private static SqlSessionFactory sqlSessionFacto ...

  7. Flask 入门(五)

    jinjia2模板传参 在html中调用python代码中传入的参数规则己经在上文中说明白了,下面,我们来实用一下: 1.编辑index.py中的代码如下: from flask import Fla ...

  8. 算法竞赛 从c到c++3

    const 常指针,指向固定位置,不能再次修改指向的位置,需要初始化,const 加在“*”号后面,名称前面,例如 int *const p: 指向常量的指针,不能修改指向地址的内容,相当于常引用,c ...

  9. Thinking in Java,Fourth Edition(Java 编程思想,第四版)学习笔记(十二)之Error Handling with Exceptions

    The ideal time to catch an error is at compile time, before you even try to run the program. However ...

  10. win10安装docker,VSCode管理docker

    背景 docker:随着技术的不断迭代,开发环境的配置与部署越来越重要.Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的 Linu ...