Java类加载机制

1、什么是类加载

每个.java类文件都存储着需要执行的程序逻辑,这些文件称为源文件,源文件不能直接被jvm执行,需要经过Java编译器(javac)编译成.class文件,.class文件中保存着Java代码经转换后的虚拟机指令,当需要使用某个类时jvm将会加载它的class文件,并创建对应的class对象,将class文件加载到虚拟机内存中,这个过程称为类加载。

2、类加载过程

类加载过程如下图所示:

1、加载:通过类的绝对路径查找此类的字节码文件,并利用字节码文件创建一个Class对象。

2、验证:目的在于确保Class文件的字节流中包含的信息符合当前虚拟机要求,不会危害虚拟机自身安全。主要包括文件格式验证,元数据验证,字节码验证,符号引用验证。

3、准备:为类的static变量分配内存并且设置该类变量的初始值即0(如static int i=5,准备阶段这里只将i初始化为0,至于5的值将在初始化时赋值),这里不包含用final修饰的static,因为final在编译的时候就会分配了。注意这里不会为实例变量分配内存和初始化,类变量会分配在方法区中,而实例变量会随着对象一起分配到Java堆中。

4、解析:有类或接口的解析,字段解析,类方法解析,接口方法解析。

5、初始化:执行静态初始化代码块和静态初始化成员变量(如前面只初始化了默认值的static变量将在这个阶段赋值),成员变量也将被初始化。

3、类加载器

3.1、类加载器分类

        虚拟机提供了3种类加载器,启动类加载器(BootstrapClassLoader)、扩展类加载器(ExtClassLoader)、应用类加载器(AppClassLoader),下面我们分别介绍下。

3.1.1、BootstrapClassLoader

        启动类加载器使用C++语言实现的,是虚拟机自身的一部分,它负责将 <JAVA_HOME>/lib路径下的核心类库或-Xbootclasspath参数指定的路径下的jar包加载到内存中,Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类。注意由于虚拟机是按照文件名识别加载jar包的,如rt.jar,如果文件名不被虚拟机识别,即使把jar包丢到lib目录下也是没有作用的。

3.1.2、ExtClassLoader

        扩展类加载器是指Sun公司实现的sun.misc.Launcher$ExtClassLoader类,由Java语言实现的,是Launcher的静态内部类。它负责加载<JAVA_HOME>/lib/ext目录下或者由系统变量-D java.ext.dir指定位路径中的类库,开发者可以直接使用标准扩展类加载器。

//ExtClassLoader类中获取路径的代码
private static File[] getExtDirs() {
     //加载<JAVA_HOME>/lib/ext目录中的类库
     String s = System.getProperty("java.ext.dirs");
     File[] dirs;
     if (s != null) {
         StringTokenizer st =
             new StringTokenizer(s, File.pathSeparator);
         int count = st.countTokens();
         dirs = new File[count];
         for (int i = 0; i < count; i++) {
             dirs[i] = new File(st.nextToken());
         }
     } else {
         dirs = new File[0];
     }
     return dirs;
 }

3.1.3、AppClassLoader

        应用类加载器是指 Sun公司实现的sun.misc.Launcher$AppClassLoader,由Java语言实现,是Launcher的静态内部类。它负责加载系统类路径java -classpath或-D java.class.path 指定路径下的类库,也就是我们经常用到的classpath路径,开发者可以直接使用应用类加载器,应用类加载器一般情况下是程序中默认的类加载器,通过ClassLoader#getSystemClassLoader()方法可以获取到该类加载器。

3.2、双亲委派机制

3.2.1、sun.misc.Launcher源码解析

        我们简化下sun.misc.Launcher源码,看看三种类加载器到底负责加载哪些class对象。

public class Launcher {
    private static Launcher launcher = new Launcher();
    private static String bootClassPath =
        System.getProperty("sun.boot.class.path");

    public static Launcher getLauncher() {
        return launcher;
    }

    private ClassLoader loader;

    public Launcher() {
        // Create the extension class loader
        ClassLoader extcl;
        try {
            extcl = ExtClassLoader.getExtClassLoader();
        } catch (IOException e) {
            throw new InternalError(
                "Could not create extension class loader", e);
        }

        // Now create the class loader to use to launch the application
        try {
            loader = AppClassLoader.getAppClassLoader(extcl);
        } catch (IOException e) {
            throw new InternalError(
                "Could not create application class loader", e);
        }

        //设置AppClassLoader为线程上下文类加载器,这个文章后面部分讲解
        Thread.currentThread().setContextClassLoader(loader);
    }

    /*
     * Returns the class loader used to launch the main application.
     */
    public ClassLoader getClassLoader() {
        return loader;
    }
    /*
     * The class loader used for loading installed extensions.
     */
    static class ExtClassLoader extends URLClassLoader {}

/**
     * The class loader used for loading from java.class.path.
     * runs in a restricted security context.
     */
    static class AppClassLoader extends URLClassLoader {}

String bootClassPath = System.getProperty("sun.boot.class.path")这是 BootstrapClassLoader负负责加载的class文件的路径  ,我们打印下:

System.out.println(System.getProperty("sun.boot.class.path"));

打印结果:

C:\Program Files\Java\jre1.8.0_91\lib\resources.jar;
C:\Program Files\Java\jre1.8.0_91\lib\rt.jar;
C:\Program Files\Java\jre1.8.0_91\lib\sunrsasign.jar;
C:\Program Files\Java\jre1.8.0_91\lib\jsse.jar;
C:\Program Files\Java\jre1.8.0_91\lib\jce.jar;
C:\Program Files\Java\jre1.8.0_91\lib\charsets.jar;
C:\Program Files\Java\jre1.8.0_91\lib\jfr.jar;
C:\Program Files\Java\jre1.8.0_91\classes

上面就是启动类加载器负责加载的class文件,其中就包括了rt.jar。

ExtClassLoader源码中发现:

private static File[] getExtDirs() {
      String var0 = System.getProperty("java.ext.dirs");
      File[] var1;
      if (var0 != null) {
        StringTokenizer var2 = new StringTokenizer(var0, File.pathSeparator);
        int var3 = var2.countTokens();
        var1 = new File[var3];

        for(int var4 = 0; var4 < var3; ++var4) {
          var1[var4] = new File(var2.nextToken());
        }
      } else {
        var1 = new File[0];
      }

      return var1;
}

   我们打印System.getProperty("java.ext.dirs"),打印结果如下:
 

C:\Program Files\Java\jre1.8.0_91\lib\ext

ExtClassLoader加载了ext目录下的jar包。

AppClassLoader源码发下:

public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException {
      final String var1 = System.getProperty("java.class.path");
      final File[] var2 = var1 == null ? new File[0] : Launcher.getClassPath(var1);
      return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction<Launcher.AppClassLoader>() {
        public Launcher.AppClassLoader run() {
          URL[] var1x = var1 == null ? new URL[0] : Launcher.pathToURLs(var2);
          return new Launcher.AppClassLoader(var1x, var0);
        }
      });
}

AppClassLoader负责加载java.class.path路径下的class文件,我们打印System.getProperty("java.class.path"),打印结果如下:

D:\workspace\ClassLoaderDemo\bin

这个路径其实就是当前Java工程的bin目录,里面存放的是我们自己定义的类的class文件,也就是我们自己的class文件是通过AppClassLoader加载的。

3.2.2、 双亲委托委派机制

        Java虚拟机对class文件采用的是按需加载方式,也就是说当需要使用该类时才会将它的class文件加载到内存生成class对象,而且加载某个类的class文件时,Java虚拟机采用的是双亲委派模式即把请求交由父类加载器处理,它一种任务委派模式。注意所谓的“父类”加载器,和继承概念中的“父类”、“超类”不是同一个概念,AppClassLoader的父类加载器是ExtClassLoader,ExtClassLoader的父类加载器是BootstrapClassLoader自定义类加载器的父类加载器是AppClassLoader。

        工作原理:

         如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式,即每个儿子都很懒,每次有活就丢给父亲去干,直到父亲说这件事我也干不了时,儿子自己想办法去完成。

        双亲委派的优势:

       1、避免类的重复加载

        Java类随着它的类加载器一起具备了一种带有优先级的层次关系,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。

        2、更安全,避免java.xxx系统类被随意篡改,保证java.xxx,javax.xxx核心类的安全。

        假设通过网络传递一个名为java.lang.Integer的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer,而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改。

        如果我们在classpath路径下自定义一个名为java.lang.SingleInterge类(该类是胡编的)呢?该类并不存在java.lang中,经过双亲委托模式,传递到启动类加载器中,由于父类加载器路径下并没有该类,所以不会加载,将反向委托给子类加载器加载,最终会通过应用类加载器加载该类。但是这样做是不允许,因为java.lang是核心API包,需要访问权限,强制加载将会报出如下异常:

java.lang.SecurityException: Prohibited package name: java.lang

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值