java类加载以及双亲委派机制

简介: web容器要支持jsp的修改,我们知道,jsp 文件最终也是要编译成class文件才能在虚拟机中运行,但程序运行后修改jsp已,经是司空见惯的事情,web容器要支持jsp的修改后不用重启。

java类加载以及双亲委派机制

java类加载运行过程



类加载过程 (classLoader.loadClass)



加载: 在硬盘上查找并通过IO读入字节码文件,使用到类时才会加载,例如调用类的main()方法,new对象等等,在加载阶段会在内存中生成一个代表这个类的

java.lang.Class对象,作为方法区这个类的各种数据的访问入口


验证: 校验字节码文件的正确性


准备: 给类的静态变量分配内存,并赋予默认值


解析: 将符号引用替换为直接引用,该阶段会把一些静态方法(符号引用,比如main()方法)替换为指向数据所存内存的指针或句柄等(直接引用),这是所谓的静态链接过程(类加载期间完成),动态链接是在程序运行期间完成的将符号引用替换为直接引用。(将符号转成内存地址的过程)


初始化: 对类的静态变量初始化为指定的值,执行静态代码块


类加载器和双亲委派机制

上面的类加载过程主要是由类加载器来实现的,java里有如下几种加载器


1.引导类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的核心类库,比如rt.jar、charsets.jar等

2.扩展类加载器(ExtClassLoader): 负责加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的JAR类包

3.应用程序类加载器(AppClassLoader): 负责加载ClassPath路径下的类包,主要就是加载你自己写的那些类

4.自定义加载器: 负责加载用户自定义路径下的类包


常见的类加载器


public static void main(String[] args) {
    //引导类加载器
    System.out.println(String.class.getClassLoader());
    //应用程序类加载器
    System.out.println(TestClassLoader.class.getClassLoader());  
    //扩展类加载器
    System.out.println(com.sun.crypto.provider.DESKeyFactory.class.getClassLoader()); 
}


null
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@77459877


结论:
  1. 由于引导类加载器是c++实现,java层面是获取不到引导类加载器的对象,所以String类的加载器为null
  2. AppClassLoader和ExtClassLoader都是com.misc.Launcher的静态类


关于ClassLoader的 parent 属性
public abstract class ClassLoader {
    //ClassLoader里面定义了一个parent属性,用于标识父类加载器
    private final ClassLoader parent;
}


关于URLClassLoader



1.URLClassLoader继承SecureClassLoader,

2.SecureClassLoader继承ClassLoader

3.AppClassLoader和ExtClassLoader都继承了URLClassLoader


类加载过程中Launcher.getLauncher()


public class Launcher {
    private static URLStreamHandlerFactory factory = new Factory();
    //具体new Launcher()方法
    private static Launcher launcher = new Launcher();
    private static String bootClassPath = System.getProperty("sun.boot.class.path");
    //默认的类加载器,在初始化的时候loader的值被赋予应用程序类加载器
    private ClassLoader loader;
    private static URLStreamHandler fileHandler;
  //返回launcher对象
    public static Launcher getLauncher() {
        return launcher;
    }
}
new Launcher()
  public Launcher() {
        ExtClassLoader extClassLoader;
        try {
            //获取扩展类加载器
            extClassLoader = Launcher.ExtClassLoader.getExtClassLoader();
        } catch (IOException var10) {
            throw new InternalError("Could not create extension class loader", var10);
        }

        try {
            //应用程序类加载器
            this.loader = Launcher.AppClassLoader.getAppClassLoader(extClassLoader);
        } catch (IOException var9) {
            throw new InternalError("Could not create application class loader", var9);
        }

        Thread.currentThread().setContextClassLoader(this.loader);
        String var2 = System.getProperty("java.security.manager");
        if (var2 != null) {
            SecurityManager var3 = null;
            if (!"".equals(var2) && !"default".equals(var2)) {
                try {
                    var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
                } catch (IllegalAccessException var5) {
                } catch (InstantiationException var6) {
                } catch (ClassNotFoundException var7) {
                } catch (ClassCastException var8) {
                }
            } else {
                var3 = new SecurityManager();
            }

            if (var3 == null) {
                throw new InternalError("Could not create SecurityManager: " + var2);
            }

            System.setSecurityManager(var3);
        }

    }

关于new URLClassLoader()


  public URLClassLoader(URL[] urls, ClassLoader parent,
                          URLStreamHandlerFactory factory) {
      //指定父类加载器
        super(parent);
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkCreateClassLoader();
        }
        acc = AccessController.getContext();
        ucp = new URLClassPath(urls, factory, acc);
    }

获取扩展类加载器



  public static ExtClassLoader getExtClassLoader() throws IOException {
            if (instance == null) {
                Class var0 = ExtClassLoader.class;
                synchronized(ExtClassLoader.class) {
                    if (instance == null) {
                        //往下走
                        instance = createExtClassLoader();
                    }
                }
            }

            return instance;
  }


  private static ExtClassLoader createExtClassLoader() throws IOException {
            try {
                return (ExtClassLoader)AccessController.doPrivileged(new PrivilegedExceptionAction<ExtClassLoader>() {
                    public ExtClassLoader run() throws IOException {
                        //获取系统扩展类jar包的文件
                        File[] var1 = Launcher.ExtClassLoader.getExtDirs();
                        int var2 = var1.length;

                        for(int var3 = 0; var3 < var2; ++var3) {
                            MetaIndex.registerDirectory(var1[var3]);
                        }

                        return new ExtClassLoader(var1);
                    }
                });
            } catch (PrivilegedActionException var1) {
                throw (IOException)var1.getException();
            }
  }


    public ExtClassLoader(File[] extJarFiles) throws IOException {
          //初始化父类URLClassLoader的相关逻辑,
          //其中第二个参数为指定父加载器,此处父加载器应该是引导类加载器,但是java层面获取不到,所以传null
          super(getExtURLs(extJarFiles), (ClassLoader)null, Launcher.factory);
          SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);
    }

获取应用程序类加载器

  //获取应用类加载器,这里classerLoader是上一步获取的扩展类加载器
  public static ClassLoader getAppClassLoader(final ClassLoader classLoader) throws IOException {
        //获取程序的类路径定义信息,类似classpath的地址
            final String classPath = System.getProperty("java.class.path");
            final File[] var2 = classPath == null ? new File[0] : Launcher.getClassPath(classPath);
            return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction<AppClassLoader>() {
                public AppClassLoader run() {
                    URL[] var1x = var1 == null ? new URL[0] : Launcher.pathToURLs(var2);
                    return new AppClassLoader(var1x, classLoader);
                }
            });
  }



  AppClassLoader(URL[] var1, ClassLoader classLoader) {
        /初始化父类URLClassLoader的相关逻辑,
          //其中第二个参数为指定父加载器,这里将扩展类加载器指定为应用程序类加载器的父加载器(不是父类,他们是平级的)
            super(var1, classLoader, Launcher.factory);
            this.ucp.initLookupCache(this);
  }

各类加载器加载的类地址

  public static void main(String[] args) {
        ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
        ClassLoader extClassLoader = appClassLoader.getParent();
        ClassLoader bootstrapClassLoader = extClassLoader.getParent();

        System.out.println(appClassLoader);
        System.out.println(extClassLoader);
        System.out.println(bootstrapClassLoader);

        System.out.println();

        System.out.println("bootstrapClassLoader加载路径:");
        bootstrapClassLoader加载路径:
        //file:/C:/application/java/jre/lib/resources.jar
        //file:/C:/application/java/jre/lib/rt.jar
        //file:/C:/application/java/jre/lib/sunrsasign.jar
        //file:/C:/application/java/jre/lib/jsse.jar
        //file:/C:/application/java/jre/lib/jce.jar
        //file:/C:/application/java/jre/lib/charsets.jar
        //file:/C:/application/java/jre/lib/jfr.jar
        //file:/C:/application/java/jre/classes
        URL[] urLs = Launcher.getBootstrapClassPath().getURLs();
        for (URL urL : urLs) {
            System.out.println(urL);
        }

        System.out.println();
        System.out.println("extClassLoader加载路径:");
        //C:\application\java\jre\lib\ext;C:\WINDOWS\Sun\Java\lib\ext
        System.out.println(System.getProperty("java.ext.dirs"));


        System.out.println();
        System.out.println("appClassLoader加载路径:");
        //这里也会打印出ext和bootstrap的类路径地址,但appClassLoader不会去加载这些类。因为双亲委派机制
        //appClassLoader主要加载target目录下的类
        System.out.println(System.getProperty("java.class.path"));

    }
双亲委派机制
JVM类加载器的亲子层结构图




加载流程:


1.当需要加载Some.class时,首先由appClassLoader查找自身有没有加载过该类,如果有,则直接返回,如果没有,则委托上一级加载

2.extClassLoader同样查找自身有没有加载过该类,如果有,则直接返回,如果没有,则委托上一级加载

3.bootstrapClassLoader同样查找自身有没有加载过该类,如果有,则直接返回,如果没有,则试着去加载。如果在自己类库中找不到该类,则加载不成功,将会委托下一级classLoader去加载该类。


4.extClassLoader尝试加载该类,如果在自己类库中找不到该类,则加载不成功,将会委托下一级classLoader去加载该类。

5.appClassLoader在自己目录下,也就是target目录下查找该类,找到,并加载执行该类。


双亲委派机制简单点就是,先找父加载器加载,不行再由子加载器加载


源码


当需要加载一个自定义Some.class时,从应用程序类加载器开始


ClassLoader关于类加载的方法,所有的类加载器均继承了该方法


1.查找当前加载器是否加载过该类(findLoadedClass)


  //查找当前类加载器是否加载过该类,如果有,直接返回,如果没有,返回null
  protected final Class<?> findLoadedClass(String name) {
        if (!checkName(name))
            return null;
        return findLoadedClass0(name);
    }

  //C++方法
  private native final Class<?> findLoadedClass0(String name);
  1. 查找引导类加载器是否加载过该类(findBootstrapClassOrNull)
private Class<?> findBootstrapClassOrNull(String name){
        if (!checkName(name)) return null;

        return findBootstrapClass(name);
}

//C++方法,查找引导类加载器是否加载过该类
private native Class<?> findBootstrapClass(String name);
  1. findClass()方法


从URL搜索路径中通过指定的类名称查找并加载类。


加载步骤:


  1. appClassLoader
  //appClasssLoader的loadClass方法
  public Class<?> loadClass(String var1, boolean resolve) throws ClassNotFoundException {
            int var3 = var1.lastIndexOf(46);
            if (var3 != -1) {
                SecurityManager var4 = System.getSecurityManager();
                if (var4 != null) {
                    var4.checkPackageAccess(var1.substring(0, var3));
                }
            }

            if (this.ucp.knownToNotExist(var1)) {
                //appClassLoader尝试着从自己已经加载过的类中查找该类。
                Class var5 = this.findLoadedClass(var1);
                if (var5 != null) {
                    if (resolve) {
                        this.resolveClass(var5);
                    }

                    return var5;
                } else {
                    throw new ClassNotFoundException(var1);
                }
            } else {
                //调用URLClassLoader加载loadClass,注意这里super不是extClassLoader,这个是继承关系,找的是父类方法。
                //extClassLoader是appClassLoader的父加载器
                return super.loadClass(var1, resolve);
            }
      }


  1. urlClassLoader
public final Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        // First check if we have permission to access the package. This
        // should go away once we've added support for exported packages.
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            int i = name.lastIndexOf('.');
            if (i != -1) {
                sm.checkPackageAccess(name.substring(0, i));
            }
    }
      //classLoader.loadClass();
        return super.loadClass(name, resolve);
}
  1. ClassLoader


protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            //第一步,从已经加载过的class中找,注意这里的this对象是appClassLoader
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    //此时parent是extClassLoader
                    if (parent != null) {
                        //向extClassLoader委托加载类
                        c = parent.loadClass(name, false);
                    } else {
                        //extClassLoader的parent为bootstrapClassLoader,但是java获取不到,所以为null,
                        //此时查找引导类加载器是否加载过该类
                        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();
                    //extClassLoader尝试加载该类,如果没有加载,则c为null,交由下一级类加载器,也就是appClassLoader尝试加载
                    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;
        }
    }


为什么要设计双亲委派机制


1.沙箱安全机制: 自己写的java.lang.String.class类不会被加载,这样防止核心api库被随意篡改(核心类库永远都是bootstrap加载,所以appClassLoader无法加载和核心类库全路径一样的类)


2.避免类的重复加载:当父加载器已经加载了该类,就没有必要子类加载器再加载一次,保证被加载的类的唯一性


全盘委托机制


全盘负责是指当一个ClassLoader装在一个类时,除非显示的使用另外一个ClassLoader,该类所依赖及引用的类也由这个ClassLoader载入。


自定义类加载器


自定义类加载器只需要继承 java.lang.ClassLoader 类,该类有两个核心方法,一个是loadClass(Sting,boolean),实现了双亲委派机制,还有一个

方法是findClass,默认实现是空方法,所以我们自定义类加载器主要是重写findclass方法。


自定义类加载的器的父加载器默认为appClassLoader,这样默认满足双亲委派机制。


打破双亲委派机制


以Tomcat类加载为例,Tomcat 如果使用默认的双亲委派类加载机制行不行?


我们思考一下:Tomcat是个web容器,那么它要解决什么问题:


1.一个web容器可能需要部署两个应用程序,不同的应用程序可能会依赖同一个第三方类库的不同版本,不能要求同一个类库在同一个服务器只有一份,因此要保证每个应用程序的类都是独立,保证相互隔离。


2.部署在同一个web容器中相同的类库相同的版本可以共享。否则,如果服务器有10个应用程序,那么要有10份相同的类库加进虚拟机。


3.web容器也有自己依赖的类库,不能与应用程序的类库混淆。基于安全考虑,应该让容器的类库和程序的类库隔离开来。


4.web容器要支持jsp的修改,我们知道,jsp 文件最终也是要编译成class文件才能在虚拟机中运行,但程序运行后修改jsp已,经是司空见惯的事情,web容器要支持jsp的修改后不用重启。



目录
相关文章
|
8天前
|
Java
并发编程的艺术:Java线程与锁机制探索
【6月更文挑战第21天】**并发编程的艺术:Java线程与锁机制探索** 在多核时代,掌握并发编程至关重要。本文探讨Java中线程创建(`Thread`或`Runnable`)、线程同步(`synchronized`关键字与`Lock`接口)及线程池(`ExecutorService`)的使用。同时,警惕并发问题,如死锁和饥饿,遵循最佳实践以确保应用的高效和健壮。
24 2
|
8天前
|
Java 数据安全/隐私保护
深入剖析:Java Socket编程原理及客户端-服务器通信机制
【6月更文挑战第21天】Java Socket编程用于构建网络通信,如在线聊天室。服务器通过`ServerSocket`监听,接收客户端`Socket`连接请求。客户端使用`Socket`连接服务器,双方通过`PrintWriter`和`BufferedReader`交换数据。案例展示了服务器如何处理每个新连接并广播消息,以及客户端如何发送和接收消息。此基础为理解更复杂的网络应用奠定了基础。
|
2天前
|
存储 缓存 监控
Java中的数据一致性与分布式锁机制
Java中的数据一致性与分布式锁机制
|
5天前
|
Java
Java中的`synchronized`关键字是一个用于并发控制的关键字,它提供了一种简单的加锁机制来确保多线程环境下的数据一致性。
【6月更文挑战第24天】Java的`synchronized`关键字确保多线程数据一致性,通过锁定代码块或方法防止并发冲突。同步方法整个方法体为临界区,同步代码块则锁定特定对象。示例展示了如何在`Counter`类中使用`synchronized`保证原子操作和可见性,同时指出过度使用可能影响性能。
19 4
|
2天前
|
存储 安全 Java
Java内省(Introspector)机制:深入理解与应用
Java内省(Introspector)机制:深入理解与应用
|
3天前
|
存储 监控 Java
深入理解Java虚拟机-类加载连接和初始化解析
深入理解Java虚拟机-类加载连接和初始化解析
|
5天前
|
安全 Java 程序员
在Java中,finalization是一种机制,允许对象在被垃圾收集器回收之前执行一些清理操作。
【6月更文挑战第24天】Java中的finalization机制允许对象在被垃圾收集前执行清理,以释放系统资源或处理敏感信息。`finalize()`方法用于定义此类操作,但它不是可靠的资源管理策略,因为调用时机不确定且可能影响性能。尽管可用于清理外部资源或作为保护措施,但应避免依赖finalization,而应优先采用手动资源管理,遵循“创建者负责”原则。
10 1
|
23小时前
|
安全 Java 数据库连接
Java中的反射机制详解:动态类加载与调用
Java中的反射机制详解:动态类加载与调用
|
1天前
|
SQL 缓存 Java
Java框架之MyBatis 07-动态SQL-缓存机制-逆向工程-分页插件
Java框架之MyBatis 07-动态SQL-缓存机制-逆向工程-分页插件
|
1天前
|
存储 安全 Java
Java泛型:深度解析编译时类型安全的核心机制
【6月更文挑战第28天】Java泛型自JDK 1.5起增强了代码安全与复用。它们允许类、接口和方法使用类型参数,如`&lt;T&gt;`在`Box&lt;T&gt;`中。泛型确保编译时类型安全,例如`List&lt;String&gt;`防止了运行时ClassCastException。尽管运行时存在类型擦除,编译时检查仍保障安全。理解泛型核心机制对于优化Java编程至关重要。