类加载器
1.虚拟机内置加载器
1.1.根类加载器(Bootstrap)
无父加载器
c++语言实现
主要负责加载系统属性“sun.boot.class.path”指定路径下的核心类库(即<JAVA_HOME\jre\lib>),出于安全考虑,根类加载器只加载java、javax、sun、开头的类。
public static void main(String[] args){
ClassLoader cl = Object.class.getClassLoader();
System.out.println(cl);//根类加载器打印null(系统的设置为null,并不是没有)
}
1.2.扩展类加载器(Extension)?
扩展类加载器,由Java语言编写,父类加载器是根类加载器。负责加载<JAVA_HOME>\jre\lib\ext目录下的类库或者系统变量“java.ext.dirs”指定的目录下的类库。
?
1.3.系统类加载器(System)
系统类加载器也称为应用类加载器,也是纯Java类。它的父类加载器是扩展类加载器。它负责从classpath环境变量或者系统属性java,class.path所指定的目录中加载器,它是用户自定义的类加载器的默认父加载器。一般情况,该类加载器是程序中默认的类加载器,可以通过
public class TestClassLoader {
public static void main(String[] args) {
ClassLoader classLoader1 = TestClassLoader.class.getClassLoader();
System.out.println(classLoader1);//jdk.internal.loader.ClassLoaders$AppClassLoader@78308db1
}
}
1.4.注意
Java虚拟机对class文件采取的是按需加载的方式。且加载器会优先把请求交给父加载器处理。双亲委派机制的父子关系并非面向对象程序设计中的继承关系,而是通过使用组合模式来复用父类加载器代码。
2.双亲委派机制的好处
- 保证每个类不会被重复加载
- 考虑到安全因素,保证Java核心api中定义类型不会被随意替换
3.ClassLoader
所有的类加载器(除了根类加载器)都必须继承java,lang.ClassLoader.它是一个抽象类,主要方法如下:
3.1.loadClass
在ClassLoader的源码中,有一个方法loadClass(String name, boolean resolve),这里是双亲委派机制的代码实现。需要注意的是,只有父类加载器加载不到类时,会调用findClass进行类的查找,所以,在定义自己的类加载器时,不要覆盖该方法,应该覆盖掉findClass方法。
//ClassLoader类的loaderClass源码
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized(this.getClassLoadingLock(name)) {
Class<?> c = this.findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (this.parent != null) {
c = this.parent.loadClass(name, false);
} else {
c = this.findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException var10) {
}
if (c == null) {
long t1 = System.nanoTime();
c = this.findClass(name);
PerfCounter.getParentDelegationTime().addTime(t1 - t0);
PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
this.resolveClass(c);
}
return c;
}
}
(等看懂了再删了这句=_=)
3.2.findClass
在自定义类加载器时,一般我们需要覆盖这个方法,且ClassLoader中给出了一个默认的错误实现。
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
3.3.defineClass
用来将byte字节解析成虚拟机能够识别的Class对象。defineClass()方法通常与findClass()方法一起使用。在自定义类加载器时,会直接覆盖ClassLoader的findClass()方法获取要加载器的字节码,然后调用defineClass()方法生成Class对象。
protected final Class<?> defineClass(String name, byte[] b, int off, int len) throws ClassFormatError {
return this.defineClass(name, b, off, len, (ProtectionDomain)null);
}
3.4.resolveClass
连接指定的类。类加载器可以使用此方法来连接类。
4.URLClassLoader
在java.net包中,JDK提供了一个更加易用的类加载器URLClassLoader,它扩展了ClassLoader,能够从本地或者网络上指定的位置加载类。我们可以使用该类作为自定义的类加载器使用。
构造方法:
//指定要加载的类的URL地址,父类加载器默认为系统类加载器
public URLClassLoader(URL[] urls) {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkCreateClassLoader();
}
this.acc = AccessController.getContext();
this.ucp = new URLClassPath(urls, this.acc);
}
//指定要加载的类的URL地址,并指定父类加载器
public URLClassLoader(URL[] urls, ClassLoader parent) {
super(parent);
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkCreateClassLoader();
}
this.acc = AccessController.getContext();
this.ucp = new URLClassPath(urls, this.acc);
}
5.用户自定义加载器
- 继承ClassLoader
- 覆盖findClass方法
5.1.自定义文件类加载器
import java.io.*;
/**
* 例子
*/
public class TestClassLoader extends ClassLoader{
private String directory;
public TestClassLoader(String directory){
this.directory = directory;
}
public TestClassLoader(String directory,ClassLoader parent){
super(parent);
this.directory = directory;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
//把类名转化成目录
String file = directory + File.separator + name.replace(".", File.separator) + ".class";
//构建输入流
InputStream in = null;
in = new FileInputStream(file);
//构建字节输出流
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buf = new byte[1024];
int len = -1;
while((len=in.read(buf))!=-1){
baos.write(buf,0,len);
}
//读到的二进制数据
byte[] data = baos.toByteArray();
baos.close();
in.close();
return defineClass(name,data,0,data.length);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
5.2.自定义网络类加载器
与自定义文件类加载器类似
import java.io.*;
import java.net.URL;
/**
* 例子
*/
public class TestClassLoader extends ClassLoader{
private String url;
public TestClassLoader(String url){
this.url = url;
}
public TestClassLoader(String url,ClassLoader parent){
super(parent);
this.url = url;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
//把类名转化成目录
String path = url + "/" + name.replace(".", "/" ) + ".class";
URL url = new URL(path);
//构建输入流
InputStream in = url.openStream();
//构建字节输出流
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buf = new byte[1024];
int len = -1;
while((len=in.read(buf))!=-1){
baos.write(buf,0,len);
}
//读到的二进制数据
byte[] data = baos.toByteArray();
baos.close();
in.close();
return defineClass(name,data,0,data.length);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
5.3.热部署类加载器
(原理:避开双亲委派机制,调用findClass()方法)
(暂略)
6.线程上下文类加载器
这种加载器加载类的方式破坏了双亲委派机制,但使得Java类加载器变得更加灵活。线程上下文类加载器可解决普通类加载器无法加载第三方提供的依赖包的问题。
(暂略)