前面我们知道类加载有系统自带的3种加载器,也有自定义的加载器,那么这些加载器之间的关系是什么,已经在加载类的时候,谁去加载呢?这节,我们将进行讲解。

一、双亲委派机制

JVM的ClassLoader采用的是树形结构,除了BootstrapClassLoader以外?每个ClassLoader都会有一个parentClassLoader,用户自定义的ClassLoader默认的parentClassLoader是SystemClassLoader,当然你可以自己指定需要用哪一个ClassLoader的实例,我们来看他的API:

默认的无参构造方法使用的是SystemClassLoader,你可以通过传入一个ClassLoader的实例来指定他的父类加载器。这里强调一点,很多人认为各个父子类加载器之间是继承关系,这里澄清一下,父子类加载器之间是组合关系,子类类加载器会含有一个parentClassLoader的对象,类加载的时候通常会按照树形结构的原则来进行,也就是说,首先是从parentClassLoader中尝试进行加载,当parent无法进行加载时,再从当前的类加载器进行加载,以此类推。JVM会保证一个类在同一个ClassLoader中只会被加载一次。
ClassLoader抽象类为我们定义了一系列的关键的方法,下来让我们来看一下
1、loadClass方法此方法用来加载指定名字的类,ClassLoader会先从已加载的类中寻找,如果没有,则使用父加载器进行加载,如果加载成功则加载,否则从当前的类加载器中进行加载,如果还没有找到该类的class文件则会抛出异常ClassNotFoundException

如果该类需要链接,则通过resolveClass进行链接。

2、defineClass,此方法用来将二进制的字节码转换为Class对象,这个对类的自定义加载非常重要,当然前文我们已经说了,当类的二进制文件被加载到内存之后,要进行语法分析,语义分析等一系列的验证,如果不符合JVM规范,则抛出ClassFormateError错误,如果生成的类名和字节码中的不一致,则抛出NoClassDefFoundException,如果加载的class是受保护的、采用不同的标签名的,或者一java.*开头的,则抛出SecurityException,如果要加载的class在之前已经被加载过,则直接抛出LinkageError。

3、resolveClass,此方法完成Class的链接,如果链接过则直接返回。当Java开发人员调用Class.forName来获取一个class对象的时候,JVM会从方法栈上寻找第一个ClassLoader,通常也就是执行Class.forName的ClassLoader,并使用这个ClassLoader来加载此类。JVM为了保护加载、执行的类的安全,不允许ClassLoader直接卸载加载了的类,只有JVM才可以卸载,在SUN的JDK中,只有ClassLoader没有 被引用的时候,次ClassLoader加载的类才会被卸载!

附:JDK中ClassLoader的部分源码

1、 构造函数

protected ClassLoader(ClassLoader parent) {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkCreateClassLoader();
}
this.parent = parent;
initialized = true;
}
protected ClassLoader() {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkCreateClassLoader();
}
this.parent = getSystemClassLoader();
initialized = true;
}

2loadClass

public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
protected synchronized Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// First, check if the class has already been loaded
Class c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClass0(name);
}
} catch (ClassNotFoundException e) {
// If still not found, then invoke findClass in order
// to find the class.
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}

类的这种加载机制我们称之为父委托加载机制,父委托机制的优点就是能够提高软件系统的安全性。因为在此机制下,用户自定义的类加载器不可能加载本应该由父加载器加载的可靠类,从而防止不可靠的甚至恶意的代码代替由父类加载器加载的可靠代码。如,java.lang.Object类总是由根类加载器加载的,其他任何用户自定义的类加载器都不可能加载含有恶意代码的java.lang.Object类。
被定义的类加载器,而它的父类加载器则被称为初始类加载器。

我们知道java中很可能出现类名相同的类,但是JVM却能正常的加载,是因为我们将相同的类名的类放在了不通的包(package)下面,这个也成为命名空间,每个类加载器都有自己的命名空间,命名空间是由该加载器以及所有父加载器所加载的类组成。在同一个命名空间中,不会出现类的完整名字(包名+类名)相同的两个类;在不同的命名空间中,有可能出现类的完整名字相同的两个类。

由同一类加载器加载的属于相同包的类组成了运行时包。决定两个类是不是属于同一个运行时包,不仅要看他们的包名称是否相同,还要看定义类加载器是否相同。只有属于同一运行时包的类之间才能相互访问可见(默认访问级别)的类和成员。假设用户自定义了一个类java.lang.TestCase并由用于自定义的类加载器加载,由于java.lang.TestCase和核心类库java.lang.*由不同的类加载器加载,他们属于不同的运行时包,所以java.lang.TestCase不能访问核心库java.lang包中的包可见成员。

同一个命名空间内的类是相互可见的。

子类加载器的命名空间包含所有父类加载器的命名空间,因此由子类加载器加载的类能看见父类加载器加载的类,相反,由父类加载器加载的类不能看见子类加载器加载的类。如果两个加载器之间没有直接或者间接的父子关系,那么他们各自加载的类互不可见。

二、自定义类加载器 

首先类的双亲委派流程为:

首先,我们定义一个待加载的普通Java类:Test.java。放在com.pony.cl包下:

package com.pony.cl;

public class Test {
public void hello() {
System.out.println("恩,是的,我是由 " + getClass().getClassLoader().getClass()
+ " 加载进来的");
}
}

注意:

如果你是直接在当前项目里面创建,待Test.java编译后,请把Test.class文件拷贝走,再将Test.java删除。因为如果Test.class存放在当前项目中,根据双亲委派模型可知,会通过sun.misc.Launcher$AppClassLoader 类加载器加载。为了让我们自定义的类加载器加载,我们把Test.class文件放入到其他目录。

接下来就是自定义我们的类加载器:

import java.io.FileInputStream;
import java.lang.reflect.Method; public class Main {
static class MyClassLoader extends ClassLoader {
private String classPath; public MyClassLoader(String classPath) {
this.classPath = classPath;
} private byte[] loadByte(String name) throws Exception {
name = name.replaceAll("\\.", "/");
FileInputStream fis = new FileInputStream(classPath + "/" + name
+ ".class");
int len = fis.available();
byte[] data = new byte[len];
fis.read(data);
fis.close();
return data; } protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = loadByte(name);
return defineClass(name, data, 0, data.length);
} catch (Exception e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
} }; public static void main(String args[]) throws Exception {
MyClassLoader classLoader = new MyClassLoader("D:/test");
Class clazz = classLoader.loadClass("com.pony.cl.Test");
Object obj = clazz.newInstance();
Method helloMethod = clazz.getDeclaredMethod("hello", null);
helloMethod.invoke(obj, null);
}
}

注意点:

Object obj = clazz.newInstance();
不能写成:
Test obj = (Test)clazz.newInstance();

如果写成这样会报错,因为当前的这个类是由系统加载器加载,而Test是由自定义加载器加载,那么系统类加载和自定义类的加载器不属于同一个运行时包,这个时候是没有办法直接转换的,只能通过反射的方式去访问,反射是唯一一种可以跨越在不同运行时包的方法。

参考资料:

圣思园张龙老师深入Java虚拟机系列

JVM学习六:JVM之类加载器之双亲委派机制的更多相关文章

  1. 深入探究JVM之类加载与双亲委派机制

    @ 目录 前言 类的生命周期 加载 验证 准备 解析 初始化 案例一 案例二 案例三 案例四 类加载器 类加载器和双亲委派模型 破坏双亲委派模型 第一次 SPI Tomcat OSGI 总结 前言 前 ...

  2. JVM类加载与双亲委派机制被打破

    前言 前文已经讲了虚拟机将java文件编译成class文件后的格式:JVM虚拟机Class类文件研究分析 java文件经过编译,形成class文件,那么虚拟机如何将这些Class文件读取到内存中呢? ...

  3. 面试~jvm(JVM内存结构、类加载、双亲委派机制、对象分配,了解垃圾回收)

    一.JVM内存结构 ▷ 谈及内存结构各个部分的数据交互过程:还可以再谈及生命周期.数据共享:是否GC.是否OOM 答:jvm 内存结构包括程序计数器.虚拟机栈.本地方法栈.堆.方法区:它是字节码运行时 ...

  4. 【Java_基础】java类加载过程与双亲委派机制

    1.类的加载.连接和初始化 当程序使用某个类时,如果该类还未被加载到内存中,则系统会通过加载.连接.初始化三个步骤来对类进行初始化.如果没有意外,jvm将会连续完成这三个步骤,有时也把这三个步骤统称为 ...

  5. Java类加载器和双亲委派机制

    前言 之前详细介绍了Java类的整个加载过程(类加载机制详解).虽然,篇幅较长,但是也不要被内容吓到了,其实每个阶段都可以用一句话来概括. 1)加载:查找并加载类的二进制字节流数据. 2)验证:保证被 ...

  6. 深入JVM系列(三)之类加载、类加载器、双亲委派机制与常见问题

    一.概述   定义:虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的java类型.类加载和连接的过程都是在运行期间完成的. 二. 类的 ...

  7. JVM体系结构之二:类加载器之2:JVM 自定义的类加载器的实现和使用

    一.回顾一下jdk自带的类加载器: 1.java虚拟机自带的加载器     根类加载器(Bootstrap,c++实现)     扩展类加载器(Extension,java实现)     应用类加载器 ...

  8. JVM之类加载器、加载过程及双亲委派机制

    JVM 的生命周期 虚拟机的启动 Java 虚拟机的启动是通过引导类加载器(bootstrap class loader)创建一个初始类(initial class)来完成的,这个类是由虚拟机的具体实 ...

  9. JVM 专题四:类加载子系统(二)双亲委派机制

    2. 双亲委派机制 2.1 双亲委派机制工作原理 2.1.1 原理 Java虚拟机对class文件采用的是按需加载的方式,也就是说当需要使用该类时才会将它的class文件加载到内存,生成class对象 ...

随机推荐

  1. R语言实现关联规则与推荐算法(学习笔记)

    R语言实现关联规则 笔者前言:以前在网上遇到很多很好的关联规则的案例,最近看到一个更好的,于是便学习一下,写个学习笔记. 1 1 0 0 2 1 1 0 0 3 1 1 0 1 4 0 0 0 0 5 ...

  2. 嵌入式linux------ffmpeg移植 编码H264(am335x编码H264)

    [cpp] view plaincopy <pre name="code" class="cpp"><pre name="code& ...

  3. Linux显示各栏位的标题信息列

    Linux显示各栏位的标题信息列 youhaidong@youhaidong-ThinkPad-Edge-E545:~$ who -H 名称 线路 时间 备注 youhaidong :0 2015-0 ...

  4. jquery的动画学习--jquery权威指南

        前面的fadeIn和fadeOut还有fadeTo以及sildeToggle还有sildeUp\sildeDown还有toggle还有show.hide等都经常用,就不再手写了,需要注意的是f ...

  5. MySQL配置文件

    [mysqld]datadir=/usr/local//mysql/data    #数据存放位置socket=/var/lib/mysql/mysql.sockuser=mysqllower_cas ...

  6. Kendo UI ASP.Net MVC 实现多图片及时显示加上传(其中有借鉴别人的代码,自己又精简了一下,如有冒犯,请多原谅!)

    View: <div class="demo-section k-content"> @(Html.Kendo().Upload() .Name("files ...

  7. 关于Android 7.0无法进行https抓包的问题

    在App进行数据请求的时候,如果每次都打印log去判断是一件很不"人性化"的操作行为,所以一般都会进行抓包分析. 以最常用的软件Fiddler来说,进行普通的http抓包没什么事, ...

  8. Hello English Again

    Currently, I just want to write something in English.Maybe I just want to review my Egnlish knowledg ...

  9. static与final的区别

    final被修饰的变量为常量一旦赋值不能修改,被修改的方法为最终方法不能被重写,被修饰的类是最终类,不能被继承static被修饰的变量和方法,为该整个类及其类的对象所共享,一个类或对象修改了被定义的类 ...

  10. javaScript执行环境、作用域链与闭包

    一.执行环境 执行环境定义了变量和函数有权访问的其他数据,决定了他们各自的行为:每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中.虽然我们编写的代码无法访问这个对象 ...