1 类加载的三个过程
1.1 加载阶段
类加载阶段就是由类加载器负责根据一个类的全限定名来读取此类的二进制字节流到JVM内部,并存储在运行时内存区的方法区,然后将其转换为一个与目标类型对应的java.lang.Class对象实例(Java虚拟机规范并没有明确要求一定要存储在堆区中,只是hotspot选择将Class对应哪个存储在方法区中),这个Class对象在日后就会作为方法区中该类的各种数据的访问入口。加载阶段由类加载器负责
1.2 链接阶段
链接阶段要做的是将加载到JVM中的二进制字节流的类数据信息合并到JVM的运行时状态中,经由验证、准备和解析三个阶段。
1.2.1 验证
1)、验证类数据信息是否符合JVM规范,是否是一个有效的字节码文件,验证内容涵盖了类数据信息的格式验证、语义分析、操作验证等。
格式验证:验证是否符合class文件规范
语义验证:检查一个被标记为final的类型是否包含子类;检查一个类中的final方法视频被子类进行重写;确保父类和子类之间没有不兼容的一些方法声明(比如方法签名相同,但方法的返回值不同)
操作验证:在操作数栈中的数据必须进行正确的操作,对常量池中的各种符号引用执行验证(通常在解析阶段执行,检查是否通过富豪引用中描述的全限定名定位到指定类型上,以及类成员信息的访问修饰符是否允许访问等)
1.2.2 准备
准备阶段的任务是给静态变量分配内存和设置初始值(零值),final static变量(实际上就是常量)会直接赋代码中实际要赋的值,这些变量都将在方法区中分配内存。
例如:
static int age=18 准备阶段会将 age=0,赋值为18的操作是在初始化阶段完成
final static int age=18 准备阶段会将 age=18
final、static、static final修饰的字段赋值的区别 :
- static修饰的字段在加载过程中准备阶段被初始化,但是这个阶段只会赋值一个默认的值(0或者null而并非定义变量设置的值)初始化阶段在类构造器中才会赋值为变量定义的值。
- final修饰的字段在运行时被初始化,可以直接赋值,也可以在实例构造器中赋值,赋值后不可修改。
- static final修饰的字段在javac编译时生成ConstantValue属性,在类加载的准备阶段直接把constantValue的值赋给该字段。可以理解为在编译期即把结果放入了常量池中
- 只有同时被final和static修饰的字段才有ConstantValue属性,且限于基本类型和String。编译时Javac将会为该常量生成ConstantValue属性,在类加载的准备阶段虚拟机便会根据ConstantValue为常量设置相应的值,如果该变量没有被final修饰,或者并非基本类型及字符串,则选择在类构造器中进行初始化。
注意:
这里不包括对常量的操作,常量在编译阶段就已经初始化了;同理也不包括对实例变量的操作,实例变量将会在对象实例化时随着对象一起分配在Java堆中。
1.2.3 解析
主要将常量池中的符号引用替换为直接引用的过程。
符号引用就是一组符号来描述目标,可以是任何字面量,而直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。有类或接口的解析,字段解析,类方法解析,接口方法解析。
通俗地说就是把占位符换成真正的地址(内存偏移等)。
例如:
String a="test";
就是将a指向test在常量池中的地址
1.3 初始化
类的初始化是类加载过程的最后一步,在整个类加载过程,除了在加载阶段可由自定义类加载器参与外,其余动作完全由虚拟机主导和控制。到了初始化阶段,才真正开始执行类中定义的Java代码。
初始化阶段是执行类构造器<clinit>()方法的过程。<clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的。
1.3.1 类加载过程中成员变量的初始化顺序
1.父类静态成员和静态初始化快
2.子类静态成员和静态初始化块
3. 父类的实例成员和实例初始化块
4.执行父类的构造方法
5.子类实例成员和实例初始化块
6.执行子类的构造方法
简记为:父静态->子静态->父实例变量->父构造–>子实例变量->子构造
2 NoClassDefFoundError和ClassNOtFoundException两种异常
什么时候会抛出ClassNotFoundException异常呢?
ClassNotFoundException发生在装入阶段,加载时从外存储器找不到需要的class就出现ClassNotFoundException。
当程序试图使用class类中的forname方法、classloader类中的findsystemclass方法,classloader类中loadclass方法通过字符串名的形式加载此类时,会抛出该异常。
try { Class c= App.class.getClassLoader().loadClass("aaa.ccc"); } catch (ClassNotFoundException e) { e.printStackTrace(); }
什么时候会抛出NoClassDefFoundError异常呢?
当执行的类已经编译,但是运行时从内存找不到需要的class就出现NoClassDefFoundError。
也就是说如果加载了一个类B,并且类B初始化加载出错,在类A中再次调用类B时,就会出现这个错误 。
public class App { public static void main(String args[]) { try { //will throw ExceptionInInitializerError new Test(); } catch (Throwable t) { t.printStackTrace(); } try { //will throw NoClassDefFoundError new Test(); } catch (Throwable t) { t.printStackTrace(); } } } class Test { public Test(){ } private static String init() { throw new RuntimeException("init error"); } static { init(); } }
java.lang.ExceptionInInitializerError at org.example.App.main(App.java:13) Caused by: java.lang.RuntimeException: init error at org.example.Test.init(App.java:30) at org.example.Test.<clinit>(App.java:33) ... 1 more java.lang.NoClassDefFoundError: Could not initialize class org.example.Test at org.example.App.main(App.java:19)