vlambda博客
学习文章列表

java加载类为什么要设计“双亲委派机制”?

点击蓝字获取更多精彩信息


直接上答案:

1、安全机制:自己写的java.lang.String.class类不会被加载,这样便可以防止核心 API库被随意篡改

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


首先:
解释之前,我们先来看一张图

java加载类为什么要设计“双亲委派机制”?

其次:
为了解答问题,这里借类加载的关键代码复习一下,请见如下代码

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(); } } // 解析class文件,就是将符号引用替换为直接引用的过程 if (resolve) { resolveClass(c); } return c; } }


  1. 首先,检查一下指定名称的类是否已经加载过,如果已经加载过了,就不需要再加载,直接 返回。

  2. 如果此类没有加载过,那么,再判断一下是否有父加载器;如果有父加载器,则由父加 载器加载(即调用parent.loadClass(name, false);),或者是调用bootstrap类加载器来加 载。

  3. 如果父加载器及bootstrap类加载器都没有找到指定的类,那么用当前类加载器的 findClass方法来完成类加载。

为了进一步证明双亲委派的作用,我们自己写一个String类,并且包名是一致的,看会不会被加载。

package java.lang;

public final class String implements java.io.Serializable, Comparable<String>, CharSequence{ @Override public int length() { return 0; }

@Override public char charAt(int index) { return 0; }

@Override public CharSequence subSequence(int start, int end) { return null; }

@Override public int compareTo(String o) { return 0; }

public static void main(String[] args) { System.out.println("测试类加载机制"); }}


执行main方法,结果出现以下报错

说明:jvm加载的并不是我们自己写的String类,而是jdk中的String类,所以这个测试证明了类加载有双亲委派机制的存在,同时证明双亲委派机制的其中一个目的就是确保Jdk的核心API的安全,不被篡改,以免造成严重的后果

有同学会说,我如果自己写一个自定义加载器,岂不是就可以打破“安全机制了”,那我们自己写一个加载器来加载我们自己写的String类吧,

自定义加载器如下:

package com.kuya123;

import java.io.FileInputStream;import java.lang.reflect.Method;

public class CrossClassLoaderTest { static class CrossClassLoader extends ClassLoader {

private String classPath;

public CrossClassLoader(String classPath) { this.classPath = classPath; }

private byte[] loadByte(String name) throws Exception { name = name.replaceAll("\\.", "/"); FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class"); int len = fis.available(); byte[] data = new byte[len]; fis.read(data); fis.close(); return data; }



protected Class<?> findClass(String name) throws ClassNotFoundException { try { byte[] data = loadByte(name); return defineClass(name, data, 0, data.length); } catch (Exception e) { e.printStackTrace(); throw new ClassNotFoundException(); } }

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) { // 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.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } if (resolve) { resolveClass(c); } return c; } } }

public static void main(String args[]) throws Exception { CrossClassLoader classLoader = new CrossClassLoader("/Users/chenrui/IdeaProjects/crosstest/target/classes"); //尝试用自己改写类加载机制去加载自己写的java.lang.String.class Class clazz = classLoader.loadClass("java.lang.String"); Object obj = clazz.newInstance(); Method method = clazz.getDeclaredMethod("print", null); method.invoke(obj, null); System.out.println(clazz.getClassLoader().getClass().getName()); }}

执行后,发现java.lang包是被保护的


这就是java安全机制中对于恶意代码所采取的防护措施

第二点原因:
java虚拟机只会在不同的类的类名相同且加载该类的加载器均相同的情况下才会判定这是一个类。如果没有双亲委派机制,同一个类可能就会被多个类加载器加载,如此类就可能会被识别为两个不同的类,相互赋值时问题就会出现

往期回顾

在看的,麻烦点一下再走好吗?