类加载器

JVM

简介

类加载器作用:类加载器加载硬盘上的或者网络上的class文件到内存中,并进行一定的处理,并为之生成对应的java.lang.Class对象。

更确切的说法:根据类的全限定名称来获取描述类的二进制字节流,并生成Class对象。java类加载器就是实现这样功能的代码。

类加载器是用来完成类的加载的,但是他的作用不只是如此,任何一个类,加载他的类加载器和这个类本身,确定了这个类在java虚拟机中的唯一性。也就是说,在java虚拟机中,类的全限定类名(包名和类名)和加载这个类的类加载器,是这个类的唯一标示。只有标示相同的两个类,才是相等的。即使两个类来源于同一个Class文件,只要加载它们的类加载器不同,那这两个类就必定不相等。这里的“相等”包括了代表类的Class对象的equals()、isAssignableFrom()、isInstance()等方法的返回结果,也包括了使用instanceof关键字对对象所属关系的判定结果。具体例子建议阅读深入理解java虚拟机-7.4.1

类加载器也是类,类加载器也需要加载器加载,java虚拟机可以安装多个类加载器,默认有三个主要的类加载器 BootStrap,ExtClassLoader,AppClassLoader. 优先级最高的加载器是BootStrap,他是内嵌在jvm中的C++代码,不需要类加载器加载,他负责加载比他低一优先级的类加载器。

1
2
System.out.println(ClassLoaderTest.class.getClassLoader().getClass().getName());//打印出的是AppClassLoader
System.out.println(System.class.getClassLoader()); //打印出的是null,其类加载器是BootStrap

三种类加载器

从java虚拟机的角度来说,只存在两种不同的类加载器,一种是根类加载器,这个类加载器使用c++代码实现的,是java虚拟机的一部分;另一种是所有其他的类加载器,这些类加载器都是用java语言实现的,独立于java虚拟机之外的,并且全都继承自java.lang.ClassLoader。

从java开发人员的角度来说,加载器可以划分的更细致些,分为下面所说的三种类加载器。

java虚拟机可以安装多个类加载器,默认有三个主要的类加载器 BootStrap,ExtClassLoader,AppClassLoader.

类加载器之间的父子关系
BootStrap——->ExtClassLoader——->AppClassLoader (爷爷——->儿子——->孙子)
根类加载器 扩展类加载器 系统类加载器也叫应用类加载器

BootStrap,ExtClassLoader,AppClassLoader分别负责加载不同路径下的class文件

BootStrap ———Java/jdk/jre/lib/rt.jar

ExtClassLoader ———Java/jdk/jre/lib/ext/*jar

AppClassLoader ———classpath指定的所有jar或者目录

此处输入图片的描述

BootStrap 根类加载器 负责加载Java/jdk/jre/lib/路径下的java的核心类。只能加载这个路径下能被虚拟机识别的类库(仅按照文件名进行识别,如rt.jar,名字不符合的类库即使放在这个目录下面也不会被加载)。
以下代码,可以获得根类加载器所加载的核心类库

1
2
3
4
5
6
7
8
// 获取根类加载器所加载的全部URL数组
URL[] urls = sun.misc.Launcher.
getBootstrapClassPath().getURLs();
// 遍历、输出根类加载器加载的全部URL
for (int i = 0; i < urls.length; i++)
{
System.out.println(urls[i].toExternalForm());
}

ExtClassLoader 扩展类加载器,负责加载 Java/jdk/jre/lib/ext/*jar 路径下的类,通过这种方式,就可以为java扩展核心类以外的新功能,将我们自己开发的类打包成jar文件,然后放在 Java/jdk/jre/lib/ext/目录下,就可使用我们自己开发的类

AppClassLoader 系统类加载器,也叫应用类加载器,负责加载classpath指定的所有jar或者目录,程序可以通过ClassLoader的静态方法getSystemClassLoader()来获取系统类加载器。如果没有特别指定,则用户自定义的类加载器都将以系统类加载器作为父类加载器。

类加载器相关机制

类加载器有三个机制
委托机制,全盘机制,缓冲机制(已经加载过的Class都会被缓存,以方便下次使用)

类加载器的委托机制

每个加载器加载类时,又先委托给其上级的类加载器加载,当所有祖宗的类加载器没有加载到类,回到发起者类加载器,还加载不了,则抛出ClassNotFound异常,不是再去找发起者加载器的儿子。

很好的解决了类加载器的基础类的统一问题(越基础的类由越上层的类加载器加载),基础类之所以被称作基础,是因为他们总是作为被用户代码调用的API。

例子:加载的都是同一个Object。

同时这样的委托机制便于集中管理,当最底层的MyClassLoader加载器想要加载某个类的时候,交给父类加载器去加载,父类加载器加载了这个类,当有最底层的ItcastClassLoader加载器想要加载MyClassLoader加载器曾经加载过的某个类的时候,同样交给父类加载器去加载,父类加载器发现自己曾经加载过这个类,就直接把这个类拿出来用了。

当Java虚拟机需要加载一个类的时候首先派当前线程的类加载器(可以用setContextClassLoader()为线程指定类加载器)去加载类,如果被加载的A类中引用了B类,则用加载了A类的加载器去加载B类(全盘机制),如果不想用线程的类加载器加载,我们也可以强制指定类加载器,我们也可以直接在代码中用ClassLoader.load()来加载类。

类加载器的全盘机制

当Java虚拟机需要加载一个类的时候首先派当前线程的类加载器(可以用setContextClassLoader()为线程指定类加载器)去加载类,如果被加载的A类中引用了B类,则用加载了A类的加载器去加载B类(全盘机制),如果不想用线程的类加载器加载,我们也可以强制指定类加载器,我们也可以直接在代码中用ClassLoader.load()来加载类。

类加载器的缓冲机制

所有被加载过的类都会被缓存,当需要使用某一个类的时候,类加载器会先从缓存区查找这个class,如果查找不到才会从硬盘上查找这个类的二进制文件,并将其转换成class对象,存入缓存。这也就是为什么我们修改了Class后,必须重启JVM,才可以生效的原因。

以下代码示范了访问JVM的类加载器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 获取系统类加载器
ClassLoader systemLoader = ClassLoader.getSystemClassLoader();
System.out.println("系统类加载器:" + systemLoader);
/*
获取系统类加载器的加载路径——通常由CLASSPATH环境变量指定
如果操作系统没有指定CLASSPATH环境变量,默认以当前路径作为
系统类加载器的加载路径
*/
Enumeration<URL> em1 = systemLoader.getResources("");
while(em1.hasMoreElements())
{
System.out.println(em1.nextElement());
}
// 获取系统类加载器的父类加载器:得到扩展类加载器
ClassLoader extensionLader = systemLoader.getParent();
System.out.println("扩展类加载器:" + extensionLader);
System.out.println("扩展类加载器的加载路径:System.getProperty("java.ext.dirs"));
System.out.println("扩展类加载器的parent:extensionLader.getParent());//打印的值是null,因为根类加载器不是用java代码实现的

此处输入图片的描述

ClassLoaderPropTest

自定义类加载器

我们可以定义自己的类加载器将自己定义的类加载器,挂在自己定义的类加载器的父类上就可以用自己定义的类加载器加载class文件
。。。。。。AppClassLoader——->自己定义的类加载器

编写自定义类加载器原理分析:

自定义的类加载器的必须继承ClassLoader,然后重写其findClass方法

ClassLoader相关方法

loadClass方法与findClass方法:

loadClass中封装了寻找父类加载器的方法,同时在这个方法里面调用了findClass方法,当寻找的父类加载器跟爷爷类加载器都无法加载到类时,就会回调findClass方法,所以我们自定义类加载器的时候,只需要重写findClass方法即可,这里体现了模板设计模式的思想
此处输入图片的描述

defineClass方法

当在重写的findClass方法里面,得到了.class文件的字节流的时候,就可以用defineClass方法将其转换class对象。
此处输入图片的描述

ClassLoader中的其他方法

findSystemClass(String name) 从本地的文件系统即classpath指定的所有jar或者目录装入文件。他在本地文件系统中寻找类文件,如果存在,就是用defineClass()方法将原始字节转换成Class对象,以将文件转换成类;
getSystemClassLoader()静态方法,用于返回系统类加载器;
getParent()返回类加载器的父类加载器;
findLoadedClass(String name)如果java虚拟机已经加载过了名为name的类,则直接返回该类的Class实例,否则返回null,此方法是java类加载机制的缓存机制的体现;

剩余代码编写过程详情看视频 视频链接:http://pan.baidu.com/s/1kTCaoIF 感觉视频讲的有点混乱

自定义类加载器可以实现的功能:

使用自定义的类加载器,可以实现如下常见的功能
执行代码前,自动验证数字签名;
根据用户提供的密码解密代码,从而可以实现代码混淆器来避免反编译Class文件;
根据用户的需求动态的加载器;
根据应用需求把其他的数据以字节码的形式加载到应用中;

有包名的类不能调用无包名的类,子类不能比父类抛出更广泛的异常

关于第48节视频----类加载器的高级问题分析,等看完web视频的servlet的时候,再回过头来看。

URLClassLoader类

URLClassLoader是ExtClassLoader跟AppClassLoader的父类。URLClassLoader的功能比较强大,它既可以从本地文件系统获取二进制文件来加载类,也可以直接从远程主机获取二进制文件来加载类。

在应用程序中可以直接通过URLClassLoader来加载类,URLClassLoader提供了如下两个构造器
URLClassLoader(URL[] urls):使用默认的父类加载器创建一个ClassLoader对象,该对象将从urls所指定的路径来查询并加载类
URLClassLoader(URL[] urls,ClassLoader parent):使用指定的父类加载器创建一个ClassLoader对象,他的功能跟前一个构造器相同

一旦得到了URLClassLoader的对象后,就可以调用该对象的loadClass()方法来加载指定的类。下面的程序示范了如何直接从文件系统加载MySQL驱动,并使用该驱动获取数据库的连接。通过这种方式来获取数据库的连接,可以无须将MySQL驱动添加到CLASSPATH环境变量中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class URLClassLoaderTest
{
private static Connection conn;
//定义一个获取数据库连接方法
public static Connection getConn(String url ,
String user , String pass) throws Exception
{
if (conn == null)
{
// 创建一个URL数组
URL[] urls = {new URL("file:mysql-connector-java-3.1.10-bin.jar")};
// 以默认的ClassLoader作为父ClassLoader,创建URLClassLoader
URLClassLoader myClassLoader = new URLClassLoader(urls);
// 加载MySQL的JDBC驱动,并创建默认实例
Driver driver = (Driver)myClassLoader.loadClass("com.mysql.jdbc.Driver").newInstance();
// 创建一个设置JDBC连接属性的Properties对象
Properties props = new Properties();
// 至少需要为该对象传入user和password两个属性
props.setProperty("user" , user);
props.setProperty("password" , pass);
// 调用Driver对象的connect方法来取得数据库连接
conn = driver.connect(url , props);
}
return conn;
}
public static void main(String[] args)throws Exception
{
System.out.println(getConn("jdbc:mysql://localhost:3306/mysql"
, "root" , "32147"));
}
}

此处输入图片的描述

源码地址
mysql-connector-java-3.1.10-bin.jar下载

当前网速较慢或者你使用的浏览器不支持博客特定功能,请尝试刷新或换用Chrome、Firefox等现代浏览器