【jvm jdk】类加载器1 sun.misc.Launcher程序入口类 & 构建ExtClassLoader,ExtClassLoader

通过这篇文章,我们能获得如下知识:
1.sun.misc.Launcher类初始化过程
2.Launcher类中定义了ExtClassLoader,ExtClassLoader这两个类加载器,并创建他们的实例
3.解释了ExtClassLoader加载<JAVA_HOME>/lib/ext下的类,ExtClassLoader加载classpath下的类的原因

1. 简介

sun.misc.Launcher类是java的入口,在启动java应用的时候会首先创建Launcher类,创建Launcher类的时候会准备应用程序运行中需要的类加载器,位于jre/lib/rt.jar中。

2. Launcher是由BootStrapClassLoader加载的

Launcher作为JAVA应用的入口,根据双亲委派模型,Laucher是由JVM创建的,它的类加载器是BootStrapClassLoader, 这是一个C++编写的类加载器,是java应用体系中最顶层的类加载器,负责加载JVM需要的一些类库(<JAVA_HOME>/lib),而Launcher类在rt.jar中,正好处于该加载器的加载路径下。可以通过一个简单的代码验证一下我们的想法。

package my.test;

import sun.misc.Launcher;

public class Test {

    public static void main(String[] args) {
        ClassLoader launcherClassLoader = Launcher.class.getClassLoader();
        System.out.println(launcherClassLoader);   //输出null

        ClassLoader testClassLoader = Test.class.getClassLoader();
        System.out.println(testClassLoader); // 输出 sun.misc.Launcher$AppClassLoader@19821f
    }
}

这里的classLoadernull,说明Launcher确实是BootstrapClassLoader加载的。

为何classLoadernull,说明Launcher确实是BootstrapClassLoader加载的?因为java.lang.Class类的getClassLoader()方法用于获取此实体的classLoader,该实体可以是类,数组,接口等。我们知道类加载器类型包括四种,分别是启动类加载器(Bootstrap ClassLoader)扩展类加载器(Extension ClassLoader)应用程序类加载器(Application ClassLoader)用户自定义加载器,如果是后3种类型,一般会打印出具体的信息,而前面代码中的打印2条结果,第二条sun.misc.Launcher$AppClassLoader@19821f说明应用了程序类加载器,含有关键词‘AppClassLoader’,与此类似,如果是Ext或自定义类型,也有相关的关键词,而第一条为null,不是后3种的类型之一,据此反推第一条是Bootstrap ClassLoader。

其实也不需要反推,根本原因在于java.lang.Class类的getClassLoader()方法实现机制,我们来看下该方法的源码和注释:

//JDK 1.8
public final class Class<T>  {
       /**
     * Returns the class loader for the class.  Some implementations may use
     * null to represent the bootstrap class loader. This method will return
     * null in such implementations if this class was loaded by the bootstrap
     * class loader.
     ...
     
     */
    @CallerSensitive
    public ClassLoader getClassLoader() {
        ClassLoader cl = getClassLoader0();
        if (cl == null)
            return null;
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            ClassLoader.checkClassLoaderPermission(cl, Reflection.getCallerClass());
        }
        return cl;
    }                           

Some implementations may use null to represent the bootstrap class loader

该方法的注释明确说明当返回值为null时,表示使用的是bootstrap class loader。

3. Launcher的初始化过程

Launcher的源码:

//JDK 1.8 

public class Launcher {
    private static Launcher launcher = new Launcher(); //类被加载时,触发创建静态实例对象,进而触发构造函数
    
	public Launcher() {    //构造函数
	  ExtClassLoader extClassLoader;
	  try {
	    extClassLoader = ExtClassLoader.getExtClassLoader();   // 获取ExtClassLoader
	  } catch (IOException iOException) {
	    throw new InternalError("Could not create extension class loader");
	  } 
	  try {
	    this.loader = AppClassLoader.getAppClassLoader(extClassLoader);  //以ExtClassLoader 作为父类加载器创建一个AppClassLoader
	  } catch (IOException iOException) {
	    throw new InternalError("Could not create application class loader");
	  } 
	  Thread.currentThread().setContextClassLoader(this.loader);   //设置默认加载器
	  ....
   }
}

首先我们知道Launcher类是应用的入口,位于rt.jar包中,默认会被BootstrapClassLoader加载,被加载时,触发创建静态实例对象,进而触发构造函数,在构造函数内,完成ExtClassLoader及AppClassLoader的创建。

我们来分析上面的代码:

  • Launcher在创建的时候,第一件事情就是获取ExtClassLoader, ExtClassLoader在JVM中是一个单例, 创建过程也是通过获取环境变量来获取ext加载的目录,生成一个ExtClassLoader,ExtClassLoader是URLClassLoader的子类。
 public static Launcher.ExtClassLoader getExtClassLoader() throws IOException {
       if (instance == null) {
           Class clazz = Launcher.ExtClassLoader.class;
           synchronized(Launcher.ExtClassLoader.class) {
               if (instance == null) {
                   instance = createExtClassLoader();   //单例模式
               }
           }
       }
       return instance;
   }

  • 在获取ExtClassLoader之后,以此作为父类加载器创建一个 sun.misc.Launcher.AppClassLoader, AppClassLoader的加载路径是java.class.path标记的路径,相同的,AppClassLoader也是URLClassLoader的子类。
  • 最终会将当前线程的上下文类加载器设置为AppClassLoader

通过上述分析,当我们需要获取当前应用程序的AppClassLoader或者ExtClassLoader的时候,可以直接使用Launcher来访问。

	  public static void main(String[] args) {
	     ClassLoader appClassLoader = Launcher.getLauncher().getClassLoader();
	     System.out.println(appClassLoader);  //打印AppClassLoader
	
	     ClassLoader extClassLoader = appClassLoader.getParent();  //通过父属性得到ext loader
	     System.out.println(extClassLoader); //打印ExtClassLoader

执行结果:

   sun.misc.Launcher$AppClassLoader@19821f
   sun.misc.Launcher$ExtClassLoader@addbf1

4. 类加载器路径

jvm提供了三种系统加载器:

  • 启动类加载器(Bootstrap ClassLoader):C++实现,在java里无法获取,负责加载<JAVA_HOME>/lib下的类。
  • 扩展类加载器(Extension ClassLoader): Java实现,可以在java里获取,负责加载<JAVA_HOME>/lib/ext下的类。
  • 系统类加载器/应用程序类加载器(Application ClassLoader):是与我们接触对多的类加载器,我们写的代码默认就是由它来加载,ClassLoader.getSystemClassLoader返回的就是它。

类加载器路径在Launcher 源码体现:

public class Launcher {
  //系统类加载器加载的jar路径
  private static String bootClassPath = System.getProperty("sun.boot.class.path");  
  
  //应用程序类加载器
   static class AppClassLoader extends URLClassLoader {
     public static ClassLoader getAppClassLoader(final ClassLoader extcl) throws IOException {
      ...
      //应用程序类加载器加载的jar路径
       final String s = System.getProperty("java.class.path");
    }
}
//扩展类加载器
  static class ExtClassLoader extends URLClassLoader {
      private static File[] getExtDirs() {
      ...
      String str = System.getProperty("java.ext.dirs");  //扩展类加载加载的jar路径器
      ...
     }

}

我们来验证下:

package com.test;

public class Test {

    public static void main(String[] args) throws ClassNotFoundException {
        System.out.println(System.getProperty("sun.boot.class.path"));

        System.out.println("------------");

        System.out.println(System.getProperty("java.ext.dirs"));
        System.out.println("------------");
        System.out.println(System.getProperty("java.class.path"));

    }
}

执行结果:

C:\Program Files\Java\jdk1.8.0_221\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\rt.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\sunrsasign.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_221\jre\classes
------------
C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext;C:\Windows\Sun\Java\lib\ext
------------
D:\workspace\vcn\1.8Test\bin;D:\maven\repo\mysql\mysql-connector-java\5.1.38\mysql-connector-java-5.1.38.jar

其中应用程序中,我引入了一个第三方包,mysql的驱动
在这里插入图片描述




参考:JAVA Launcher简析
classload之java程序入口sun.misc.Launcher源码分析

从 sun.misc.Launcher 类源码深入探索 ClassLoader

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
您好!针对您提到的 `sun.misc.BASE64Encoder` 的打包报错问题,我可以给您提供一些解决方案。 首先,需要注意的是,`sun.misc.BASE64Encoder` 是一个非标准的,它不是 Java 标准库中的一部分。因此,使用它可能会导致一些问题,特别是在不同的 Java 版本或不同的环境下。 如果您遇到了 `sun.misc.BASE64Encoder` 的打包报错,可能是因为该在最新的 Java 版本中已被移除。为了解决这个问题,您可以尝试以下几种方法: 1. 使用 Java 标准库提供的 `java.util.Base64` 来进行 Base64 编码和解码操作。这是推荐的做法,因为它是 Java 标准库的一部分,可以跨平台使用,并且具有更好的兼容性。 例如,使用 `java.util.Base64` 进行编码可以这样写: ```java import java.util.Base64; public class Main { public static void main(String[] args) { String originalString = "Hello, World!"; String encodedString = Base64.getEncoder().encodeToString(originalString.getBytes()); System.out.println(encodedString); } } ``` 2. 如果您仍然希望使用 `sun.misc.BASE64Encoder`,您可以试试通过设置系统属性来恢复它。在启动 JVM时,添加以下参数: ``` -Djdk.internal.lambda.dumpProxyClasses=/path/to/your/project ``` 这样可以将 `sun.misc.BASE64Encoder` 恢复到项目中的 `sun.misc` 包路径下。 请注意,这种方法并不推荐,因为它依赖于非标准的,并且在未来的 Java 版本中可能会被移除。因此,尽量使用标准库中提供的来进行 Base64 编码和解码操作。 希望这些信息对您有所帮助!如果您有其他问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值