一、双亲委派机制
由于)va虚拟机中有多个类加载器,双亲委派机制的核心是解决一个类到底由谁加载的问题。
1.1 双亲委派的作用
- 保证类加载的安全性:
- 通过双亲委派机制避免恶意代码替换JDK中的核心类库,比如java.lang.String,确保核心类库的完整性和安全性。
- 避免重复加载:
- 双亲委派机制可以避免同一个类被多次加载。
1.2 什么是双亲委派机制
- 双亲委派机制指的是:当一个类加载器接收到加载类的任务时,会自底向上查找是否加载过,再由顶向下进行加载。
假设在com.practice包下有一个类A.java,应用程序类加载器接收到一个任务,他要去加载A.java这个类,首先他会检查一下这个类有没有被加载过,发现没有被加载过,那么他就会把这个类委派给它的父类(扩展类加载器),扩展类加载器发现也没有加载过,继续向上委派,委派给它的父亲(启动类加载器),启动类加载器发现A.java曾经加载过,所以它直接把A.java类的class对象返回,加载过程结束。
如果所有的父类加载器都无法加载该类,则由当前类加载器自己尝试加载。所以看上去是自顶向下尝试加载。
假设在com.practice包下有一个类B.java,三个类加载器都没有加载过,此时会首先从顶部(启动类加载器)尝试进行加载,发现这个类不在当前类加载器的加载路径当中,就把这个任务委派给它的下级(扩展类记载器),此时刚好有这个类的加载路径,就会进行加载,而当应用类加载器要加载B.java时,首先还是对他的父类进行查找,判断是否加载过,加载过,则返回这个类的class对象
向下委派起到了一个加载优先级的作用
1.3 双亲委派机制问题
- 重复的类:
- 如果一个类重复出现在三个类加载器的加载位置,应该由谁来加载?
答案应该是启动类加载器加载,因为根据双亲委派机制,它的优先级是最高的
- String类能覆盖吗
- 在自己的项目中去创建一个java.lang.String类,会被加载吗
不能,会返回启动类加载器加载在rt.jar包中的String类
1.4 面试题(类的双亲委派机制是什么)
1、当一个类加载器去加载某个类的时候,会自底向上向父类查找是否加载过,如果加载过就直接返回,如果一直到最顶层的类加载器都没有加载,再由顶向下进行加载。
2、应用程序类加载器的父类加载器是扩展类加载器,扩展类加载器的父类加载器是启动类加载器。
3、双亲委派机制的好处有两点:第一是避免恶意代码替换DK中的核心类库,比如java.lang.String,确保核心类库的完整性和安全性。第二是避免一个类重复地被加载
二、打破双亲委派机制
2.1 自定义类加载器
- 双亲委派机制的核心代码(源码):
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; } }
- 自定义类加载器:
public class BreakClassLoader1 extends ClassLoader { private String basePath; private final static String FILE_EXT = ".class"; public void setBasePath(String basePath) { this.basePath = basePath; } private byte[] loadClassDate(String name) throws IOException { String path = basePath + File.separatorChar + name.replace('.', File.separatorChar) + FILE_EXT; // 将包名转换为文件路径 InputStream is = null; ByteArrayOutputStream baos = null; try { is = new FileInputStream(path); baos = new ByteArrayOutputStream(); int bufferSize = 4096; byte[] buffer = new byte[bufferSize]; int bytesNumRead = 0; while ((bytesNumRead = is.read(buffer)) != -1) { baos.write(buffer, 0, bytesNumRead); } return baos.toByteArray(); } catch (IOException e) { e.printStackTrace(); } finally { try { if (is != null) { is.close(); } if (baos != null) { baos.close(); } } catch (IOException e) { e.printStackTrace(); } } return null; } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { byte[] classDate = new byte[0]; try { classDate = loadClassDate(name); } catch (IOException e) { e.printStackTrace(); } if (classDate == null) { throw new ClassNotFoundException(); } else { return defineClass(name,classDate,0,classDate.length); } } public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException { BreakClassLoader1 classLoader1 = new BreakClassLoader1(); classLoader1.setBasePath("D:\\lib\\"); Class<?> clazz1 = classLoader1.loadClass("com.practice.Student"); clazz1.newInstance(); System.out.println(clazz1.getClassLoader()); } }