类加载过程
加载
- 加载时机
jvm创建对象前,会先检查类是否加载,寻找类对应的class对象,若加载好,则为你的对象分配内存。初始化也就是代码:new Object()。
类加载器并不需要等到某个类被“首次主动使用”时再加载它,JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError错误)如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误。
当执行类名.class时,JVM会先检查Class对象是否装入内存,如果没有装入内存,则将Class对象装入内存,然后返回Class对象,如果装入内存,则直接返回Class对象。在加载Class对象后,不会对Class对象进行初始化。(静态代码不执行和初始化)
当执行Class.forName()时,JVM也会先检查Class对象是否装入内存,如果没有装入内存,则将Class对象装入内存,然后返回Class对象,如果装入内存,则直接返回Class对象。在加载Class对象后,会对类进行初始化,即执行类的静态代码块。forName()方法中的参数是类名字符串,类名字符串 = 包名 + 类名。Class.forName()的一个很常见的用法是在加载数据库驱动的时候。
- 加载过程
- 类加载器通过类的全路径限定名读取类的二进制字节流。(到文件系统对应目录下找)
- 将二进制字节流代表的类结构转化到运行时数据区的方法区中。
- 在堆中生成代表这个类的java.lang.Class实例(类对象)。类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。
- 加载策略(双亲委派模型)
如果一个类加载器收到了类加载的请求,它首先不会自己去加载这个类,而是把这个请求委派给父类加载器去完成,每一层的类加载器都是如此,这样所有的加载请求都会被传送到顶层的启动类加载器中,只有当父加载器无法完成加载请求(它的搜索范围中没找到所需的类)时,子加载器才会尝试去加载类。下面举一个大家都知道的例子说明为什么要使用双亲委派模型:
黑客自定义一个java.lang.String类,该String类具有系统的String类一样的功能,只是在某个函数稍作修改。比如equals函数,这个函数经常使用,如果在这个函数中,黑客加入一些“病毒代码”。并且通过自定义类加载器加入到JVM中。此时,如果没有双亲委派模型,那么JVM就可能误以为黑客自定义的java.lang.String类是系统的String类,导致“病毒代码”被执行。
而有了双亲委派模型,黑客自定义的java.lang.String类永远都不会被加载进内存。因为首先是最顶端的类加载器加载系统的java.lang.String类,最终自定义的类加载器无法加载java.lang.String类。
本质原理为系统内置类不允许复写,先在系统内置的类中寻找。
连接
- 验证
确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
- 准备
在方法区中给类变量(static修饰)分配内存。然后初始化其值**(对应数据类型的默认值)****,如果类变量是常量<被final修饰>,则直接赋值为该常量值,否则为java类型的默认零值。**实例变量会在对象实例化时随着对象一块分配在Java堆中。
- 解析
class文件常量池主要用于存放两大类常量:字面量(Literal)和符号引用量(Symbolic References)。
字面量: 是指直接出现在Java源码中的值,包括整数、浮点数、单引号中的单个字符 、双引号中的字符串,以及保留字true、false和null。例如:1 1.0 '1' "one" true false null。
符号引用(Symbolic References**)**:符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能够无歧义的定位到目标即可。
例如,在Class文件中它以CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info等类型的常量出现。对应以下三种类型的常量:
- 类和接口和全限定名:例如对于String这个类,它的全限定名就是java/lang/String。
- 字段名称和描述符:所谓字段就是类或者接口中声明的变量,包括类级别变量(static)和实例级的变量。
- 方法名称和描述符。所谓描述符就相当于方法的参数类型+返回值类型。
**java .class文件组成:**字面量,符号引用,java语言。
在Java中,一个java类将会编译成一个.class文件。在编译时,java类并不知道所引用的类的实际地址,因此只能使用符号引用来代替。比如org.simple.People类引用了org.simple.Language类,在编译时People类并不知道Language类的实际内存地址,因此只能使用符号org.simple.Language(假设是这个,当然实际中是由类似于CONSTANT_Class_info的常量来表示的)来表示Language类的地址。
符号引用就是一个字符串,只要我们在代码中引用了一个非字面量的东西,不管它是变量还是常量,它都只是由一个字符串定义的符号,这个字符串存在常量池里,类加载的时候第一次加载到这个符号时,就会将这个符号引用(字符串)解析成直接引用(指针)。
初始化
对类变量(static)进行初始化。静态代码块执行。