《深入理解JVM》第七章 类加载器

版权声明:版权为ZZQ所有 https://blog.csdn.net/qq_39148187/article/details/81977724

虚拟机设计团队把类加载阶段中的“通过一个类的全限类名”来捕获描述这个类的二进制字节流,这个动作放入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 连同的类加载器一起换掉实现热部署

实际上原理就是,双亲委派模型不是如果加载了就不会在加载,我们直接把他的加载器就换掉,这样不就能重新 加载? 

猜你喜欢

转载自blog.csdn.net/qq_39148187/article/details/81977724