虚拟机中类的加载器和双亲委托模型

类加载的过程:

.java文件由java编译器编译成.class文件。.class保存着java代码经过转换后的虚拟机指令。当需要使用某个类,虚拟机加载相应的.class文件,并创建对应的class对象,将class文件加载到虚拟机的内存,这个过程称为类加载。

加载(Loading)->[验证(verification)-准备(preparation)-解析(resolution)]->初始化(Initialization)

加载:就是查找到到这个类的.class文件,利用.class文件创建class对象

验证:目的在于确保Class文件的字节流中包含信息符合当前虚拟机要求,不会危害虚拟机自身安全。主要包括四种验证,文件格式验证,元数据验证,字节码验证,符号引用验证。

准备:就是给类变量赋初值(如static int i=5;这里只将i初始化为0,至于5的值将在初始化时赋值)。

解析:将常量池中的符号引用替换为直接引用,就是把这些引用指向具体的对象的过程。

初始化:类加载最后阶段,若该类具有超类,则对其进行初始化,执行静态初始化器和静态初始化成员变量(如前面只初始化了默认值的static变量将会在这个阶段赋值,成员变量也将被初始化)。

虚拟机中的加载器:

虚拟机提供了3种类加载器,引导(Bootstrap)类加载器、扩展(Extension)类加载器、系统(System)类加载器(也称应用类加载器)

一般默认都是应用类加载器加载

 

        Class cls=Class.forName("LearnJava.Parent");
        ClassLoader classLoader=cls.getClassLoader();
        ClassLoader sys=ClassLoader.getSystemClassLoader();
        ClassLoader ext=sys.getParent();
        ClassLoader boot=ext.getParent();
        System.out.println(sys);
        System.out.println(ext);
        System.out.println(boot);
console:
sun.misc.Launcher$AppClassLoader@2a139a55
sun.misc.Launcher$ExtClassLoader@7852e922
null

 

当到达启动类加载器时候为什么是null呢,启动类加载器是在JVM内部通过C/C++实现的,并不是Java,自然也就不能继承ClassLoader类,自然就不能输出其名称。

同样的System.out.println("List类的加载器的名称:"+List.class.getClassLoader());这种也是null和上面一个原因。

 

自定义加载器的加载过程:

bootstrap ClassLoader(负责加载 %JAVA_HOME%/lib目录下的jar包和类或者或被 -Xbootclasspath参数指定的路径中的所有类)

extension ClassLoader(负责加载目录 %JRE_HOME%/lib/ext 目录下的jar包和类,或被 java.ext.dirs 系统变量所指定的路径下的jar包)

App ClassLoader(面向我们用户的加载器,负责加载当前应用classpath下的所有jar包和类)

自定义的加载器(加载用户指定目录下的class”)

 

往往从右往左检查类是否已经加载,从左往右去尝试加载类。

上面这句话的过程就是双亲委派模型:

双亲委派模型的工作过程:

(1)当前类加载器从自己已经加载的类中查询是否此类已经加载,如果已经加载则直接返回原来已经加载的类。

(2)如果没有找到,就去委托父类加载器去加载(如代码c = parent.loadClass(name, false)所示)。父类加载器也会采用同样的策略,查看自己已经加载过的类中是否包含这个类,有就返回,没有就委托父类的父类去加载,一直到启动类加载器。因为如果父加载器为空了,就代表使用启动类加载器作为父加载器去加载。(对应上面从右往左)

(3)如果启动类加载器加载失败(例如在$JAVA_HOME/jre/lib里未查找到该class),会使用拓展类加载器来尝试加载,继续失败则会使用AppClassLoader来加载,继续失败则会抛出一个异常ClassNotFoundException,然后再调用当前加载器的findClass()方法()这个是自己定义的类加载器,里面是要重写findClass方法的)进行加载,如果有自定义的类加载器就一定要重写这个方法。(对应上面从左往右)

 

双亲委派模型源码:

public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }
protected synchronized Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
    {
    // First, check if the class has already been loaded
    Class c = findLoadedClass(name);//能找到直接返回
    if (c == null) {
        try {
        if (parent != null) {
            c = parent.loadClass(name, false);//去父加载器里面找
        } else {
            c = findBootstrapClassOrNull(name);//到达bootstrap了
        }
        } 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.
            c = findClass(name);
        }
    }
    if (resolve) {
        resolveClass(c);
    }
    return c;
}

重写自己的加载器

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.WritableByteChannel;
 
public class MyClassLoader extends ClassLoader
{
    public MyClassLoader()
    {
        
    }
    
    public MyClassLoader(ClassLoader parent)
    {
        super(parent);
    }
    
    protected Class<?> findClass(String name) throws ClassNotFoundException
    {
//主要是这个地方 File file
= new File("D:/People.class"); try{ byte[] bytes = getClassBytes(file); //defineClass方法可以把二进制流字节组成的文件转换为一个java.lang.Class Class<?> c = this.defineClass(name, bytes, 0, bytes.length); return c; } catch (Exception e) { e.printStackTrace(); } return super.findClass(name); } private byte[] getClassBytes(File file) throws Exception { // 这里要读入.class的字节,因此要使用字节流 FileInputStream fis = new FileInputStream(file); FileChannel fc = fis.getChannel(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); WritableByteChannel wbc = Channels.newChannel(baos); ByteBuffer by = ByteBuffer.allocate(1024); while (true){ int i = fc.read(by); if (i == 0 || i == -1) break; by.flip(); wbc.write(by); by.clear(); } fis.close(); return baos.toByteArray(); } }
主函数里面

MyClassLoader mcl = new MyClassLoader();

Class<?> clazz = Class.forName("People", true, mcl);//指定加载器
 

为什么需要双亲委托模型:简单来说就是使用正确的类加载器去加载相应的类

   假设黑客自定义一个java.lang.String类,该String类具有系统的String类一样的功能,只是在某个函数稍作修改。比如equals函数,这个函数经常使用,如果在这这个函数中,黑客加入一些“病毒代码”。并且通过自定义类加载器加入到JVM中。此时,如果没有双亲委派模型,那么JVM就可能误以为黑客自定义的java.lang.String类是系统的String类,导致“病毒代码”被执行。但因为双亲委托模型的存在,黑客自定义的java.lang.String类永远都不会被加载进内存。因为首先是最顶端的类加载器加载系统的java.lang.String类,最终自定义的类加载器无法加载java.lang.String类。

   假设我们用自定义的类加载器强制加载自己定义的类,这样确实可以成功加载,但我们使用不了,因为在JVM中,我们判断两个对象是不是相同类型,我们还会去判断他们的加载器是不是同一个类型,不是的话我们也会返回false。

   JVM 区分不同类的方式不仅仅根据类名,相同的类文件被不同的类加载器加载产生的是两个不同的类),也保证了 Java 的核心 API 不被篡改。

参考:

https://www.cnblogs.com/wxd0108/p/6681618.html

https://blog.csdn.net/zhangliangzi/article/details/51338291

https://blog.csdn.net/SEU_Calvin/article/details/52315125

去破坏双亲委派机制则不仅要继承ClassLoader类,还要重写loadClass和findClass方法

除了重写findClass方法外还重写了loadClass方法,默认的loadClass方法是实现了双亲委派机制的逻辑,即会先让父类加载器加载,当无法加载时才由自己加载。这里为了破坏双亲委派机制必须重写loadClass方法,即这里先尝试交由System类加载器加载,加载失败才会由自己加载。它并没有优先交给父类加载器,这就打破了双亲委派机制。

tomcat违背了双亲委托模型:也是通过上面这个方法实现的

https://www.cnblogs.com/aspirant/p/8991830.html

posted @ 2018-12-11 15:04  LeeJuly  阅读(156)  评论(0)    收藏  举报