• 曹工力荐:调试 jdk 中 rt.jar 包部分的源码(可自由增加注释,修改代码并debug)


    背景

    大家知道,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 ocketmq-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-ckljdk-debug	argetclasses"
    
    

    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 FilesJavajdk1.8.0_11jrelib
    esources.jar;
    C:Program FilesJavajdk1.8.0_11jrelib
    t.jar;
    C:Program FilesJavajdk1.8.0_11jrelibsunrsasign.jar;
    C:Program FilesJavajdk1.8.0_11jrelibjsse.jar;
    C:Program FilesJavajdk1.8.0_11jrelibjce.jar;
    C:Program FilesJavajdk1.8.0_11jrelibcharsets.jar;
    C:Program FilesJavajdk1.8.0_11jrelibjfr.jar;
    C:Program FilesJavajdk1.8.0_11jreclasses
    

    这个参数啥意思,差不多就是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-ckljdk-debug	argetclasses;C:Program FilesJavajdk1.8.0_11jrelib
    esources.jar;C:Program FilesJavajdk1.8.0_11jrelib
    t.jar;C:Program FilesJavajdk1.8.0_11jrelibsunrsasign.jar;C:Program FilesJavajdk1.8.0_11jrelibjsse.jar;C:Program FilesJavajdk1.8.0_11jrelibjce.jar;C:Program FilesJavajdk1.8.0_11jrelibcharsets.jar;C:Program FilesJavajdk1.8.0_11jrelibjfr.jar;C:Program FilesJavajdk1.8.0_11jreclasses"
    

    最好直接用下面这个代码来拼好路径,免得人工出错:

    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去做修改。

    总结

    希望大家调试愉快。谢谢大家。有帮助的话,点个推荐。

  • 相关阅读:
    乱七八杂
    转化跟踪设置说明
    Photoshop图象切片保存为网页HTML(DIV+CSS布局)的方法
    HTTP 错误 403.14
    打开asp出现An error occurred on the server when processing the URL
    ADODB.Connection 错误 '800a0e7a'
    修改客户端连接的服务器IP地址(内部使用)
    关于云计算的10个常见问题解答
    云安全的11个挑战及应对策略
    如何鉴别一个区块链项目的真假?
  • 原文地址:https://www.cnblogs.com/grey-wolf/p/12817615.html
Copyright © 2020-2023  润新知