类加载器原理
将class文件字节码内容加载到内存中,并将这些静态数据转换为方法区的运行时数据结构,在堆中生成一个代表这个类的java.lang.Class对象,作为方法区类数据访问的入口
类缓存 标准的JavaSE类记载器可以按照要求查找类,但一旦某个类被加载到类加载器中, 它将维持加载(缓存)一段时间。不过,JVM垃圾回收器可以回收这些Claas对象。
类加载器树状结构、双亲委托机制
类加载器树状结构
引导类加载器 用来加载Java的核心库(JAVA_HOME/jre/lib/rt/jar,或sun.boot.class.path路径下的内容),是用原生的代码(c++)实现的,并不继承java.lang.ClassLoader。加载扩展类加载器和应用程序类加载器。并指定它们的父类加载器。
扩展类记载器 用来加载Java的扩展库(JAVA_HOME/jre/ext/*.jar,或java.ext.dirs路径下的内容)。 Java虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载Java类。
应用程序类加载器 它根据Java应用的类路径(classpath,java.class.path路类) 一般来说,Java应用的类都是由它来完成加载的。由sun.misc.Launcher$AppClassLoader实现。
自定义类加载器 开发人员可以通过继承java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊的需要。
public class Demo {
public static void main(String[] args) {
//获取应用程序类加载器
System.out.println(ClassLoader.getSystemClassLoader());
//获取扩展类加载器
System.out.println(ClassLoader.getSystemClassLoader().getParent());
//获取引导类加载器
System.out.println(ClassLoader.getSystemClassLoader().getParent().getParent());
//获取classpath()
//System.out.println(System.getProperty("java.class.path"));
}
}
//结果
sun.misc.Launcher$AppClassLoader@2503dbd3
sun.misc.Launcher$ExtClassLoader@511d50c0
null
public class FileSystemClassLoader extends ClassLoader {
String rootDir;
public FileSystemClassLoader(String rootDir) {
this.rootDir = rootDir;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class c = findLoadedClass(name);
if (c != null) {
return c;
} else {
ClassLoader parent = this.getParent();
try {
c = parent.loadClass(name);
}catch (Exception e){
e.printStackTrace();
}
if (c != null) {
return c;
} else {
byte[] classData = getClassData(name);
if (classData == null) {
throw new ClassNotFoundException("自定义类加载器没有加载到");
} else {
c = defineClass(name, classData, 0, classData.length);
}
}
}
return c;
}
private byte[] getClassData(String className) {
String path = rootDir + "/" + className.replace(".", "/") + ".class";
InputStream is = null;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
is = new FileInputStream(path);
byte[] buffer = new byte[1024];
int temp = 0;
while ((temp = is.read(buffer)) != -1) {
baos.write(buffer,0,temp);
}
return baos.toByteArray();
} catch (Exception e) {
return null;
} finally {
if(is != null){
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(baos != null){
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
public class TestMyClassLoader {
public static void main(String[] args) throws ClassNotFoundException {
FileSystemClassLoader loader = new FileSystemClassLoader("/Users/wjk/Desktop");
FileSystemClassLoader loader1 = new FileSystemClassLoader("/Users/wjk/Desktop");
Class c = loader.loadClass("com.Hello");
Class c1 = loader1.loadClass("com.Hello");
Class c2 = loader.loadClass("com.Hello");
Class c3 = loader.loadClass("java.lang.String");
System.out.println(c.hashCode());//被两个类加载器加载的同一个类,JVM认为是不同的(c和c1的hashCode值不一样)
System.out.println(c1.hashCode());
System.out.println(c2.hashCode());
System.out.println(c.getClassLoader());//使用的是自定义的类加载器
System.out.println(c3.getClassLoader());//使用的是引导类加载器
}
}
//结果
1725154839
1670675563
1725154839
classLoaderTest.FileSystemClassLoader@5e2de80c
null
双亲委托机制
代理模式:交给其他类加载器加载指定的类
双亲委托机制
(1)就是当某个特定的类加载器接到加载类的请求的时候,首先委托给其父类(父类如果有父类一直向上追溯),直到父类加载器无法加载时,该加载器进行加载。
(2)双亲委托机制是为了保证Java核心库的类型安全。
这种机制保证不会加载到用户自定义的java.lang.Class类的情况 (3)类加载器除了用于加载类,也是安全最基本的屏障。
双亲委托机制是代理模式的一种,但是并不是所有的类加载都是双亲委托机制,比如tomcat类加载器首先尝试特定的 类加载器,加载不到类时在尝试器父类加载器。
自定义类加载器
如何实现自定义类加载器:
(1)继承java.lang.ClassLoader
(2)检查所请求的类型是否已经被这个类加载器加载到命名空间,如果已经被加载直接返回。
(3)委派给父类加载(也可以不委派,这个程序控制)。
(4)调用自定义加载器findClass()方法获取字节码,然后调用defineClass()导入类型到方法区。
线程上下文类加载器
双亲委派机制以及类加载器的问题
(1)一般情况下,保证一个类中所关联的其他类都是由当前类加载器所加载的。
例如:ClassA本身在扩展类加载器下找到,那么它里面new出来的一些也就只能用扩展类加载器查找(
不会低一个级别),所以有的应用程序类加载器可以找到,却没有找到。
(2)JDBC API。它有实现driven的部分(mysql/sql server),我们的JDBC API都是有引导类加载器
或者扩展类加载器载入的,但是JDBC drive却是由扩展类记载器或者应用程序类加载器载入,那么就有可能找不到drive中,在Java领域,其实只要分成Api+SPI(service provice interface特定厂商提供)的,都会遇到这个问题。简而言之:接口定义在Java本身,实现却在第三方,Java本本身使用引导类加载器或者扩展类加载器载入,而第三方实现使用扩展类加载器或者应用程序类记载器加载。
SPI接口是Java核心库的一部分,是由引导类架子器加载的;SPI实现的Java类一般是由应用程序类加载器加载的。引导类加载器是无法找到SPI的实现类的,因为它只加载Java的核心库。
动态加载资源的时候必须需要的加载器
(1)系统类加载器
(2)当前类加载器
(3)当前线程类加载器
线程类加载器
线程类加载器是为了抛弃双亲委托加载链式模式。
每一个线程都有一个关联上下文类加载器,如果用new Thread()方式生成
新的线程,新线程将继续继承其父线程的上下文类加载器。如果程序对线程上下文
类加载器没有任何变动的话,程序中所有的线程将都使用系统类加载器(即:应用程序类
加载器)作为上下文类加载器。
public class Demo3 {
public static void main(String[] args) {
//获得Demo类的类加载器
ClassLoader loader1 = Demo.class.getClassLoader();
System.out.println(loader1);
//获得当前线程类加载器
ClassLoader loader2 = Thread.currentThread().getContextClassLoader();
System.out.println(loader2);
//使用自定义类加载器
Thread.currentThread().setContextClassLoader(new FileSystemClassLoader("/Users/wjk/Desktop"));
//获得当前线程类加载器
System.out.println(Thread.currentThread().getContextClassLoader());
}
}
//结果
sun.misc.Launcher$AppClassLoader@2503dbd3
sun.misc.Launcher$AppClassLoader@2503dbd3
classLoaderTest.FileSystemClassLoader@60e53b93
服务器类加载器原理和OSGI介绍
服务器类加载器
(1)一切为了安全,TOMCAT不能使用系统默认的类加载器。
原因:
如果TOMCAT跑你的web项目的时候使用系统默认的类加载器,
你可以直接肆无忌惮的操作系统的各个目录,这是相当危险的。对于Java EE容器中的Web应用来说,类加载器的实现
与一般Java应用有所不同。每一个Web应用都一个对应的类加载器,它先尝试加载某个类,加载不了再委托给父类加载器,
(2)为了安全TOMCAT需要实现自己的类加载器
我可以限制你把类写在指定的位置,否则我不给你加载。
OSGI介绍
OSGI(Open Service Gateway Initative)是面向Java的动态模块系统。它为开发人员提供了 面向服务和基础组件的运行环境,并提供标准的方式用来管理软件的生命周期。
Eclipse就是基于OSGI技术构建的。
原理: OSGI中的每个模块都包含Java包和类。模块可以声明它所依赖的需要导入的其他模块和Java包和 类,也可以声明自己导出的包和类,供其他模块来使用。也就是说能够隐藏和共享某些Java包和类。 这个是通过OSGI特有的类加载器机制来实现的。OSIG中每个模块都有对应一个类加载器,它负责加载模块 自己包含的Java包和类。当它需要加载Java核心库(java开头的包和类)的类时,它要代理给父类加载器来完成。当它需要加载导入的Java类的时候,它会代理给导出此Java类的模块来完成加载,模块也可以显式声明某些类和包必须由父类加载器加载。