虚拟机设计团队把类加载阶段中的“通过一个类的全限类名”来捕获描述这个类的二进制字节流,这个动作放入jvm 中,实现这个动作的代码叫做类加载器
类加载器可以是java语言的一项创新,也是java 流行的主要原因之一,最初是为了满足java Applet的需求开发出来的,目前java Applet 没人听说话吧? 但是类加载器却在层次上划分,OSGI,热部署,代码加密,等领域大放异彩,成为java 体系统中的重要基石,可谓是失之桑榆,收之东隅 ;
1. 类与类的加载器
每一个加载器都有一个独立的命名空间,意思是,同一个class 被不同的classloader加载,这个两个被加载的类必定不相同
这里相同指的是,equals() isAssignableFrom()方法 isInstanse 返回的结果,
package com.jvm.ClassLoaderDemo;
import com.jvm.HeapOOM;
import java.io.IOException;
import java.io.InputStream;
public class ClassLoaderTest {
public static void main(String[] args) throws Exception {
ClassLoader myLoader = new ClassLoader() {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
try {
String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
InputStream is = getClass().getResourceAsStream(fileName);
if (is == null) {
return super.loadClass(name);
}
byte[] b = new byte[is.available()];
is.read(b);
return defineClass(name, b, 0, b.length);
} catch (IOException e) {
throw new ClassNotFoundException(name);
}
}
};
Object obj = myLoader.loadClass("com.jvm.ClassLoaderDemo.ClassLoaderTest").newInstance();
System.out.println(obj);
System.out.println(obj instanceof com.jvm.ClassLoaderDemo.ClassLoaderTest);
}
}
从上代码我们可以看出虽然同样加载同一个class 文件但是他们却不是同一类的
2.双亲委派模型
https://blog.csdn.net/stonehigher125/article/details/51304370
双亲委派模型的意思呢, 就是有类要加载请求了,先让父类处理,如果父类没有办法处理,抛出异常,这个时候调用自己的findLoadedClass方法,这个就和啃老差不多,
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
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
// to find the class.
long t1 = System.nanoTime();
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;
}
}
这个代码有注释的首先判断这个类型是否被加载,如果加载过了就不会在进行加载相同的类
然后调用父类 把类的路径传递给父类,父类加载,然后父类不能加载给了BootStrapClassLoader 然后如果还是不行抛出异常的话,
然后就字节调用findClass()方法进行加载
这样有个好处,就是如果rt .jar 中已经定义了那些类的话,我们我们写相同的类,他不会被加载,为什么?
因为bootstarp 首先加载了 rt.jar 中的类,如果我们搞相同的话,他会被加载,
从开发人员角度来说,类加载器可以分的更细,绝大部分java 程序都用到3个系统提供的类加载器
1.启动类加载器 (bootstart classloader ) 这个类负责把%JAVA_HOME%bin 下的,或者通过-Xbootclasspath参数指定的路径中的,并且虚拟机识别的类库加载到java虚拟机中,这个是用Cpp语言实现的,是jvm 的一部分,其他类都是用java实现
2.拓展类加载器,这个类有sun.misc.Lanuncher$ExtClassLoader的实现,他负责加载%JAVA_HOME%\bin\ext目录中,或者被java.ext.dirs系统变量所指的路径中所有类库,开发者可以直接使用扩展类加载器
3.应用程序加载类器,这个是由Sun.misc.Lanuncher$App_ClassLoader 实现,这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值他负责加载用户类(ClassPath)路径下类库
3.破坏双亲委派模型
https://blog.csdn.net/qq_27298687/article/details/73565191
https://blog.csdn.net/u013851082/article/details/70214881
所谓破坏就是打破了双亲委派模型,
1. jdk1.2之前实现Classloader 都是要重新Classloader ,之后我们把自己的逻辑卸载了findClass 中,通过loadClass 调用findClass
2.JNDI 服务,JNDI 的目的就是为了对资源的集中管理,查找,他需要调用由独立厂商实现并部署在应用程序的ClassPath,但是启动类不可能认识这些代码,为了解决这个问题,java 团队引入了一个不太优雅的设计,线程上下文加载器,JNDI服务使用这个线程上下文加载所需要的SPI 代码,也就是让父类去请求完成加载动作,这就打破了双亲委派模型,java 中设计SPI 的加载动作都采用这种方式JNDI JDBC JCE JAXB JBI
https://www.cnblogs.com/lovesqcc/p/5229353.html
3.第三次打破双亲委派模型是由于用户对程序动态性追求, 代码热替换 模块热部署
Sun公司提出的JSR-294 JSR 277在于jcp组织的模块化规范之争中败给了JSR-291 (OSGI), 虽然sun 不甘心失去java模块化的主导权,独立发展在Jigsaw,OSGI实现热部署的关键是自定义类加载器机制的实现,每个程序模块(OSGI中称之为Bundle)都有自己的类加载器,如果需要替换一个Bundle,就把Bundle 连同的类加载器一起换掉实现热部署
实际上原理就是,双亲委派模型不是如果加载了就不会在加载,我们直接把他的加载器就换掉,这样不就能重新 加载?