JVM类加载与双亲委派
1 类加载
如果是第一次使用某个类,jvm就会去加载这个类。加载一个类分为加载、连接、初始化3个步骤。类的加载也叫类的初始化。
使用类可以是:
1).new User();
2).User.TYPE;
3).User.changePassword(user, password);
4).Class.forName("com.*.User");
5).new UserExtends(); (User类的子类)
6).User.main(); (含有main方法的类, 并且这个类是程序的入口)
1.1 加载
1.加载时jvm首先会通过类的全限定名获取类的二进制字节流
2.将字节流转化为方法区中的运行时数据结构
3.在堆上创建一个java.lang.Class实例,用来封装类在方法区内的数据结构,并向外提供了访问方法区内数据结构的接口
1.2 验证
类的连接分为三个步骤, 验证是连接的第一个步骤, 验证包括对以下四个方面的验证:
1.类文件结构验证:验证字节流是否符合Class文件规范,是否能被当前版本的虚拟机处理
2.元数据验证:对字节码描述的信息进行语义分析,保证其符合java语言规范要求
3.字节码验证:通过数据流和控制流分析,确定程序语义是合法的,符合逻辑的。这里主要对方法体进行校验
4.符号引用验证:对类自身以外的信息,也就是常量池中的各种符号引用,进行匹配校验
1.3 准备
准备阶段主要是给类的静态变量分配内存。
1.4 解析
在解析阶段会把常量池中的符号引用转换成直接引用
1.5 初始化
在初始化阶段时,jvm才会真正开始执行类中定义的java代码
1.初始化时首先执行类的构造器<clinit>()方法。类构造器<clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块中的语句合并产生的。
2.如果一个类有父类还没有初始化,则先初始化父类。初始化一个接口或类时, 不会初始化父接口。只有当调用一个接口的变量或方法时才会初始化这个接口
2 类加载器
jvm是通过类加载器来加载一个类的,类加载器也是jvm的一部分。 在jvm中一般有四种类加载器:启动类加载器(BoorstrapClassLoader)、扩展类加载器(ExtensionClassLoader)、应用程序类加载器(AppClassLoader)、用户自定义加载器(UserClassLoader)。
其中启动类加载器是扩展类加载器的父类, 扩展类加载器又是应用程序类加载器,应用程序类加载器又是用户自定义加载器。
2.1 启动类加载器
启动类加载器用来加载java的核心类,没有继承自java.lang.ClassLoader,也不是用java代码来实现的。它主要负责加载java主目录中lib文件夹下jar包中的class。由于启动类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。
2.2 扩展类加载器
扩展类加载器负责加载jre的扩展目录,lib/ext或者由java.ext.dirs系统属性指定的目录中的jar包的class。
2.3 应用程序类加载器
应用程序类加载器负责在jvm启动时加载来自-classpath选项或者java.class.path系统属性的jar包或者class。
2.4 用户自定义类加载器
用户自定义类加载器是我们自己实现的加载器,一般继承java.lang.ClassLoader
3 双亲委派机制
3.1 为什么要用双亲委派机制
jvm中加载一个类时通常会使用双亲委派机制。通过上面的加载这一步骤我们知道,加载器来加载类时会通过一个包的全限定名来获取描述此类的二进制字节流。也就是说,虚拟机是根据类的全限定名来加载一个类的,那么如果同时存在两个一样的全限定名的类的话,加载器要去加载哪个类呢?这就是双亲委派可以解决的问题。另外,判断两个类是否相等,除了它们要来源于同一个class文件外,还要被同一个加载器加载。
3.2 双亲委派机制的思路
当一个类加载器要加载类时, 这个加载器首先自己不会去加载这个类,而是把加载这个类的工作委托给自己的父加载器。一直向上委托到启动类加载器,如果父加载器找不到这个类,则会一直再向下委托给子加载器。如果子加载器可以找到就加载,如果找不到就抛出ClassNotFoundException。
3.2 双亲委派机制的好处
1.防止系统类被篡改而引发的安全问题
2.减少重复加载
3.3 双亲委派机制破坏
双亲委派机制固然好, 但是也有无法解决的问题。比如父加载器无法加载子加载器加载的类, 一个很常见的例子就是jdbc驱动:JDBC的Driver接口定义在jdk中,但是这个接口实现类却是各个数据库厂商自己实现的。Driver接口是由启动类加载器加载的,启动类加载器当然无法加载到厂商自己实现的jdbc驱动实现。所以这就有了双亲委派机制的破坏。可以通过Thread.currentThread().getContextClassLoader()和Thread.currentThread().getContextClassLoader(ClassLoader classLoader)来获取和设置当前线程的上下文加载器。这就实现了父加载器来加载子加载器要加载的类。