zoukankan      html  css  js  c++  java
  • 详解class的加载过程

    一、Java从编码到执行

    首先我们来看一下Java是如何从编码到执行的呢? 我们有一个x.java文件通过执行javac命令可以变成x.class文件,当我们调用Java命令的时候class文件会被装载到内存中,这个过程叫做classloader。一般情况下我们自己写代码的时候会用到Java的类库,所以在加载的时候也会把Java类库相关的类也加载到内存中。装载完成之后会调用字节码解释器和JIT即时编译器来进行解释和编译,编译完之后由执行引擎开始执行,执行引擎下面对应的就是操作系统硬件了。下图是大体的流程:

    Java叫做跨平台的语言,JVM可以称之为跨语言的平台;

    有个问题:java是解释执行还是编译执行?答:解释和编译是可以混合的,特别常用的代码或则是代码用到的次数特别多的时候,会把一个即时编译做成本地编译,这样会很大程度上的提高效率。

    Java虚拟机是如何做到这么多语言都可以在上面运行,关键在于class文件,任何语言只要能编译成class文件,并且符合class文件的规范你就可以放在Java虚拟机上去运行。

    二、详解class文件的加载过程

    接下来主要讲的是一个class文件是怎么从硬盘上到内存中,并开始执行的。

    类加载主要有三个过程:loading 、linking 、initializing;其中linking又分为三个步骤:verification 、preparation 、resolution;

    1、首先Loading是什么意思呢?是把一个class问价load到内存中去;

    2、接下来是Linking分为了三小步:

    • verification  是用来校验加载进来的class文件是否符合class文件标准,如果不符合直接就会被拒绝了;
    • preparation  是将class文件静态变量赋默认值而不是初始值,例如static int i =8;这个步骤并不是将i赋值为8,而是赋值为默认值0;
    • resolution  是把class文件常量池中用到的符号引用转换成直接内存地址,可以访问到的内容;

    3、initializing  成为初始化,静态变量在这个时候才会被赋值为初始值;

    下面为类加载过程的简化图:

    类加载器的加载过程是分成不同的层次来加载的,不同的类加载器来加载不同的class文件,  Bootstrap >Extension>Application>Custom(自定义类加载器)

    1、第一个类加载器的层次为:Bootstrap 称为启动类加载器,是Java类加载层次中最顶层的类加载器,负责加载JDK中的核心类库。

    2、第二个类加载器的层次为:Extension 是用来加载扩展类的,主要负责加载Java的扩展类库,默认加载JAVA_HOME/jre/lib/ext/目录下的所有jar包。

    3、第三个类加载器的层次为:Application 又称为系统类加载器,负责在JVM启动时,加载来自在命令java中的classpath或者java.class.path系统属性或者CLASSPATH操作系统属性所指定的JAR类包和类路径。

    4、第三个类加载器的层次为:CustomClassLoader(自定义加载器)  

     1 package com.example.demo.classloader;
     2 
     3 public class ClassLoaderScope {
     4     public static void main(String[] args) {
     5         System.out.println("-------------------Bootstrap加载类-------------------");
     6         String property = System.getProperty("sun.boot.class.path");
     7         String s = property.replaceAll(";", System.lineSeparator());
     8         System.out.println(s);
     9 
    10         System.out.println("-------------------Ext加载类-------------------");
    11 
    12         String property1 = System.getProperty("java.ext.dirs");
    13         String s1 = property1.replaceAll(";", System.lineSeparator());
    14         System.out.println(s1);
    15 
    16         System.out.println("-------------------App加载类-------------------");
    17 
    18         String property2 = System.getProperty("java.class.path");
    19         String s2 = property2.replaceAll(";", System.lineSeparator());
    20         System.out.println(s2);
    21     }
    22 }
    23         /**输出结果只截取了部分*/
    24         //E:JDKjdk1.8jrelib
    esources.jar
    25         //E:JDKjdk1.8jrelib
    t.jar
    26         //E:JDKjdk1.8jrelibsunrsasign.jar
    27         //E:JDKjdk1.8jrelibjsse.jar
    28         //E:JDKjdk1.8jrelibjce.jar
    29         //E:JDKjdk1.8jrelibcharsets.jar
    30         //E:JDKjdk1.8jrelibjfr.jar
    31         //E:JDKjdk1.8jreclasses
    32         //----------------------------------------------
    33         //E:JDKjdk1.8jrelibext
    34         //C:WindowsSunJavalibext
    35         //----------------------------------------------
    36         //E:JDKjdk1.8jrelibcharsets.jar
    37         //E:JDKjdk1.8jrelibdeploy.jar
    38         //E:JDKjdk1.8jrelibextaccess-bridge-64.jar
    39         //E:JDKjdk1.8jrelibextcldrdata.jar
    40         //E:JDKjdk1.8jrelibextdnsns.jar
    41         //E:JDKjdk1.8jrelibextjaccess.jar
    42         //E:JDKjdk1.8jrelibextjfxrt.jar

    特别注意一点这个的层级关系并没有继承的关系在里面,只是单单纯纯的语法上的继承;

    下图为类加载的一个全过程:

    用比较通俗的话来解释这个过程,当有一个类需要被加载时,首先要判断这个类是否已经被加载到内存,判断加载与否的过程是有顺序的,如果有自己定义的类加载器,会先到custom class loader 的cache(缓存)中去找是否已经加载,若已加载直接返回结果,否则到App的cache中查找,如果已经存在直接返回,如果不存在,到Extension中查找,存在直接返回,不存在继续向父加载器中寻找直到Bootstrap顶层,如果依然没找到,那就是没有加载器加载过这个类,需要委派对应的加载器来加载,先看看这个类是否在自己的加载范围内,如果是直接加载返回结果,若不是继续向下委派,以此类推直到最下级,如果最终也没能加载,就会直接抛异常 ClassNotFoundException,这就是双亲委派模式。

    理解双亲委派模式:

    1、父加载器:不是类加载器的加载器,也不是类加载器的父类加载器(此处意思是没有父类与子类之间的继承关系)。

     1 package com.example.demo.classloader;
     2 
     3 /**
     4  * 验证了父加载器不是加载器的加载器
     5  */
     6 public class ParentAndChild {
     7     public static void main(String[] args) {
     8         //AppClassLoader
     9         ClassLoader classLoader = ParentAndChild.class.getClassLoader();
    10         System.out.println(classLoader);
    11 
    12         //null  这里AppClassLoader的加载器不是ExtClassLoader  而是Bootstrap
    13         ClassLoader appclassLoader = ParentAndChild.class.getClassLoader().getClass().getClassLoader();
    14         System.out.println(appclassLoader);
    15 
    16         //ExtClassLoader   AppClassLoader的父加载器是ExtClassLoader
    17         ClassLoader parent = ParentAndChild.class.getClassLoader().getParent();
    18         System.out.println(parent);
    19 
    20         //null
    21         ClassLoader parentparent = ParentAndChild.class.getClassLoader().getParent().getParent();
    22         System.out.println(parentparent);
    23 
    24         //null
    25         ClassLoader parentparentparent = ParentAndChild.class.getClassLoader().getParent().getParent().getParent();
    26         System.out.println(parentparent);
    27 
    28         /**输出结果*/
    29         //sun.misc.Launcher$AppClassLoader@18b4aac2
    30         //null
    31         //sun.misc.Launcher$ExtClassLoader@23fc625e
    32         //null
    33         //Exception in thread "main" java.lang.NullPointerException at com.example.demo.classloader.ParentAndChild.main(ParentAndChild.java:22)
    34     }
    35 }

    2、双亲委派:其工作原理的是,如果一个类加载器收到了类加载请求,并不会直接去加载,而是自下而上的向顶层类加载器查找是否已经被加载了,如果被加载就不用进行加载,如果未被加载过,则会自上而下的检查是否属于自己加载的范围,如果属于则加载,如果不属于则向下委托,直到类被加载进来才能叫做成功,如果加载不成功就会抛异常classnotfoundexeption,这就叫做双亲委派。

    3、为什么要搞双亲委派模式?

    主要是为了安全,这里可以使用反证法,如果任何类加载器都可以把class加载到内存中,我们就可以自定义类加载器来加载Java.lang.string。在打包时可以把密码存储为String对象,偷偷摸摸的把密码发送到自己的邮箱,这样会造成安全问题。

    三、自定义类加载器

     1 package com.example.demo.classloader;
     2 
     3 public class ClassLoaderByHand {
     4     public static void main(String[] args) throws ClassNotFoundException {
     5         Class<?> clazz = ClassLoaderByHand.class.getClassLoader().
     6                 loadClass("com.example.demo.threaddemo.juc_002.Account");
     7         String name = clazz.getName();
     8         System.out.println(name);
     9         
    10     }
    11 }
    12 
    13    /**
    14     * 输出结果
    15     */
    16    //com.example.demo.threaddemo.juc_002.Account

    代码运行结果可以看出,就是你要加载一个类你只要调用classLoader中的 loadClass()方法就能把这个类加载到内存中,加载完成之后会给你返回一个Class类的对象。

    在硬盘上找到这个类的源码,把它load到内存,与此同时生成一个Class对象,上述的小程序是通过 ClassLoaderByHand 找到他的加载器AppClassLoader 然后调用它的loadClass()方法,让它帮我们把 Account类加载进来,返回一个clazz对象,使用clazz.getName()方法正常返回Account类。

    什么时候我们需要自己定义去加载一个类?

    热部署时就是先把之前加载的类给干掉 ,然后使用的自定义类加载器来进行重新加载

    spring的动态代理,一个新的class 当需要的时候就会把它load到内存中

    我们还是来看一下源码吧,加载过程最主要的还是ClassLoader中的loaderClass()方法:

     结合上面给的类加载过程的图解一起看会更容易一些;

     1 protected Class<?> loadClass(String name, boolean resolve)
     2             throws ClassNotFoundException
     3     {
     4         synchronized (getClassLoadingLock(name)) {
     5             /**
     6              * 在加载之前先调用findLoadedClass()方法查看是否已经加载过此类
     7              * 若加载过 返回该对象
     8              * 如果未加载则返回null 进行下一步
     9              */
    10             // First, check if the class has already been loaded
    11             Class<?> c = findLoadedClass(name);
    12             if (c == null) {
    13                 long t0 = System.nanoTime();
    14                 try {
    15                     //判断有无父加载器 如果不为空说明还未到顶层Bootstrap递归调用loadClass()
    16                     if (parent != null) {
    17                         c = parent.loadClass(name, false);
    18                     } else {
    19                         //如果没有父加载器说明调用的加载器为Bootstrap Class Loader, 在此加载器内存中查找是否已经加载
    20                         c = findBootstrapClassOrNull(name);
    21                     }
    22                 } catch (ClassNotFoundException e) {
    23                     // ClassNotFoundException thrown if class not found
    24                     // from the non-null parent class loader
    25                 }
    26                 //若以上的操作都没成功加载此类
    27                 if (c == null) {
    28                     // If still not found, then invoke findClass in order
    29                     // to find the class.
    30                     long t1 = System.nanoTime();
    31                     //调用自己的findClass()
    32                     c = findClass(name);
    33 
    34                     // this is the defining class loader; record the stats
    35                     sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
    36                     sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
    37                     sun.misc.PerfCounter.getFindClasses().increment();
    38                 }
    39             }
    40             if (resolve) {
    41                 resolveClass(c);
    42             }
    43             return c;
    44         }
    45     }
  • 相关阅读:
    配置文件和脚本文件区别
    .sh
    瘋耔思维空间
    vi编辑器的三种模式
    在ubuntu系统荣品开发配套JDK安装
    如何查看自己运行ubuntu是32位还是64位
    志气
    高仿微信朋友圈
    Java OCR tesseract 图像智能字符识别技术 Java代码实现
    构建基于Javascript的移动CMS——加入滑动
  • 原文地址:https://www.cnblogs.com/dongl961230/p/13212080.html
Copyright © 2011-2022 走看看