类加载机制
jvm把描述类的数据从class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被jvm直接使用的java类型。在java中,类型的加载、连接和初始化都是在程序运行期间完成的。
类的生命周期
加载、验证、准备、解析、初始化、使用、卸载。
如果类没有初始化过,以下5种情况需要立即对类进行初始化:
1、遇到new(使用new创建对象)、getstatic(获取类的静态变量)、putstatic(设置类的静态变量)、invokestatic(调用类的静态方法)这4条字节码指令时。
2、使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,必须初始化。
3、初始化一个类的时候,如果其父类还没有初始化,需要先触发父类的初始化。
4、jvm启动的时候,用户需要指定一个要执行的主类(包含main方法的那个类),虚拟机会先初始化这个主类。
5、如果一个java.lang.invoke.MethodHandler实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄对应的类没有进行过初始化,则需要先触发其初始化。
来看一些示例:
ex1:
package com.ty.classLoder; public class SuperClass { public static int value = 123; static { System.out.println("SuperClass init"); } }
package com.ty.classLoder; public class SubClass extends SuperClass { static { System.out.println("SubClass init"); } }
package com.ty.classLoder; public class Test { public static void main(String[] args) { System.out.println(SubClass.value); } }
运行结果:
SuperClass init
123
对于静态字段,只有直接定义这个字段的类才会被初始化,子类引用父类中定义的静态字段,只会触发父类的初始化,而不会触发子类的初始化。
ex2:
package com.ty.classLoder; public class SuperClass { public static int value = 123; static { System.out.println("SuperClass init"); } }
package com.ty.classLoder; public class SubClass extends SuperClass { static { System.out.println("SubClass init"); } }
package com.ty.classLoder; public class Test { public static void main(String[] args) { SuperClass[] arr = new SuperClass[10]; } }
运行结果:啥也没输出
通过数组定义来引用类,不会触发此类的初始化。
ex3:
package com.ty.classLoder; public class ConstClass { public static final String HEELO_WORLD = "hello world"; static { System.out.println("ConstClass init"); } }
package com.ty.classLoder; public class Test { public static void main(String[] args) { System.out.println(ConstClass.HEELO_WORLD);; } }
运行结果:
hello world
因为在编译阶段常量传播优化,已经将ConstClass的常量放入到了字符串常量池(因为是字面量直接赋值),并且将此常量存储到了Test类的常量池中,Test类访问此常量即是对自身常量池的引用了。
类加载的过程
1、加载
a、根据一个类的全限定名(com.ty.classLoder.Test)来获取定义此类的二进制字节流;-----可以从zip包、网络中获取;也可以计算生成,例如动态代理技术,在java.lang.reflect.Proxy中,就是使用ProxyGenerator.generateProxyClass来为特定接口生成形式为$Proxy的代理类的二进制字节流;由jsp文件生成等等
b、将字节流所代表的静态存储结果转化为方法区运行时的数据结果;
c、在内存中生成一个代表这个类的java.lang.class对象,作为方法区这个类的各种数据的访问入口。
2、验证
是连接阶段的第一步,为了确保class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
a、文件格式验证-----在文件格式验证通过之后,字节流进入内存的方法区中进行存储,后面的验证都是基于方法区的存储结构进行的。
b、元数据验证
这个类是否有父类、这个类的父类是否继承了不允许被继承的类(final修饰的类)、类不是抽象类,是否实现了父类或接口之中要求实现的所有方法、类中字段是否与父类产生矛盾(例如覆盖父类的final字段等等)
c、字节码验证
通过数据流和控制流分析,确定程序语义是合法的。
d、符号引用验证
对类自身以外(常量池中的各种符号引用)的信息进行匹配性校验-----例如符号引用通过字符串描述的全限定名是否能找到对应的类
3、准备
为类变量分配内存并设置类变量(static修饰的变量)初始值(数据类型的零值,例如false、0、null这些)的阶段,这些变量所使用的内存都将在方法区中进行分配。
4、解析
jvm将常量池内的符号引用替换为直接引用的过程。
5、初始化
初始化阶段就是执行类构造器<clinit>()方法的过程。
类加载器
1、双亲委派模型
双亲委派:如果一个类加载器收到了加载某个类的请求,则该类加载器并不会去加载该类,而是把这个请求委派给父类加载器,每一个层次的类加载器都是如此,因此所有的类加载请求最终都会传送到顶端的启动类加载器;只有当父类加载器在其搜索范围内无法找到所需的类,并将该结果反馈给子类加载器,子类加载器会尝试去自己加载。
2、jvm中的类加载器
jvm提供了三种系统加载器:
启动类加载器(Bootstrap ClassLoader):C++实现,在java里无法获取,负责加载/lib下的类。
扩展类加载器(Extension ClassLoader): Java实现,可以在java里获取,负责加载/lib/ext下的类。
系统类加载器/应用程序类加载器(Application ClassLoader):是与我们接触对多的类加载器,我们写的代码默认就是由它来加载,ClassLoader.getSystemClassLoader返回的就是它。
jvm中的类加载器:
3、破坏双亲委派模型
之前在Tomcat架构解析(五)-----Tomcat的类加载机制中介绍过tomcat的类加载机制,它就是典型的破坏了双亲委派模型。
原文:https://www.cnblogs.com/alimayun/p/10753599.html