类加载器
JVM的类加载通过classLoader及其子类完成的
类加载器:
启动类加载器(Bootstrap ClassLoader): 负责加载存放在 <JAVA_HOME>\lib 目录中的核心类库,如rt.jar、resources.jar等(或者被 -Xbootclasspath 参数所指定的路径中的,并且是虚拟机识别的类库)。这个加载器是 C++ 编写的,随着JVM启动。
扩展类加载器(Extension ClassLoader): 负责加载<JAVA_HOME>\lib\ext 目录中的类库,(同样也可以用 java.ext.dirs 系统变量来指定路径)。
应用程序类加载器(Application ClassLoader): 负责加载用户类路径 classpath 上所有的 jar 包和 .class 文件。
自定义类加载器: 可以支持一些个性化的扩展功能(比如:JRebel)
类与类加载器
对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。
这句话可以表达得更通俗一些:比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个Class文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等。
这里所指的“相等”,包括代表类的Class对象的 equals() 方法、isAssignableFrom() 方法、isInstance() 方法的返回结果,也包括使用 instanceof 关键字做对象所属关系判定等情况。
类加载器执行顺序
加载时机和过程
类加载的四个时机:
- 执行new、getStatic、putStatic、invokeStaic四条指令时
- 使用java.lang.reflect包办法是,对类进行反射调用
- 初始化一个类时,发现父类还没初始化,要先初始化父类
- 当虚拟机启动时,用户需要指定一个主类main,需要先将主类加载
一个类的一生:
类加载做了什么
- 类全限定名称 -> 二进制字节流加载class文件
- 字节流静态数据 -> 方法区(永久代、元空间)
- 创建字节码Class对象
双亲委派
如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器区执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父加载器无法完成此加载任务,子加载器才会尝试自己去加载,如果均加载失败,就会抛出ClassNotFoundException异常,这就是双亲委派模式。
双亲委派优点:
安全,可避免用户自己编写的类动态替换Java的核心类,如java.lang.String。,java核心api中定义类型不会被随意替换,假设通过网络传递一个名为java.lang.Integer的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer,而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改。
避免全限定命名的类重复加载(使用了findLoadClass()判断当前类是否已加载)。Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。