精华内容
下载资源
问答
  • 类加载过程

    万次阅读 多人点赞 2018-08-06 12:30:04
    类的加载就是将class文件中的二进制数据读取到... 类加载的最终产物是堆内存中的Class对象,对于同一个ClassLoader来说,无论类被加载多少次,对应带堆内存中的对象始终是同一个,这里所说的加载是类加载过程中的第...

        类的加载就是将class文件中的二进制数据读取到内存中,然后将该字节流所代表的静态数据结构转化为方法区中运行的数据结构,并且在堆内存中生成一个java.lang.Class对象作为访问方法区数据结构的入口


     

         类加载的最终产物是堆内存中的Class对象,对于同一个ClassLoader来说,无论类被加载多少次,对应带堆内存中的对象始终是同一个,这里所说的加载是类加载过程中的第一个阶段,第二个阶段

         连接阶段:

    1.验证,确保当前class文件的字节流所包含的内容符合当前JVM的规范要求,并且不会出现危害JVM自身安全的代码,当前字节流不符合规范会抛出VerifyError的异常,或者子异常,验证的信息有:(1)文件格式:验证二进制文件是什么类型,验证是否符合当前JVM规范,(2)元数据验证:检查类是否有父类、接口,验证其父类、接口的合法性,    验证被final修饰的类,  验证是否是抽象类,是否实现了父类的抽象方法或者接口中的方法,   验证方法的重载。(3)字节码验证,主要验证程序的控制流程比如循环、分支等,(4)符号验证,主要验证符号引用转换为直接引用时的合法性

    2.准备,当一个Class文件的字节流通过验证,就开始为该对象的类变量,也就是静态变量,分配内存和初始值,各种数据类型的初始值:

    序号

    数据类型

    大小/位

    封装类

    默认值

    可表示数据范围

    1

    byte(位)

    8

    Byte

    0

    -128~127

    2

    short(短整数)

    16

    Short

    0

    -32768~32767

    3

    int(整数)

    32

    Integer

    0

    -2147483648~2147483647

    4

    long(长整数)

    64

    Long

     0L

    -9223372036854775808~9223372036854775807

    5

    float(单精度)

    32

    Float

    0.0f

    1.4E-45~3.4028235E38

    6

    double(双精度)

    64

    Double

    0.0

    4.9E-324~1.7976931348623157E308

    char(字符)

    16

    Character

    '/uoooo'(null)

    0~65535

    8

    boolean

    8

    Boolean

    flase

    true或false

     

    final修饰:

        private static int aa = 10;//(1)
        private static final int bb = 10;//(2)

    在(1)的位置  static int aa = 10在准备阶段中不是10,而是初始值0,而(2)static final int bb= 10会是10,因为final修饰的静态变量不会导致类的初始化,可以直接计算出结果。

    3.解析,所谓解析就是指在常量池中找到类、接口、方法、字段的符号引用,并将其替换为直接引用的过程。

    4.初始化,执行<clinit>()方法(clinit是class initialize的简写),<clinit>()方法再编译过程中生成,此方法中包含了所有类变量的赋值以及静态代码语句块的执行代码,编译器收集的顺序是由执行语句在源文件中的出现顺序来决定的,静态语句块只能对后面的静态变量进行赋值,而不能对其进行访问,

    public class test2 {
    
        static {
            System.out.println(X);(1)
            X =100;
        }
       private static int X = 10;
    }

    在(1)位置,会编译错误,illegal forward reference,另外,父类的静态语句块会被优先执行java编译器会生成<clinit>()方法,但是该方法不是每一个类都会生成,如果某一个类无静态变量或者静态语句块,就没有生成<clinit>()方法的必要,<clinit>()方法在触发了类的初始化是被调用,如果有多个线程同时访问这个方法,不会引起线程安全问题,

    package ClassTest;
    
    import java.util.concurrent.TimeUnit;
    import java.util.stream.IntStream;
    
    public class ClassInit {
        static{
             try {
                 System.out.println("这是静态语句块,我被初始化了");
                 TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        public static void main(String[] args) {
            IntStream.range(0,5).forEach(i->new Thread(ClassInit :: new));
        }
    }
    

    运行结果为

    这是静态语句块,我被初始化了
    休眠结束
    
    Process finished with exit code 0
    

    在同一时间,只能有一个线程执行到静态代码块中的内容,并且静态代码块仅仅执行一次,JVM保证了<clinit>()方法在多线程的执行环境下的同步语义。

    再看一个小栗子

    package ClassTest;
    
        public class Singleton {
        //(1)
        private static int x = 0;
        private static int y ;
        private static Singleton singleton = new Singleton();//(2)
        private Singleton(){
            x++;
            y++;
        }
    
        public static Singleton getSingleton(){
            return singleton;
        }
        public static void main(String[] args) {
            Singleton singleton = Singleton.getSingleton();
            System.out.println("这是X变量"+singleton.x);
            System.out.println("这是Y变量"+singleton.y);
        }
    }
    

    当(2)语句放到(1)位置时,输出的结果会有变化,目前这个代码输出结果会是:X=1,Y=1。当语句变动后,输出结果为:X=0,Y=1。解释一下:

        private static int x = 0;
        private static int y ;
        private static Singleton singleton = new Singleton();

    在连接阶段的准备过程中,每个变量都会被赋一个初始值    x=0 y=0 singleton = null,跳过解析过程,在初始化阶段每一个变量赋正确的值

    x=0,y=0,singleton=new Singleton(),在new Singleton的时候执行类的构造函数,对x和y进行了自增,因此结果为x=1,y=1.
     

    private static Singleton singleton = new Singleton();

    private static int x = 0;
    private static int y ;

    在连接阶段的准备过程中,每个变量都会被赋一个初始值    singleton = null,x=0,y=0 跳过解析过程,在初始化阶段每一个变量赋正确的值,首先会进入Singleton的构造函数,执行完构造函数后 x=1,y=1,然后开始初始化x,y,    x被赋予0的值,y没有被赋值,因此在构造函数中的值即为y的值,y=1,所以结果为x=0,y=1

     

     

    展开全文
  • JVM类加载过程

    万次阅读 多人点赞 2019-06-20 15:10:25
    1. JVM类加载过程 1.概述 从类的生命周期而言,一个类包括如下阶段: 加载、验证、准备、初始化和卸载这5个阶段的顺序是确定的,类的加载过程必须按照这种顺序进行,而解析阶段则不一定,它在某些情况下...

    1. JVM类加载过程

        1.概述

        从类的生命周期而言,一个类包括如下阶段:

        

            加载、验证、准备、初始化和卸载这5个阶段的顺序是确定的,类的加载过程必须按照这种顺序进行,而解析阶段则不一定,它在某些情况下可能在初始化阶段后在开始,因为java支持运行时绑定。

        2. 类加载时机

        加载(loading)阶段,java虚拟机规范中没有进行约束,但初始化阶段,java虚拟机严格规定了有且只有如下5种情况必须立即进行初始化(初始化前,必须经过加载、验证、准备阶段):

        (1)使用new实例化对象时,读取和设置类的静态变量、静态非字面值常量(静态字面值常量除外)时,调用静态方法时。

        (2)对内进行反射调用时。

        (3)当初始化一个类时,如果父类没有进行初始化,需要先初始化父类。

        (4)启动程序所使用的main方法所在类

        (5)当使用1.7的动态语音支持时。

        如上5种场景又被称为主动引用,除此之外的引用称为被动引用,被动引用有如下3种常见情况

    • 通过子类引用父类的静态字段,只会触发父类的初始化,而不会触发子类的初始化。
    • 定义对象数组和集合,不会触发该类的初始化
    • 类A引用类B的static final常量不会导致类B初始化(注意静态常量必须是字面值常量,否则还是会触发B的初始化)
    public class TestClass {
        public static void main(String[] args) {
            System.out.println(ClassInit.str);
            System.out.println(ClassInit.id);
        }
    }
    class ClassInit{
        public static final long id=IdGenerator.getIdWorker().nextId();//需要初始化ClassInit类
        public static final String str="abc";//字面值常量
        static{
            System.out.println("ClassInit init");
        }
    }
    • 通过类名获取Class对象,不会触发类的初始化。如System.out.println(Person.class);
    • 通过Class.forName加载指定类时,如果指定参数initialize为false时,也不会触发类初始化。
    • 通过ClassLoader默认的loadClass方法,也不会触发初始化动作

        注意:被动引用不会导致类初始化,但不代表类不会经历加载、验证、准备阶段。

        3. 类加载方式

        这里的类加载不是指类加载阶段,而是指整个类加载过程,即类加载阶段到初始化完成。

       (1)隐式加载

    • 创建类对象
    • 使用类的静态域
    • 创建子类对象
    • 使用子类的静态域
    • 在JVM启动时,BootStrapLoader会加载一些JVM自身运行所需的class
    • 在JVM启动时,ExtClassLoader会加载指定目录下一些特殊的class
    • 在JVM启动时,AppClassLoader会加载classpath路径下的class,以及main函数所在的类的class文件

        (2)显式加载

    • ClassLoader.loadClass(className),只加载和连接、不会进行初始化
    • Class.forName(String name, boolean initialize,ClassLoader loader); 使用loader进行加载和连接,根据参数initialize决定是否初始化。

    2. 加载阶段

        加载是类加载过程中的一个阶段,不要将这2个概念混淆了。

        在加载阶段,虚拟机需要完成以下3件事情:

    • 通过一个类的全限定名来获取定义此类的二进制字节流
    • 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
    • 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

        加载.class文件的方式

    • 从本地系统中直接加载
    • 通过网络下载.class文件
    • 从zip,jar等归档文件中加载.class文件
    • 从专有数据库中提取.class文件
    • 将Java源文件动态编译为.class文件    

        相对于类生命周期的其他阶段而言,加载阶段(准确地说,是加载阶段获取类的二进制字节流的动作)是可控性最强的阶段,因为开发人员既可以使用系统提供的类加载器来完成加载,也可以自定义自己的类加载器来完成加载。

    3. 连接阶段

      3.1 验证:确保被加载的类的正确性

        确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。

    • 文件格式验证:验证字节流是否符合Class文件格式的规范,如:是否以模数0xCAFEBABE开头、主次版本号是否在当前虚拟机处理范围内等等。
    • 元数据验证:对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言规范的要求;如:这个类是否有父类,是否实现了父类的抽象方法,是否重写了父类的final方法,是否继承了被final修饰的类等等。
    • 字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的,如:操作数栈的数据类型与指令代码序列能配合工作,保证方法中的类型转换有效等等。
    • 符号引用验证:确保解析动作能正确执行;如:通过符合引用能找到对应的类和方法,符号引用中类、属性、方法的访问性是否能被当前类访问等等。

        验证阶段是非常重要的,但不是必须的。可以采用-Xverify:none参数来关闭大部分的类验证措施。

      3.2 准备:为类的静态变量分配内存,并将其赋默认值

        为类变量分配内存并设置类变量初始值,这些内存都将在方法区中分配。对于该阶段有以下几点需要注意:

    • 只对static修饰的静态变量进行内存分配、赋默认值(如0、0L、null、false等)。
    • 对final的静态字面值常量直接赋初值(赋初值不是赋默认值,如果不是字面值静态常量,那么会和静态变量一样赋默认值)。

      3.3 解析:将常量池中的符号引用替换为直接引用(内存地址)的过程

        符号引用就是一组符号来描述目标,可以是任何字面量。属于编译原理方面的概念如:包括类和接口的全限定名、字段的名称和描述符、方法的名称和描述符。

        直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。如指向方法区某个类的一个指针。

        假设:一个类有一个静态变量,该静态变量是一个自定义的类型,那么经过解析后,该静态变量将是一个指针,指向该类在方法区的内存地址。 具体见后续文章。

    4. 初始化:为类的静态变量赋初值

        赋初值两种方式:

    • 定义静态变量时指定初始值。如 private static String x="123";
    • 静态代码块里为静态变量赋值。如 static{ x="123"; } 

        注意:只有对类的主动使用才会导致类的初始化。

    5. clinit 与 init

        在编译生成class文件时,编译器会产生两个方法加于class文件中,一个是类的初始化方法clinit, 另一个是实例的初始化方法init。

      5.1 clinit 

        clinit指的是类构造器,主要作用是在类加载过程中的初始化阶段进行执行,执行内容包括静态变量初始化和静态块的执行。

        注意事项:

        1. 如果类中没有静态变量或静态代码块,那么clinit方法将不会被生成。

        2. 在执行clinit方法时,必须先执行父类的clinit方法。

        3. clinit方法只执行一次。

        3. static变量的赋值操作和静态代码块的合并顺序由源文件中出现的顺序决定。如下代码所示:

    public class TestClass {
        public static void main(String[] args) {
            ClassInit init=ClassInit.newInstance();
    
            System.out.println(init.x);
            System.out.println(init.y);
        }
    }
    
    class ClassInit{
        private static ClassInit init=new ClassInit();
        public static int x;
        public static int y=0;
        static{
            x=10;
            y=10;
        }
        private ClassInit(){
            x++;
            y++;
        }
        public static ClassInit newInstance(){
            return init;
        }
    }
    //在类加载到连接完成阶段,ClassInit类在内存中的状态为:init=null,x=0,y=0
    //初始化阶段时,需要执行clinit方法,该方法类似如下伪代码:
    clinit(){
    	//init=new ClassInit();调用构造方法
        x++;//x=1 因为此时x的值为连接的准备阶段赋的默认值0,然后++变成1
        y++;//y=1 因为此时y的值为连接的准备阶段赋的默认值0,然后++变成1
        //x=0;//为什么这里没有执行x=0,因为程序没有给x赋初值,因此在初始化阶段时,不会执行赋初值操作
        y=0;//因为类变量y在定义时,指定了初值,尽管初值为0,因此在初始化阶段的时候,需要执行赋初值操作
        x++;//第一个静态块的自增操作,结果为x=2;
        y++;//第一个静态块的自增操作,结果为y=1;
    }
    //所以最终结果为x=2,y=1
    //如果private static ClassInit init=new ClassInit(); 代码在public static int y=0;后面,那么clinit方法的伪代码如下:
    clinit(){
        //x=0;//这里虽然没有执行,但此时x的值为连接的准备阶段赋的默认值0
        y=0;//因为类变量y在定义时,指定了初值,尽管初值为0,因此在初始化阶段的时候,需要执行赋初值操作
    	//init=new ClassInit();调用构造方法
        x++;//x=1 因为此时x的值为连接的准备阶段赋的默认值0,然后++变成1
        y++;//y=1 因为此时y的值为初始化阶段赋的初值,只是这个初值刚好等于默认值0而已,然后++变成1
        x++;//第一个静态块的自增操作,结果为x=2;
        y++;//第一个静态块的自增操作,结果为y=2;
    }
    //最终结果为x=2,y=2

        5.2 init

        init指的是实例构造器,主要作用是在类实例化过程中执行,执行内容包括成员变量初始化和代码块的执行。

        注意事项:

        1. 如果类中没有成员变量和代码块,那么clinit方法将不会被生成。

        2. 在执行init方法时,必须先执行父类的init方法。

        3. init方法每实例化一次就会执行一次。

        3. init方法先为实例变量分配内存空间,再执行赋默认值,然后根据源码中的顺序执行赋初值或代码块。如下代码所示:

    public class TestClass {
        public static void main(String[] args) {
            ClassInit init=new ClassInit();
        }
    }
    
    class ClassInit{
        public int x;
        public int y=111;
        public ClassInit(){
            x=1;
            y=1;
        }
        {
            x=2;
            y=2;
        }
        {
            x=3;
            y=3;
        }
    }
    //实例化步骤为:先为属性分配空间,再执行赋默认值,然后按照顺序执行代码块或赋初始值,最后执行构造方法
    //根据上述代码,init方法的伪代码如下:
    init(){
    	x=0;//赋默认值
        y=0;//赋默认值
        y=111;//赋初值
        x=2;//从上到下执行第一个代码块
        y=2;//从上到下执行第一个代码块
        x=3;//从上到下执行第二个代码块
        y=3;//从上到下执行第二个代码块
        //ClassInit();执行构造方法
        x=1;//最后执行构造方法
        y=1;//最后执行构造方法
    }
    //如果上述代码的成员变量x,y的定义在类最后时,那么init方法的伪代码如下:
    init(){
    	x=0;//赋默认值
        y=0;//赋默认值
        x=2;//从上到下执行第一个代码块
        y=2;//从上到下执行第一个代码块
        x=3;//从上到下执行第二个代码块
        y=3;//从上到下执行第二个代码块
        y=111;//赋初值
        //ClassInit();执行构造方法
        x=1;//最后执行构造方法
        y=1;//最后执行构造方法
    }

    6. 卸载阶段

        执行了System.exit()方法

        程序正常执行结束

        程序在执行过程中遇到了异常或错误而异常终止

        由于操作系统出现错误而导致Java虚拟机进程终止
       

    展开全文
  • java类加载过程

    万次阅读 2019-03-10 11:39:38
    类加载机制 JVM将类描述数据从.class文件中加载到内存,并对数据进行,解析和初始化,最终形成被JVM直接使用的Java类型。 类从被加载到JVM中开始,到卸载为止,整个生命周期包括:加载、验证、准备、解析、初始化、...

    类加载机制

    JVM将类描述数据从.class文件中加载到内存,并对数据进行,解析和初始化,最终形成被JVM直接使用的Java类型。 类从被加载到JVM中开始,到卸载为止,整个生命周期包括:加载、验证、准备、解析、初始化、使用卸载七个阶段。
    ——《深入理解Java虚拟机 JVM高级特性与最佳实践》
    在这里插入图片描述

    1. 加载(Loading):
      简单的说,类加载阶段就是由类加载器负责根据一个类的全限定名来读取此类的二进制字节流到JVM内部,并存储在运行时内存区的方法区,然后将其转换为一个与目标类型对应的java.lang.Class对象实例(Java虚拟机规范并没有明确要求一定要存储在堆区中,只是hotspot选择将Class对应哪个存储在方法区中),这个Class对象在日后就会作为方法区中该类的各种数据的访问入口。加载阶段由类加载器负责,过程见类加载器;简单的说,类加载阶段就是由类加载器负责根据一个类的全限定名来读取此类的二进制字节流到JVM内部,并存储在运行时内存区的方法区,然后将其转换为一个与目标类型对应的java.lang.Class对象实例(Java虚拟机规范并没有明确要求一定要存储在堆区中,只是hotspot选择将Class对应哪个存储在方法区中),这个Class对象在日后就会作为方法区中该类的各种数据的访问入口。加载阶段由类加载器负责,过程见类加载器;
    2. 链接(Linking)
      链接阶段要做的是将加载到JVM中的二进制字节流的类数据信息合并到JVM的运行时状态中,经由验证、准备和解析三个阶段。
      验证:验证类数据信息是否符合JVM规范,是否是一个有效的字节码文件,验证内容涵盖了类数据信息的格式验证、语义分析、操作验证等。
    • 格式验证
      验证是否符合class文件规范

    • 语义验证
      检查一个被标记为final的类型是否包含子类;检查一个类中的final方法是否被子类进行重写;确保父类和子类之间没有不兼容的一些方法声明(比如方法签名相同,但方法的返回值不同)

    • 操作验证
      在操作数栈中的数据必须进行正确的操作,对常量池中的各种符号引用执行验证(通常在解析阶段执行,检查是否通过引用中描述的全限定名定位到指定类型上,以及类成员信息的访问修饰符是否允许访问等)
      准备:准备阶段负责为类中static变量分配空间,并初始化(与程序无关,系统初始化);被final修饰的静态变量,会直接赋予原值;类字段的字段属性表中存在ConstantValue属性,则在准备阶段,其值就是ConstantValue的值。
      解析:解析阶段负责将常亮池中所有符号引用转换为直接引用(得到类或者字段、方法在内存中的指针或者偏移量,以便直接调用该方法),这个可以在初始化之后再执行。可以认为是一些静态绑定的会被解析,动态绑定则只会在运行是进行解析;静态绑定包括一些final方法(不可以重写),static方法(只会属于当前类),构造器(不会被重写);

    1. 初始化
      初始化阶段负责将所有static域按照程序指定操作对应执行(赋值static变量,执行static块)。
      如果执行的是static代码块,那么在初始化阶段,JVM就会执行static代码块中定义的所有操作。
      所有类变量初始化语句和静态代码块都会在编译时被前端编译器放在收集器里头,存放到一个特殊的方法中,这个方法就是方法,即类/接口初始化方法。该方法的作用就是初始化一个中的变量,使用用户指定的值覆盖之前在准备阶段里设定的初始值。任何invoke之类的字节码都无法调用方法,因为该方法只能在类加载的过程中由JVM调用。
      如果父类还没有被初始化,那么优先对父类初始化,但在方法内部不会显示调用父类的方法,由JVM负责保证一个类的方法执行之前,它的父类方法已经被执行。
      JVM必须确保一个类在初始化的过程中,如果是多线程需要同时初始化它,仅仅只能允许其中一个线程对其执行初始化操作,其余线程必须等待,只有在活动线程执行完对类的初始化操作之后,才会通知正在等待的其他线程。
      上述阶段通常都是交叉混合允许,没有严格的先后执行顺序;

    类加载器

    类加载器的主要任务:是类加载过程中的加载操作:根据一个类的全限定名读取该类的二进制字节流到JVM内部,然后转换为一个对应的java.lang.Class对象实例
    开发者可以通过编写自定义类加载器来自定义类的加载规则

    类加载器分类

    • 启动类加载器/Bootstrap ClassLoader

    在HotSpot虚拟机中,Bootstrap ClassLoader用C++语言编写并嵌入JVM内部,主要负载加载JAVA_HOME/lib目录中的所有类,或者加载由选项-Xbootcalsspath指定的路径下的类;

    • 拓展类加载器/ExtClasLoader

    ExtClassLoader继承ClassLoader类,负载加载JAVA_HOME/lib/ext目录中的所有类型,或者由参数-Xbootclasspath指定路径中的所有类型;

    • 应用程序类加载器/AppClassLoader

    ExtClassLoader继承ClassLoader类,负责加载用户类路径ClassPath下的所有类型,一般情况下为程序的默认类加载器;

    • 自定义加载器
      Java虚拟机规范将所有继承抽象类java.lang.ClassLoader的类加载器,定义为自定义类加载器;

    双亲委派模型

    在这里插入图片描述

    双亲委派过程

    当一个类加载器收到类加载任务时,立即将任务委派给它的父类加载器去执行,直至委派给最顶层的启动类加载器为止。如果父类加载器无法加载委派给它的类时,将类加载任务退回给它的下一级加载器去执行;
    除了启动类加载器以外,每个类加载器拥有一个父类加载器,用户的自定义类加载器的父类加载器是AppClassLoader;
    双亲委派模型可以保证全限名指定的类,只被加载一次;
    双亲委派模型不具有强制性约束,是Java设计者推荐的类加载器实现方式;

    展开全文
  • 浅析 Tomcat类加载过程

    2019-07-28 02:38:30
    java 类加载器的功能是将 class 加载入内存, tomcat的的应用程序加载过程使 tomcat拥有了在同一个 jvm 中加载管理多个应用的功能. 在介绍 tomcat应用程序加载过程前,我们先简单了解下 java 类加载机制.在Class 类中,...

    java 类加载器的功能是将 class 加载入内存, tomcat的的应用程序加载过程使 tomcat拥有了在同一个 jvm 中加载管理多个应用的功能.

    在介绍 tomcat应用程序加载过程前,我们先简单了解下 java 类加载机制.在Class 类中,我们可以看到Class#getClassLoader 方法,通过这个方法我们可以获取到相应类对应的 ClassLoader 信息.

    我们在应用程序中去输出不同类的 ClassLoader,比如

    public class Test {
      public static void main(String[] args) {
        System.out.println(Test.class.getClassLoader());
        System.out.println(Integer.class.getClassLoader());
      }
    }
    

    输出的结果是

    sun.misc.Launcher$AppClassLoader@18b4aac2
    null
    

    即我们自定义的类的 ClassLoader 为Launcher 中的内部类AppClassLoader,IntegerClassLoader显示为 null, 实际是它也是有ClassLoader 的,所有classLoadernull 的 class 的classLoader 都是bootstrap class loader,其中 java 应用程序中是不可见的. bootstrap class loade 是最底层的 ClassLoader, 它用于加载 jvm内部的 class. 在bootstrap classloader 之下是Launcher$ExtClassLoader, ExtClassLoader用于加载$JAVA_HOME/jre/lib/ext/下的 class; 在 ClassLoader 中,我们可以看到存在 parent 属性, 这里就得提到 ClassLoader 的委派模型,默认情况下,ClassLoader 去加载类时,会先委派给 parent 加载,若 parent 无法加载,再由自身加载,我们可以简单看下ClassLoader#loadClass(String name, boolean resolve)方法(不相关的代码未展示)

    protected Class<?> loadClass(String name, boolean resolve)
          throws ClassNotFoundException {
         //判断 parent 是否为 null, null 即 bootstrap class loader
        if (parent != null) {
          c = parent.loadClass(name, false);
        } else {
          c = findBootstrapClassOrNull(name);
        }
        //如parent 未加载相应的 class,则自身加载
        if (c == null) {
          // If still not found, then invoke findClass in order
          // to find the class.
          c = findClass(name);
        }
        return c;
      }
    

    Launcher$ExtClassLoader之下是Launcher$AppClassLoader,也就是我们上面 Test.class 的 ClassLoader,Launcher$AppClassLoader的 parent 为Launcher$ExtClassLoader.

    默认情况下,我们自已经编写的 Class的 ClassLoader 都是Launcher$AppClassLoader, 但tomcat 下的 webapps 目录下,是可以拥有多个应用的,如果所有类都使用同一个 ClassLoader 加载,无疑会造成应用程序的混乱. 比如两个应用分别使用 springboot2.x和 springboot1.x, 同时存在两个版本的版本冲突问题相应大家都有遇到.

    tomcat 加载类的过程,可从org.apache.catalina.startup.Bootstrap#main入口,在initClassLoaders()方法中,可以看到 tomcat 初始化了三个 ClassLoader. common,server(catalina)与 shared.

    private void initClassLoaders() {
                commonLoader = createClassLoader("common", null);
                if( commonLoader == null ) {
                    // no config file, default to this loader - we might be in a 'single' env.
                    commonLoader=this.getClass().getClassLoader();
                }
                catalinaLoader = createClassLoader("server", commonLoader);
                sharedLoader = createClassLoader("shared", commonLoader);
    }
    

    其中 common 是 shared 与 catalina的 parent,每个 ClassLoader 对应的类范围在$TOMCAT_HOME/conf/catalina.properties配置中可见,

    common.loader="${catalina.base}/lib".....
    server.loader=
    shared.loader=
    

    顾名思义,common 为公共基础ClassLoader,shared 为多个应用的共享的 ClassLoader,Server 为服务器的 ClassLoader.
    在 tomcat 中,继续往下看,我们可以看到在init方法中

    public void init() throws Exception {
    	Thread.currentThread().setContextClassLoader(catalinaLoader);
    	String methodName = "setParentClassLoader";
            Class<?> paramTypes[] = new Class[1];
            paramTypes[0] = Class.forName("java.lang.ClassLoader");
            Object paramValues[] = new Object[1];
            paramValues[0] = sharedLoader;
            Method method =
                startupInstance.getClass().getMethod(methodName, paramTypes);
            method.invoke(startupInstance, paramValues);
       }
    

    最终的结果是线程上下文的 ClassLoader 为catalinaLoader(serverLoader),CatalinaparentClassLoadersharedClassLoader.(Catalina不是 ClassLoader,只是存储了 parentClassLoader 信息,下面的StandardContext也是)
    然后再看StandardContext#startInternal

    protected synchronized void startInternal() throws LifecycleException {
       WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
       webappLoader.setDelegate(getDelegate());
       setLoader(webappLoader);
    }
    

    这里将 Catalina 中的 parentLoader 放入了 WebAppLoader 中,
    Webapp中定义了属性

    private String loaderClass = ParallelWebappClassLoader.class.getName();
    

    ParallelWebappClassLoader也就是我们应用服务的 ClassLoader,具体可见方法WebAppLoader#createClassLoader()

    private WebappClassLoaderBase createClassLoader()
            throws Exception {
    
            Class<?> clazz = Class.forName(loaderClass);
            WebappClassLoaderBase classLoader = null;
    
            if (parentClassLoader == null) {
                parentClassLoader = context.getParentClassLoader();
            }
            Class<?>[] argTypes = { ClassLoader.class };
            Object[] args = { parentClassLoader };
            Constructor<?> constr = clazz.getConstructor(argTypes);
            classLoader = (WebappClassLoaderBase) constr.newInstance(args);
    
            return classLoader;
        }
    

    创建ParallelWebappClassLoader后,startInternal()中执行了
    classLoader.setResources(context.getResources());
    将 classLoader 与应用中class 绑定.
    Context 与 WebAppLoader 也进行关联.
    这样,不同的 应用有了不同的 classLoader,但他们的 parent 都是 sharedClassLoader,也就是说他们可以共享sharedClassLoader 以上的所有类.

    而后请求过来时,首先经过的不是ParallelWebappClassLoader,因为没有 url 是没法判断是哪个应用的,这时是 catalinaLoader(serverLoader)处理请求,根据请求 url 寻找到对应的 context,再切换至对应应用的ParallelWebappClassLoader,对用户的请求进行处理.
    StandardHostValue#invoke方法可见

    public final void invoke(Request request, Response response)
            throws IOException, ServletException {
    		Context context = request.getContext();
    		context.bind(Globals.IS_SECURITY_ENABLED, MY_CLASSLOADER);
    }
    

    切换 classLoder 的方法可见
    StandardContext#bind

        public ClassLoader bind(boolean usePrivilegedAction, ClassLoader originalClassLoader) {
            Loader loader = getLoader();
            ClassLoader webApplicationClassLoader = null;
            if (loader != null) {
                webApplicationClassLoader = loader.getClassLoader();
            }
            Thread.currentThread().setContextClassLoader(webApplicationClassLoader);
      }
    

    以上即 tomcatClassLoader 加载与切换的简略过程.

    展开全文
  • 【总结】类加载过程

    千次阅读 2020-07-27 18:48:47
    该文章为知识总结的文章,如果是初学者,建议先从专栏学习:JVM专栏 文章目录一、类的加载过程二、加载...类加载过程的第一步,主要完成下面3件事情: 通过全类名获取定义此类的二进制字节流 将字节流所代表的静态存.
  • 一、什么是类加载机制?虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。二、类加载的时机类从被加载到...
  • Java JVM的类加载过程详解

    千次阅读 2020-04-07 20:49:07
    本文深入介绍Java的类加载过程:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)。
  • Java类加载过程详解

    千次阅读 2021-01-29 17:46:58
    类的生命周期类的加载类加载过程加载链接验证准备解析初始化总结 首先我们先简单了解一下整个JVM的体系结构是怎样的,如下图所示(图1): 再详细具体一点(图2): 上面的图片有些地方看不懂没关系,先在脑子里...
  • 给大家详细讲述了java类的加载过程以及类加载器的相关知识点内容,有需要的朋友可以学习下。
  • 首先明确“加载”是“类加载”的一个过程,不要混淆。加载过程中,虚拟机需要完成下列三个事情: 通过一个类的全限定名获取定义此类的二进制字节流; 将这个字节流所代表的静态存储结构转化为方法区运行时的数据...
  • Java:浅析类加载过程(一)

    千次阅读 2019-07-01 08:47:00
    《深入理解java虚拟机》一文中,把类加载过程分为5步:加载、验证、准备、解析以及初始化。而第一步“加载”这个过程,虚拟机需要完成3大步骤: (1)通过一个类的全限定名来获取此类的二进制流。 (2)将这个字节...
  • 从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载...
  • Java编译原理--类加载过程

    千次阅读 多人点赞 2018-08-15 20:44:47
    上一篇文章介绍了class文件的存储细节,class文件包括了的各种描述信息,但是Java程序的运行需要在内存中实现, 那么虚拟机是如何加载这些class文件的?class文件中的静态结构是如何转换成实际的存储结构...
  • 简述类加载过程

    2020-02-29 14:01:53
    3. 类加载的总体流程3.1 加载过程3.2 验证过程3.3 准备过程3.4 解析过程3.5 初始化过程 < hr/> 1. 什么是类加载 1.1 类加载的五大步骤 2. 什么时候触发类加载? 3. 类加载的总体流程 3.1 加载过程 3.2 验证...
  • Java虚拟机加载类的全过程包括:加载、验证、准备、解析、初始化。验证、准备、解析叫连接过程。今天我们讲解析。 解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,符号引用在class文件中以CONSTANT_...
  • 再战JVM (1) 类加载过程

    千次阅读 2021-04-05 12:30:07
    类加载过程一. 类加载过程二. 加载1. 加载过程2. 加载class文件的方式三. 连接1. 验证2. 准备3. 解析1. 类或接口的解析2. 字段的解析3. 方法解析 一. 类加载过程 类加载过程可以分为三个阶段 加载(Loading) 连接...
  • 类加载的时机 类从被加载到虚拟机中内存开始,到卸载除内存为止,它的生命周期包括如下图所示: 上图中的 加载,验证,准备,初始化,卸载这5个步骤的顺序是固定的,类的加载器也必须按这个顺序开始,而解析...
  • Java类加载过程

    千次阅读 2020-10-14 11:05:31
    Java类加载需要经历一下7个过程: 1.加载 加载是类加载的第一个过程,在这个阶段,将完成一下三件事情: •通过一个类的全限定名获取该类的二进制流。 •将该二进制流中的静态存储结构转化为方法去运行时数据结构...
  • 主要介绍了深入理解Java 类加载过程的相关资料,需要的朋友可以参考下
  • ** 1.如何从海量的APP找出一个二次打包的应用呢 ** 1.流量特征检测:首先二次打包应用仅仅是对原apk进行反编译修改了某些特征...app在启动的过程中创建了PathClassLoader加载dex文件,那么我们跟进PathClassLoader: /l
  • 深度分析Java的ClassLoader机制(源码级别)和Java加载、链接和初始化两个文章中分别介绍过,当一个Java第一次被真正使用到的时候静态资源被初始化、Java加载和初始化过程都是线程安全的。所以,创建一个...
  • Class类加载过程与类加载器

    千次阅读 2020-05-02 19:03:55
    在说类加载器和双亲委派模型之前,先来梳理下Class类文件的加载过程,JAVA...类加载过程 Class类从被加载到虚拟机内存开始,到卸载出内存为止,其生命周期包括:加载(Loading)、验证(Verification)、准备(Prepa...
  • Java中类加载过程和对象创建过程

    千次阅读 2015-11-21 21:47:26
    类加载过程: 1, JVM会先去方法区中找有没有相应类的.class存在。如果有,就直接使用;如果没有,则把相关类的.class加载到方法区 2, 在.class加载到方法区时,会分为两部分加载:先加载非静态内容,再加载静态...
  • JVM—类加载过程学习

    2020-12-22 07:34:00
    1 类加载过程图解   系统加载Class类型的文件主要是:加载->连接->初始化,其中连接分为:验证->准备->解析;其实,整个生命周期是7步,类从被加载到虚拟机内存中开始,到卸载出内存为止,分为:加载->验证->准备...
  • 面试官:请你谈谈Java的类加载过程

    万次阅读 多人点赞 2018-02-01 00:08:28
    刚刚走出校门的应届毕业生,如果在去寻求一份Java开发的工作时,你的面试官很有可能一边看着你的简历,一边漫不经心地问你:了解过Java类的加载过程吗?...今天,小编就Java类加载过程这个问题,抛砖引玉,说一下...
  • 类加载过程和对象创建的过程

    千次阅读 2019-06-03 19:09:32
    类加载过程 在java中,类加载是指将类的相关信息加载到内存。并且只有第一次使用这个类时才会加载。 一个类的信息主要包括以下部分: 类变量(静态变量); 类初始化代码: – 定义静态变量时的赋值语句 – 静态...
  • 主要介绍了Java中类加载过程全面解析,具有一定参考价值,需要的朋友可以了解下。
  • Java类加载过程

    万次阅读 多人点赞 2018-09-14 19:09:22
    JVM和 当我们调用 Java 命令运行某个 Java 程序时,该命令将会启动一条 Java 虚拟机进程,不管该 Java 程序有多么复杂,该程序启动了多少个线程,它们都处于该 Java 虚拟机进程里。同一个 JVM 的所有线程、所有...
  • 主要介绍了Java类加载基本过程详细介绍的相关资料,需要的朋友可以参考下

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 893,377
精华内容 357,350
关键字:

类的加载过程