类加载器详解
package com.yang.jvm; import sun.net.spi.nameservice.dns.DNSNameService; public class Test { public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException { System.out.println(System.getProperty("sun.boot.class.path")); //bootStrap启动类的加载路径 System.out.println(System.getProperty("java.ext.dirs")); //拓展类的加载路径 System.out.println(System.getProperty("java.class.path")); //应用类或者说是系统类的加载路径 //因为字节码是使用类加载器加载的,所以每个字节码都会记录加载该字节码的类加载器,所以就可以通过字节码的getClassLoader()获得类加载器 System.out.println(String.class.getClassLoader());//启动类加载器使用的是C或C++语言写的,所以打印启动类加载器都是返回null, System.out.println(DNSNameService.class.getClassLoader());//输出的是拓展类加载器sun.misc.Launcher$ExtClassLoader@2f333739 System.out.println(Test.class.getClassLoader());//应用类加载器sun.misc.Launcher$AppClassLoader@14dad5dc } }
1.类加载器机制:
2. 通过源码看内置类加载器的创建过程,也就是Launcher类,除了启动类加载器是c++写的,ext和app类加载器是java写的:
3.源码查询双亲委派机制:通过app类加载器的load方法
为什么要设计双亲委派机制?
安全机制:自己写的java.lang.String.class类不会被加载,这样便可以防止核心
API库被随意篡改
避免类的重复加载:当父亲已经加载了该类时,就没有必要子ClassLoader再加载一
次,保证被加载类的唯一性
4. 打破双亲委派机制:应用场景:tomcat在部署不同的war包时,每个war包可能拥有相同的类,但版本不同,如war1的spring是4.0版本 war2的版本是5.0版本,如果没打破双亲委派机制,那么
war1如果先加载,委派app类加载器加载了spring4.0的jar包,war2去加载时,委托app类加载器,此时spring4.0已存在,那么spring5.0就不会被加载了
5.类加载器的隔离性:在一个jvm进程中,同个类可以被不同的类加载器加载,虽然他们的包名是一致的,下面自定义类加载器来说明这一点:
a.在2个不同的文件夹下分别放入相同的类F
b.自定义类加载器,并打破双亲委派机制
public class MyClassLoader extends ClassLoader { public String path=""; public MyClassLoader(String path) { super(); this.path=path; } @Override protected Class<?> findClass(String name2) throws ClassNotFoundException { int i = name2.lastIndexOf(".")+1; String className=name2.substring(i); String name=name2.substring(0,i).replace(".","\\")+"\\"+className+".class"; try { FileInputStream fileInputStream = new FileInputStream(new File(path + name)); byte[] data=new byte[fileInputStream.available()]; fileInputStream.read(data); Class<?> aClass = super.defineClass(name2, data, 0, data.length); return aClass; } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return null; } @Override public Class<?> loadClass(String name) throws ClassNotFoundException { //如果是我们自己定义的类,就不委派给父加载器 也就是打破双亲委派机制 if(name.startsWith("com.yang")){ return this.findClass(name); } //其他类的加载委派给父加载器 return super.loadClass(name); } }
执行测试代码:
6. 类加载的过程
验证:将class文件读取后,需要校验格式对不对,如class文件的开头都是cafe baby这几个字母
准备:给静态变量赋默认值,如bolean的默认值是flase
解析:分静态连接和动态连接,类的加载阶段指是的是静态连接,连接是指将符号引用转成直接引用,啥意思:如 main()方法,方法名是一个字符串,
它加载到内存中,至少要有个地方存储它的信息吧,存储就会有个地址,因此这里就是将main()方法这个符号指向内存中的地址
初始化:给静态变量赋真正的值,执行init()方法
7.类的加载是懒加载,也就是用到才会加载:
.
public class F { static { System.out.println("我是F我被加载了"); } } public class F2 { static { System.out.println("我是F2我被加载了"); } } public class KK { public static void main(String[] args) { F f = new F(); F2 f2=null; //不会被加载 } }
8. 全盘负责委托机制:加载一个A类的类加载器,同时也会使用给类加载器加载A类引用的相关类,除非这些类已经明确使用某个类加载器来加载
9. 类加载器和它的父加载器的关系是组合关系而不是继承关系