1.类的加载、连接、初始化
程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过加载、连接、初始化三个步骤对该类就行初始化。
加载指将类的class文件读入内存,并为之创建一个java.lang.Class对象。类的加载由类加载器完成,加载器通常由JVM提供,称为系统类加载器,也可以通过继承ClassLoader创建自己的类加载器。
当类被加载之后,系统为之生成了一个对应的Class对象,接着就会进入连接夹断,连接阶段负责把类的二进制数据合并到jre中。连接又可以分为三个阶段:验证(用于检验被加载的类是否有正确的内部结构,并和其他类协调一致)、准备(类准备阶段则负责为类的静态Field分配内存,并设置默认初始值)、解析(将类的二进制数据中的符号引用替换成直接引用)。
初始化阶段,虚拟机负责对类进行初始化,主要对静态Field进行初始化。在Java类中对静态Field指定初始值有两种方式:声明静态Field时指定初始值、使用静态初始化块为静态Field指定初始值。
2.类初始化的时机
当Java程序首次通过下面几种方式来使用某个类或接口时,系统就会初始化该类或接口:
创建类的实例(使用new操作符、通过反射创建、通过反序列化创建)
调用某个类的静态方法
访问某个类或接口的静态Field,或为该静态Field赋值
使用反射方式来强制创建某个类或接口对应的Class对象
初始化某个类的子类
直接使用java.exe命令来运行某个主类。
有一种情况不会导致类初始化:final型的静态Field,如果该Field的值在编译时就可以确定下来,那么这个Field相当于“宏变量”。Java编译器会在编译时直接把这个Field出现的地方替换成它的值。
3.类加载器
类加载器负责将.class文件加载到内存中,并为之生成对应的java.lang.Class对象。一旦一个类被载入了JVM中,同一个类就不会被再次载入。
在Java中,确定类唯一的方法就是使用类的全限定类名(包名+类名)作为标识;但在JVM中,一个类用其全限定类名和其类加载器作为其唯一标识。
当JVM启动时,会形成由3个类加载器组成的初始类加载器层次结构。
Bootstrap ClassLoader(根类加载器):负责加载Java的核心类,其本身并不是java.lang.ClassLoader的子类,而是由JVM自身实现的。
Extension ClassLoader(扩展类加载器):负责加载JRE的扩展目录中JAR包中的类,通过这种方式为Java扩展核心类以外的功能。
System ClassLoader(系统类加载器):负责在JVM启动时加载来自Java命令的-classpath选项、java.class.path系统属性,或者CLASSPATH环境变量所指定的JAR包和类路径。程序通过ClassLoader的静态方法getSystemClassLoader()来获取系统类加载器。
实际上,扩展类加载器的父类是根类加载器,只是根类加载器并不是Java实现的。系统类加载器是AppClassLoader的实例,扩展类加载器是ExtClassLoader的实例,这两个类都是URLClassLoader的实例。
JVM中除了跟加载器之外的所有类加载器都是ClassLoader子类的实例。
类加载器加载Class步骤
4.类加载机制
JVM的类加载机制主要有如下3种。
全盘委托:就是当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示的使用另外一个类加载器来载入。
父类委托:先让父类加载器视图加载该Class,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类(此处的父子指的是类加载器实例之间的关系)。
缓存机制:保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区中搜寻该Class,只有当缓存区中不存在该Class对象时,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存中。这就是为什么修改了Class后必须重启JVM,修改才生效的原因。