java.lang.Class
对象实例,在虚拟机提供了3种类加载器,启动(Bootstrap)类加载器、扩展(Extension)类加载器、系统(System)类加载器(也称应用类加载器)Bootstrap class loader
:最基本的类加载器,本地语言编写,不需要被加载,直接被嵌套在虚拟机中(是虚拟机自身的一部分)${JAVA_HOME}/lib/
路径下核心类库(rt.jar)或者 -Xbootclasspath
参数指定的路径下的jar包加载到内存中null
Extension class loader
:扩展类加载器,是指Sun实现的 sun.misc.Launcher$ExtClassLoader
类,由Java
实现,是Launcher的静态内部类。${JAVA_HOME}/lib/ext/
目录下或者由系统变量 -Djava.ext.dir
指定位路径中的类库ExtClassLoader
System class loader
:系统(System)类加载器或者应用程序加载器。 指 Sun公司实现的 sun.misc.Launcher$AppClassLoader
java -classpath
或-D java.class.path
指定路径下的类库,就是我们经常用到的classpath
路径。一般情况下该类加载是程序中默认的类加载器,通过ClassLoader.getSystemClassLoader()
就能获取。AppClassLoader
在Java
的日常应用程序开发中,类的加载几乎是由上述3种类加载器相互配合执行的,在必要时,我们还可以自定义类加载器,需要注意的是,Java虚拟机对class文件采用的是按需加载的方式,也就是说当需要使用该类时才会将它的class
文件加载到内存生成class
对象,而且加载某个类的class
文件时,Java虚拟机采用的是双亲委派模式即把请求交由父类处理,它是一种任务委派模式。
JVM
中的所有类装载器采用父子关系的树形结构进行组织。
//1. bootstrap class loader: null,拿不到 BootStrap 的名字
System.out.println(String.class.getClassLoader());
//2. extesion class loader 负责扩展包中的类(jre/lib/ext下面的类) sun.misc.Launcher$ExtClassLoader
System.out.println(com.sun.crypto.provider.DESKeyFactory.class.getClassLoader().getClass().getName());
//3. application class loader 负责自己写的类,也称为系统 classLoader。sun.misc.Launcher$AppClassLoader
System.out.println(JdkClassLoader.class.getClassLoader().getClass().getName());
// sun.misc.Launcher$AppClassLoader@14dad5dc
System.out.println(ClassLoader.getSystemClassLoader());
// sun.misc.Launcher$AppClassLoader@19821f
System.out.println(ClassLoader.getSystemClassLoader().getClass().getName());
// sun.misc.Launcher$AppClassLoader@14dad5dc
System.out.println(JdkClassLoader.class.getClassLoader());
// sun.misc.Launcher$AppClassLoader
System.out.println(JdkClassLoader.class.getClassLoader().getClass().getName());
输出:
null
sun.misc.Launcher$ExtClassLoader
sun.misc.Launcher$AppClassLoader
sun.misc.Launcher$AppClassLoader@14dad5dc
sun.misc.Launcher$AppClassLoader
sun.misc.Launcher$AppClassLoader@14dad5dc
sun.misc.Launcher$AppClassLoader
AppClassLoader -> ExtClassLoader -> BootStrap
ClassLoader loader = JdkClassLoader.class.getClassLoader();
while (loader != null) {
System.out.println("=======" + loader.getClass().getName());
loader = loader.getParent();
}
System.out.println("-------" + loader);
输出:
=======sun.misc.Launcher$AppClassLoader
=======sun.misc.Launcher$ExtClassLoader
-------null
ClassLoader.loadClass()
方法来指定某个类加载器加载某个类AppClassLoader -> ExtClassLoader -> BootStrap
ClassNotFoundException
getChild()
方法,即使有,那么多子加载器也不知道具体找哪一个graph BT
A[ExtClassLoader]-->C[URLClassLoader]
B[AppClassLoader]-->C[URLClassLoader]
C-->D[SecureClassLoader]
D-->E[ClassLoader]
loadClass(String)
:该方法加载指定名称(包括包名)的二进制类型,不再建议用户重写但用户可以直接调用该方法,loadClass()
方法是ClassLoader
类自己实现的,该方法中的逻辑就是双亲委派模式的实现
。findClass()
方法去加载。loadClass
实现也可以知道如果不想重新定义加载类的规则,也没有复杂的逻辑,只想在运行时加载自己指定的类,那么我们可以直接使用this.getClass().getClassLoder.loadClass("className")
,这样就可以直接调用ClassLoader
的loadClass
方法获取到class
对象。protected Class> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 先从缓存查找该class对象,找到就不用重新加载
Class> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
//如果找不到,则委托给父类加载器去加载
c = parent.loadClass(name, false);
} else {
//如果没有父类,则委托给启动加载器去加载
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// 如果都没有找到,则通过自定义实现的findClass去查找并加载
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {//是否需要在加载时进行解析
resolveClass(c);
}
return c;
}
}
findClass(String)
:自定义类加载时,建议把自定义的类加载逻辑写在findClass()方法中。ClassLoader
类中并没有实现findClass()
方法的具体代码逻辑,取而代之的是抛出ClassNotFoundException异常defineClass(byte[] b, int off, int len)
:用来将byte字节流解析成JVM能够识别的Class对象。defineClass()
方法通常与findClass()
方法一起使用ClassLoader
的findClass()
方法并编写加载规则,取得要加载类的字节码后转换成流,然后调用defineClass()
方法生成类的Class
对象。见后面的定义类加载器resolveClass(Class≺?≻ c)
:使用该方法可以使用类的Class
对象创建完成也同时被解析。前面我们说链接阶段主要是对字节码进行验证,为类变量分配内存并设置初始值同时将字节码文件中的符号引用转换为直接引用sun.misc.Launcher$AppClassLoader
JdkClassLoader.java
打包,放JAVA_HOME\lib\ext\
目录。重新运行:则打印出 sun.misc.Launcher$ExtClassLoader
AppClassLoader
,其先会委托给ExtClassLoader
,继续委托给顶层 BootStrap
,开始BootStrap
先找,没有找到就轮到 ExtClassLoader
找,找不到就轮到 AppClassLoader
找,找不到就抛异常JdkClassLoader.java
的路径后,ExtClassLoader
可以获取System.out.println(JdkClassLoader.class.getClassLoader().getClass().getName());
class
文件的显示加载与隐式加载(JVM 加载 class 文件到内存的方式)ClassLoader
加载class
对象,如直接使用Class.forName(name)
或this.getClass().getClassLoader().loadClass()
加载class
对象ClassLoader
的方法加载class
对象,而是通过虚拟机自动加载到内存中,如在加载某个类的class
文件时,该类的class
文件中引用了另外一个类的对象,此时额外引用的类将通过JVM
自动加载到内存中。ClassLoader
loadClass()
或者 findClass()
loadClass()
:打破委托机制。loadClass()内部会去找父类委托,然后再找自己,调用findClass()方法。不建议。findClass()
:继承委托机制。先让父加载器运行,然后自己运行definClass()
:实现把得到的 class 文件转换成字节码public class CustomClassLoaderAttachment extends Date {
private String name;
public CustomClassLoaderAttachment(String name) {
this.name = name;
}
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("CustomClassLoaderAttachment{");
sb.append("name='").append(name).append('\'');
sb.append('}');
return sb.toString();
}
}
public class CustomClassLoader extends ClassLoader {
/**
* 类的加载路径
*/
private String classPath;
/**
* 类加载器的名字
*/
private String name;
public CustomClassLoader() {
}
public CustomClassLoader(String classPath, String name) {
this.classPath = classPath;
this.name = name;
}
/**
* 重写 loadClass() 方法,打破委托机制
*
* @param name
* @return
* @throws ClassNotFoundException
*/
@Override
public Class> loadClass(String name) throws ClassNotFoundException {
// loadClass() 方法会去 父类委托,然后再找自己,调用findClass()方法
return super.loadClass(name);
}
/**
* 重写 findClass() 方法,先让父类树处理,然后自己处理,继承了委托机制
*
* @param name
* @return
*/
@Override
protected Class> findClass(String name) {
String classFileName = classPath + name + ".class";
try {
FileInputStream fis = new FileInputStream(classFileName);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
cypher(fis, bos);
fis.close();
byte[] bytes = bos.toByteArray();
return defineClass(null, bytes, 0, bytes.length);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
*
*
* @param ips
* @param ops
*/
private static void cypher(InputStream ips, OutputStream ops) {
int b;
try {
while ((b = ips.read()) != -1) {
ops.write(b);
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws Exception {
CustomClassLoader classLoader = new CustomClassLoader("/WorkSpace/Sam/Learning/Learning-JavaOptimize/Java-JDK/target/classes/com/learning/optimize/jdk/classloader/", "CustomClassLoader");
Class> clazz = classLoader.loadClass("CustomClassLoaderAttachment");
System.out.println(clazz.getClassLoader().getClass().getName());
Constructor> constructor = clazz.getConstructor(String.class);
Date attachment = (Date) constructor.newInstance("名字");
System.out.println(attachment);
}
/*
* 输出:
com.learning.optimize.jdk.classloader.CustomClassLoader
CustomClassLoaderAttachment{name='名字'}
*/
}
ParallelWebappClassLoader
URLClassLoader
是JDK实现的自定义类加载器@WebServlet(name = "ServletClassLoader", value = {"/servletClassLoader"})
public class ServletClassLoader extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html");
PrintWriter out = resp.getWriter();
//系统加载器
out.println("----" + ClassLoader.getSystemClassLoader().getClass().getName());
//循环遍历加载器
ClassLoader loader = this.getClass().getClassLoader();
while (loader != null) {
out.println(loader.getClass().getName() + "
");
loader = loader.getParent();
}
out.println(loader);
out.flush();
out.close();
}
/*
sun.misc.Launcher$AppClassLoader org.apache.catalina.loader.ParallelWebappClassLoader
java.net.URLClassLoader
sun.misc.Launcher$AppClassLoader
sun.misc.Launcher$ExtClassLoader
null
*/
}
输出:
sun.misc.Launcher$AppClassLoader org.apache.catalina.loader.ParallelWebappClassLoader
java.net.URLClassLoader
sun.misc.Launcher$AppClassLoader
sun.misc.Launcher$ExtClassLoader
null
ClassLoader
类加载加载path
:路径不能以 ‘/’ 开头ClassPath
根路径下获取// 默认则是从 ClassPath 根下获取,path不能以 '/' 开头,最终是由 ClassLoader 获取资源
InputStream ips2 = ClassLoaderLoadResource.class.getClassLoader().getResourceAsStream("config2.properties");
Class
类加载ClassPath
下获取资源,与 ClassLoader
类加载加载一样// path 不以 '/' 开头,就是针对当前的类。是从此类所在的包下取资源
// path 以 '/' 开头,则是从 ClassPath(Src根目录) 下获取资源
InputStream ips3 = ClassLoaderLoadResource.class.getResourceAsStream("config2.properties");
ClassPathResource
就是根据类加载器与 Class类的资源获取功能实现的