文章目录
1 类加载子系统

1.1 类加载子系统的作用
类加载子系统负责从文件或网络中加载class文件,class文件在文件开头有特定的文件标识,即cafe babe

类加载子系统(ClassLoader)只负责class文件的加载,至于它是否可以运行,则由执行引擎(Executing Engine)决定,加载的类信息被存放到称为方法区的内存空间
整体的加载过程:

class文件存放在本地硬盘上,可以理解为某个产品的模板,这个模板需要被加载到JVM中,class文件被加载到JVM后,被称为元数据模板(该类唯一的Class对象) 放在方法区,根据元数据模板就可以生产实例对象,在class文件->JVM->元数据模板的过程中,就需要类加载器作为一个快递员的角色
1.2 Loading 加载阶段
加载:
1,通过一个类的全限定名获取定义此类的二进制字节流
2,将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
3,在内存中生成一个代表这个类的java.lang.Class对象
作为方法区这个类的各种数据的访问入口
加载主要是由类加载器完成的,类加载器负责加载所有的类,其为所有被载入内存中的类生成一个java.lang.Class实例对象
1.2.1 类加载器分类

程序中最常见的类加载器始终只有3种:
引导类加载器(Bootstrap Class Loader)
1,是由C/C++实现的,嵌套在JVM内部
2,用来加载核心类库中的类(String,Integer...) 只加载包名开头为
java,javax,sun等开头的类
3,没有父加载器,加载扩展类加载器和系统类加载器
并指定为它们的父类加载器
4,获取不到引导类加载器对象,因为是C/C++编写的
只要类加载器是null,那么就是引导类加载器
扩展类加载器(Extension Class Loader)
1,由Java编写,派生于ClassLoader类
2,父加载器为引导类加载器
3,从java.ext.dirs系统属性所指定的目录中加载类库
或从JDK的安装目录的jre/lib/ext子目录下加载类库
也可以加载该目录中的jar文件
系统类加载器(System Class Loader)
1,也称为应用程序类加载器,由Java编写,派生于ClassLoader类
2,父加载器为扩展类加载器
3,用于加载用户自定义的类

1.2.2 用户自定义类加载器
在Java开发应用程序中,类的加载是由引导类加载器,扩展类加载器,系统类加载器互相配合执行的,在必须要时,还可以自定义类加载器,来定制类的加载方式
用户自定义的类由系统类加载器加载,那么为什么要使用自定义类加载器?
1,隔离加载类
避免同名类加载时的冲突
2,修改类加载的方式
需要时再动态地加载
3,扩展加载源
除了本地磁盘,网络等源,还可以加载数据库等字节码文件的来源
4,防止源码泄露
加密后,使用时用自定义类加载器解密
自定义类加载器的实现步骤:
1,继承ClassLoader类
2,把自定义类加载逻辑写到findClass()中
3,编写自定义类加载器时,如果没有太复杂的需求
可以直接继承URLClassLoader,避免自己编写findClass()
及获取字节码流的方式,使自定义类加载器编写更为简洁
1.2.3 ClassLoader类的常用方法及获取方法
ClassLoader类,是一个抽象类,除了引导类加载器,所有的类加载器都继承它
ClassLoader的常用方法:
getParent() 返回该类加载器的超类加载器
loadClass(String name) 加载名为name的类,返回Class对象
findClass(String name) 查找名为name的类,返回Class对象
findLoadedClass(String name) 查找名为name的已被加载过的类,返回Class对象
defineClass(String name, byte[] b, int off, int len)
把字节数组b中的内容转换为一个Java类,返回Class对象
resolveClass(Class<?> klass) 连接指定的一个Java类
获取ClassLoader的方法:
1,获取当前类的ClassLoader
klass.getClassLoader();
2,获取当前线程上下文的ClassLoader
Thread.currentThread().getContextClassLoader();
3,获取系统类加载器
ClassLoader.getSystemClassLoader();
4,获取调用者的ClassLoader
DriverManager.getClassLoader();
1.2.4 类加载机制 (双亲委派机制)
JVM对class文件采用的是按需加载的方式,即需要使用该类时才会将它的class文件加载到内存生成Class对象,而加载某个类的class文件时,JVM采用双亲委派机制,即把请求交给父类处理,是一种任务委派模式
先来看这样一个示例:
package java.lang;
public class String {
static {
System.out.println(
"自定义的java.lang包下的自定义String类中的静态代码块" +
"应该会在类加载系统的初始化阶段被执行");
}
}
public class StringTest {
public static void main(String[] args) {
String str = new String();
System.out.println("Hello JVM");
}
}
上述的静态代码块中的内容是不会输出的,因为加载String类时,加载的仍然是核心API库中的String类,而不是自定义的String,这就是双亲委派机制在工作的结果
双亲委派机制的工作原理
1,如果一个类加载器收到了类加载请求,它并不会自己先去加载
而是把这个请求委托给父类加载器去执行
2,如果父类加载器还存在父类加载器,则进一步向上委托
依次递归,直到到达顶层的引导类加载器
3,如果父类加载器可以完成类加载任务,就成功返回
如果无法完成加载任务,才由子加载器尝试自己去加载

这就是上述示例加载自定义String类失败的结果,系统加载器将加载java.lang.String类的请求委托给了扩展类加载器,扩展类加载器将加载请求委托给了引导类加载器,引导类加载器发现java.lang.String可以加载,所以直接加载了核心API中的String类,而不是自定义的String类
双亲委派机制的优势:
1,避免类的重复加载
2,保护程序安全,防止核心API被篡改
如上述的java.lang.String示例
自定义的java.lang.String是永远不会被加载的
双亲委派机制会加载核心API的java.lang.String
1.3 Linking 链接阶段
经过Loading后,能够在内存中生成该类的Class对象,接下来需要进行链接 Linking,链接阶段又分为: 验证+准备+解析
验证(Verify): 用于确保Class对象中包含的信息复合当前虚拟机的要求
以保证加载类的正确性,不会危害虚拟机的安全
主要包括四种验证: 文件格式验证,元数据验证,字节码验证,符号引用验证
准备(Prepare): 为类中的静态变量分配内存并设置该变量的默认初始值(0,null,false)
对于被final修饰的常量,在此阶段直接赋值
且不会为类中的实例实例变量分配内存并初始化
解析(Resolve): 将常量池中的部分符号引用替换成直接引用
解析实际上是在初始化阶段之后完成的
符号引用就是用一组符号描述所引用的目标
直接引用就是直接指向目标的指针,相对偏移量或一个间接定位到目标的句柄
解析动作主要针对类或接口,字段,类方法,接口方法,方法类型等
1.4 Initialization 初始化阶段
经过Loading和Linking后,接下来需要进行初始化
初始化(Initialization):
初始化阶段就是执行方法<clinit>()的过程
该方法不需要定义,由javac编译器自动收集
类中的所有类变量的赋值动作和静态代码块中的语句合并而来
为静态变量赋值,如果该类具有父类,需要先执行父类的<clinit>()方法
<clinit>()在多线程环境下会被同步加锁
static {
num = 2;
}
private static int num = 20;
上述过程最终num的值是20
在Linking的Prepare阶段num被赋值为0
在Initialization阶段静态代码块和静态变量赋值语句被合并先后执行
num的值先被赋值为2,在被赋值为20
1.5 类加载子系统的补充
1,在JVM中表示两个Class对象是否是同一个类有两个必要条件:
1,类的完整名称一致,包括包名
2,加载这个类的ClassLoader必须相同
即时这两个Class对象来源于同一个class文件,被同一个虚拟机加载
但只要加载它们的ClassLoader不相同,那么这个两个Class对象也是不相同的
2,类的主动使用和被动使用
主动使用:
1,创建类的实例
2,访问某个类或接口的静态变量
3,调用类的静态方法
4,反射(Class.forName("xxx"))
5,初始化一个类的子类
6,JVM启动时被标明为启动类的类
7,JDK7开始提供的动态语言支持
除了上述7种情况,其它使用Java类的方式都是对类的被动使用
主动使用和被动使用的差别就是,被动使用不会进行初始化
1127

被折叠的 条评论
为什么被折叠?



