ClassLoader-类的加载顺序

  • ClassNotFoundException:
    此问题从java.lang.ClassNotFoundException由来,对此异常,它涉及到了java技术体系中的类加载。java的类加载机制是java技术体系中的核心问题。虽说我们和它直接打交道不多,但对其背后的机理有一定的理解有助于我们排查程序中出现的类加载失败等技术问题。

  • 类的加载过程:
    一个java文件从加载到被卸载整个生命过程,总共要经历5个阶段,jvm将类加载过程分为:加载->连接(验证+准备+解析)->初始化(使用前准备)->使用->卸载

    1. 加载:
      首先通过一个类的全限定名来获取此类的二进制字节流;其次将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构;最后在java堆中生成一个代表这个类的class对象,作为方法区这些数据的访问入口。总的来说就是查找并加载类的二进制数据。
    2. 连接:
      验证:确保被加载类的正确性;
      准备:为类的静态变量分配内存,并将其初始化为默认值;
      解析:把类中的符号引用转换为直接引用;
    3. 为类的静态变量赋予正确的初始值
  • 类的初始化

    1. 类什么时候才被初始化?
      1. 创建某个类的实例,也就是new一个对象
      2. 访问某个类的静态变量,或者对该静态变量赋值
      3. 调用类的静态方法
      4. 反射
      5. 初始化一个类的子类(会先初始化该子类的父类)
      6. JVM启动时标明的启动类,即文件名和类名相同的那个类
    2. 类的初始化顺序:
      1. 如果该类还没有被加载和连接则先被加载和连接
      2. 假如这个类存在直接父类,并且这个类还没有被初始化,(注意在类的加载器中,类只能被初始化一次),那就初始化直接的父类(不适用于接口)
      3. 假如类中存在初始化语句(如static变量和static块),那就依次执行这些初始化语句。
      4. 总的来说,初始化顺序依次是:(静态变量、静态初始化块)->(变量、初始化块)->构造器; 如果有父类,则顺序是:父类static方法->子类static方法->父类构造方法->子类构造方法
  • 类的加载
    类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个这个类的 java.lang.Class对象,用来封装在方法区类的对象。 如:
    在这里插入图片描述
    在这里插入图片描述
    类加载的最终产品是位于堆区中的Class对象。Class对象封装了在方法区内的数据结构,并且向java程序员提供了访问方法区内的数据结构的接口。加载类的方式有以下几种:

  1. 从本地系统直接加载
  2. 通过网络下载.class文件
  3. 从zip,jar等归档文件中加载.class文件
  4. 从专有数据库中提前.class文件
  5. 将java源文件动态编译为.class文件(server)
  • 加载器:
    jvm的类加载是通过ClassLoader及其子类来完成的,类的层次关系和加载顺序可以由下图来描述:
    在这里插入图片描述

    1. 加载器:
      1. BootstrapClassLoader(启动类加载器)
        负责加载$JAVA_HOME中jre/lib/rt.jar里所有的class,加载System.getProperty(“sun.boot.class.path”)所指定的路径或jar。

      2. ExtensionClassLoader(标准扩展类加载器)
        负责加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目录下的jar包。载System.getProperty(“java.ext.dirs”)所指定的路径或jar。

      3. AppClassLoader(系统类加载器)
        负责记载classpath中指定的jar及目录中class

      4. CustomClassLoader(自定义类加载器)
        属于应用程序自身需要定义的ClassLoader,如tomcat,jboss都会根据j2ee规范自行实现。

    2. 类加载器的顺序
      1. 加载过程会先检查类是否已被加载,检查顺序是自底而上,从Custom ClassLoader到Bootstrap ClassLoader逐层检查,只要某个ClassLoader已加载就视为已加载此类,保证此类被所有ClassLoader加载一次。而加载的顺序是自顶向下。
      2. 在加载类时,每个类加载器会加载任务给其父类,父类找不到再由自己加载。
      3. BootstrapLoader(启动类加载器)是最顶级的加载器,其父类加载器为null
  • 继承的加载顺序
    下面的例子就是用static块来测试类的加载顺序。static块会在首次加载时执行

class A{    
  static{        
	System.out.println("A static");    
  }
}
class B extends A{    
 static{        
  System.out.println("B static");    
 }
}
class C extends B{
  private static D d = new D();    
  static{        
    System.out.println("C static");    
  }
}
class D{    
  static{       
   System.out.println("D static");   
 }
}
 public class ExtendTest {    
   public static void main(String[] args) {
      C c = new C();    
  }
  }

在上面的例子中,类C继承B,B继承A,而C有依赖于D。因此当创建C的时候,会自动加载C继承的B和依赖的D,然后B又会加载继承的A。只有A加载完,才能顺利的加载B;BD加载完,才能加载C。这就是类的加载顺序了。

结果为:
A static
B static
D static
C static
  • 所有的变量都初始化完,才会执行构造函数
    在类的加载过程中,只有类的内部变量创建完,才会执行这个类的构造方法。
class A2{    
	B2 b2 = new B2();    
	static{        
	  System.out.println("A static");    
	}    
	public A2(){        
	 System.out.println("A2()");
	}
}
class B2{    
	C2 c2 = new C2();    
	D2 d2 = new D2();    
	static{        
	  System.out.println("B static");    
	}    
	public B2() {
	  System.out.println("B2()");    
	}
}
class C2{    
	static{        
	  System.out.println("C static");    
	}    
	public C2() {        
	  System.out.println("C2()");    
	}
}
class D2{    
	static{        
	  System.out.println("D static");    
	}    
	public D2() {        
	  System.out.println("D2()");    
	}
}
public class VarTest {    
	public static void main(String[] args) {        
	A2 a2 = new A2();    
}
}

在上面的例子中,A2里面有B2变量,B2则有C2D2变量。因此类的加载还是先读取到哪个,就执行相应的静态块。
当依赖的对象都定义完,才会执行构造方法:

A static
B static
C static
C2()
D static
D2()
B2()
A2()
  • 静态成员与普通成员类的加载区别
    在类的加载过程中,静态成员类的对象,会优先加载;而普通成员类的对象则是使用的时候才回去加载。
class A3{    
	B3 b3 = new B3();    
	static C3 c4 = new C3();    
	static{        
	 System.out.println("A3");    
	}
class B3{
    static{        
      System.out.println("B3");    
    }
}
class C3{    
	static{        
	  System.out.println("C3");    
	}
}
public class StaticTest {    
	public static void main(String[] args) {        
	A3 a3 = new A3();    
	}
}
}

输出:

C3
A3
B3

总结:

第一点,所有的类都会优先加载基类
第二点,静态成员的初始化优先
第三点,成员初始化后,才会执行构造方法
第四点,静态成员的初始化与静态块的执行,发生在类加载的时候。
第四点,类对象的创建以及静态块的访问,都会触发类的加载。

类构造方法的顺序:

class A{    
	public A() {        
	  System.out.println("A");    
	}
}
class B extends A{    
	public B() {        
	  System.out.println("B");    
	}
}
class C extends B {    
	private D d1 = new D("d1");    
	private D d2 = new D("d2");    
	public C() {        
	  System.out.println("C");    
	}
}
class D {    
	public D(String str) {        
	  System.out.println("D "+str);    
	}
}
public class ExtendTest {    
	public static void main(String[] args) {        
	  C c = new C();    
	}
}

输出:

A
B
D d1
D d2
C

结论:
首先调用基类的构造方法,其次调用成员的构造方法,最后调用自己的构造方法

  • 什么时候需要类加载

    • 第一次使用类信息时加载
    • 类的加载原则:延迟加载,能不加载就不加载
  • 触发类加载的几种情况:

    1. 调用静态成员时,会加载静态成员真正所在的类及其父类,通过子类调用父类的静态成员时,只会加载父类而不会加载子类
    2. 第一次new对象时,加载(第二次再new同一个类时,不需要再加载)
    3. 加载子类时会先加载父类(覆盖父类方法时所抛出的异常不能超过父类定义的范围 注:如果静态属性有final修饰时,则不会加载,当成常量使用;eg:public static final int a=123; 但如果上边的等式右值改为表达式(且该表达式在编译时不能确定其值)时则会加载类 eg:public static final int a = math.PI 如果访问的是类的公开常量,那么如果编译器在编译时能确定这个常量的值,就不会被加载; 如果编译时不能确定其值的话,则运行时加载。)
  • 类加载顺序:

    1. 加载静态成员、代码块(Object的最先加载);再依次加载到本类的静态成员。同一个类的静态成员、代码块,按代码的顺序加载。
      如果其间调用静态方法,则调用时会首先运行静态方法,再继续加载。同一个类里调用静态方法时,可以不理会代码的顺序。
    2. 加载非静态成员/代码块(实例块再创建对象时才会被加载,而静态成员在不创建对象时可以加载)
      先递归的加载父类的非静态成员/代码块;在依次加载到本类的非静态成员。
      同一个类里的非静态成员/代码块,按写代码的顺序加载,同一个类里调用方法时,可以不理会写代码的顺序。
      但调用属性时,必须主席加载顺序。一半编译不通过,如果能在加载前调用,值为默认初始值(如 null 或0)
    3. 调用构造方法
      先递归的调用父类构造方法即上溯下行;默认调用父类空参的,也可在第一行写明调用父类某个参数的。
      再一次到本类的构造方法:构造方法内,也可在首行写明调用本类某个其他的构造方法。
      需注意得是:如果加载时遇到override成员时,可看做是所需创建的类型赋值给当前类型。
      其调用按多态法则:只有非静态方法有多态;而静态方法,静态属性,非静态属性都没有多态。
      假设子类override父类所有的成员,包括静态成员,非静态属性和非静态方法。
      由于构造子类时会先构造父类;而构造父类时其所用的静态成员和非静态属性是父类的,但非静态方法却是父类的;
      由于构造父类时,子类并未加载;如果此时所调用的非静态方法里有成员,则这个成员是子类的;
      由于构造父类时,子类并未加载;如果此时所调用的非静态方法中有成员,则这个成员是子类的,且非静态属性是默认初始值的。
  • Java程序在执行过程中,类,对象及其成员加载,初始化的顺序如下:

    1. 首先加载要创建对象的类及其直接或间接父类
    2. 再类被加载的同时会将静态成员进行加载,主要包括静态成员变量的初始化,静态语句块的执行,在加载时按代码的先后顺序进行。
    3. 需要的类加载完成后,开始创建对象,首先会加载非静态的成员,主要包括非静态成员变量的初始化,非静态语句块的执行,在加载时按代码的先后顺序进行。
    4. 最后执行构造器,构造器执行完毕,对象生成。
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: ClassLoaderJava中用于加载Java的对象,它将Java文件加载到内存中,并在Java虚拟机中生成对应的java.lang.Class对象。 Java虚拟机有三种ClassLoader: 1. 启动类加载器(Bootstrap ClassLoader):用于加载Java运行时环境所需要的,它加载是由C++编写的,并由虚拟机自身启动。 2. 扩展类加载器(Extension ClassLoader):用于加载Java的扩展库,默认加载JAVA_HOME/jre/lib/ext目录下的。 3. 应用程序类加载器(Application ClassLoader):用于加载用户路径(Classpath)上的,是Java应用程序的类加载器。 在Java中,每个都会由对应的ClassLoader加载,并且每个ClassLoader都有一个双亲委派机制来保证Java加载安全。在加载某个时,如果当前ClassLoader无法加载,就会委派给它的父ClassLoader去尝试加载。如果父ClassLoader也无法加载,就会继续向上委派,直到启动类加载器(顶层父加载器)。如果启动类加载器也无法加载,就会抛出ClassNotFoundException异常。 这样做的好处是可以确保Java的安 ### 回答2: ClassLoader类加载器)是Java虚拟机(JVM)的重要组成部分,它负责将的字节码文件加载到内存中并生成对应的Class对象。ClassLoader加载机制是指ClassLoader加载的过程中的一种规则或策略。 ClassLoader采用的是双亲委派模型,即在类加载的过程中先委派给父类加载器,只有在父类加载器无法加载时才由子类加载器进行加载。这种机制保证了的唯一性和安全性。 ClassLoader通过三个阶段加载加载、连接和初始化。首先,加载阶段将的二进制文件读入内存,并创建一个Class对象表示该,这个过程是ClassLoader的主要作用之一。然后,在连接阶段,ClassLoader将对进行验证、准备和解析。验证阶段确保二进制文件合法、安全,准备阶段为静态变量分配内存并设置默认值,解析阶段将符号引用转换为直接引用。最后,在初始化阶段,ClassLoader进行初始化,执行静态变量赋值、静态代码块等。 ClassLoader采用的是双亲委派模型,它的加载顺序是先由父类加载器尝试加载,只有当父类加载器无法找到时,才由子类加载器进行加载。这样可以有效地避免重复加载和冲突,保证了的唯一性和安全性。双亲委派模型按照树形结构组织ClassLoader,根ClassLoader位于ClassLoader的最顶层,它没有父ClassLoaderClassLoader根据加载的要求,逐级向上委派,直到根ClassLoader。如果根ClassLoader仍然无法加载,则会由最底层的ClassLoader尝试加载ClassLoader还支持动态加载,这通过ClassLoader的findClass方法实现。当一个由特定的ClassLoader加载时,它会首先尝试从内存中查找,如果找不到,会根据查找路径去加载的字节码,然后通过defineClass方法将字节码转换为Class对象。 总结来说,ClassLoader加载机制是通过双亲委派模型,按照一定的顺序和策略将类加载到内存中并生成对应的Class对象。这种机制保证了的唯一性和安全性,同时支持动态加载。 ### 回答3: ClassLoader类加载器)是Java中负责将文件加载到JVM中并解析的机制。ClassLoader加载机制是Java实现动态加载和运行时加载的核心机制之一,它采用了双亲委派模型。 在双亲委派模型中,类加载器根据特定的规则进行加载,首先尝试使用父类加载加载,如果父类加载器找不到该,则交给子类加载器进行加载。这样一层一层的委派直到达到根加载器。如果最终都无法加载,则抛出ClassNotFoundException异常。 ClassLoader加载机制具有以下特点: 1. 双亲委派模型:ClassLoader按照层级关系负责加载,保证了的唯一性和安全性。可以避免重复加载冲突的问题。 2. 优先加载系统ClassLoader首先尝试加载系统,即JRE核心库,然后再加载自定义。这样保证了系统的优先级和稳定性。 3. 动态加载ClassLoader可以根据需要在运行时动态加载。它可以通过自定义类加载器来加载外部的文件,实现动态扩展和插件化的功能。 4. 热部署:ClassLoader可以在应用程序运行时替换文件,实现热部署的功能。这对于需频繁修改代码的开发和调试非常有用。 总结来说,ClassLoader加载机制是通过双亲委派模型实现加载和解析,保证的唯一性和安全性。它支持动态加载和热部署,为Java提供了高度灵活性和可扩展性。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值