加载-双亲委派机制类加载的过程是较为复杂的,今天来梳理下面试
如上图,若是一个类收到类加载请求,它并不会本身先去加载类,而是把这个请求委托给父类加载器执行,若是父类加载器还有父类加载器,则会进一步向上委托,依次递归,直到请求到达启动类加载器,若是父类加载器可以完成加载任务,则成功返回,若是父类加载器没法完成加载任务,子类加载器会本身尝试去加载,这就是双亲委派机制数据库
public class ClassLoaderTest { public static void main(String[] args) { // 获取系统类加载器 ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); System.out.println(systemClassLoader); // 获取其上层:扩展类加载器 ClassLoader extClassLoader = systemClassLoader.getParent(); System.out.println(extClassLoader); // 获取其上层:获取不到启动类加载器 ClassLoader bootstrapClassLoader = extClassLoader.getParent(); System.out.println(bootstrapClassLoader); // 对于用户自定义类来讲:默认使用系统类加载器进行加载 ClassLoader classLoader = ClassLoaderTest.class.getClassLoader(); System.out.println(classLoader); // String类使用启动类加载器进行加载的 ClassLoader classLoader1 = String.class.getClassLoader(); System.out.println(classLoader1); // DNSNameService使用扩展类加载器加载 ClassLoader classLoader2 = DNSNameService.class.getClassLoader(); System.out.println(classLoader2); } }
以下,jdk的内置类加载器会默认加载一些jar包bootstrap
public class MyClassLoader extends ClassLoader { private String classLoaderName; private String path; public void setPath(String path) { this.path = path; } public MyClassLoader(String classLoaderName, String path) { // 将系统类加载器看成该类的父加载器 super(); this.classLoaderName = classLoaderName; this.path = path; } public byte[] loadClassData(String name) { System.out.println("abc"); InputStream is = null; byte[] data = null; ByteArrayOutputStream byteArrayOutputStream = null; try { this.classLoaderName = name.replace(".", "/"); String fileExtension = ".class"; is = new FileInputStream(path.concat(classLoaderName).concat(fileExtension)); byteArrayOutputStream = new ByteArrayOutputStream(); int ch = 0; while (-1 != (ch = is.read())) { byteArrayOutputStream.write(ch); } data = byteArrayOutputStream.toByteArray(); } catch (Exception e) { e.printStackTrace(); } finally { try { assert is != null; is.close(); assert byteArrayOutputStream != null; byteArrayOutputStream.close(); } catch (Exception e) { e.printStackTrace(); } } return data; } @Override protected Class<?> findClass(String name) { byte[] data = loadClassData(name); return this.defineClass(name, data, 0, data.length); } }
测试一下,我删除了target目录下的MyTest.class,而后将MyTest.class移动到/Users/zhangxiaobin/Desktop这个目录下,findClass时系统类加载器没有加载到MyTest.class,自定义类加载器就可以加载到这个类了设计模式
public static void main(String[] args) throws Exception { // 定义第一个类加载器 MyClassLoader myClassLoader = new MyClassLoader("myClassLoader", "/Users/zhangxiaobin/Desktop/"); Class<?> clazz = myClassLoader.loadClass("com.example.jvm.MyTest"); Object object = clazz.newInstance(); System.out.println(object); System.out.println(clazz.getClassLoader()); System.out.println(clazz.getClassLoader().getParent()); System.out.println(clazz.hashCode()); // 定义第二个类加载器 MyClassLoader myClassLoader2 = new MyClassLoader("myClassLoader", "/Users/zhangxiaobin/Desktop/"); Class<?> clazz2 = myClassLoader2.loadClass("com.example.jvm.MyTest"); Object object2 = clazz2.newInstance(); System.out.println(object2); System.out.println(clazz.getClassLoader()); System.out.println(clazz.getClassLoader().getParent()); System.out.println(clazz2.hashCode()); }
测试一下,发现自定义了两个类加载器,同一个类被加载了两次,这是由于类加载器有一个命名空间的问题,每一个类加载器都有本身的命名空间,在同一个命名空间中,不会出现类的完整名字(包括类的包名)相同的两个类,在不一样的命名空间中,有可能会出现类的完整名字(包括类的包名)相同的两个类多线程
将target目录下的MyTest.class弄回来,会发现该类是使用了系统类加载器来加载的架构
在类加载器命名空间的限制下,双亲委派机制在某些场景下没法知足咱们的需求,好比SPI机制并发
Java核心类库定义了接口,并未给出实现,这些接口的实现来自不一样的jar包(厂商),好比JDBC,Java核心类库定义了Connection等接口,不一样的厂商有不一样的实现,MySQL、Oracle等等,这些实现是经过jar包的方式加载的,jar包位于ClassPath下。Java核心类库是由启动类加载器加载的,ClassPath下的jar包是由系统类加载器加载的,按照命名空间的规则,他们是不可见的app
办法总比问题多,jdk在双亲委派机制的基础上,新增了线程上下文类加载器,经过给当前线程设置线程上下文类加载器的方式来实现对于接口实现类的加载jvm
这个线程上下文类加载器通常是系统类加载器分布式
// 具体设置的代码在Launcher这个类中 public Launcher() { // Create the extension class loader ClassLoader extcl; try { // 获取扩展类加载器 extcl = ExtClassLoader.getExtClassLoader(); } catch (IOException e) { throw new InternalError( "Could not create extension class loader", e); } // Now create the class loader to use to launch the application try { // 获取系统类加载器 loader = AppClassLoader.getAppClassLoader(extcl); } catch (IOException e) { throw new InternalError( "Could not create application class loader", e); } // 将线程上下文类加载器设置为系统类加载器,此加载器是能够替换的 Thread.currentThread().setContextClassLoader(loader); .... }
SPI机制是经过ServiceLoader这个类进行实现类的加载
public static <S> ServiceLoader<S> load(Class<S> service) { // 能够看到,获取了线程上下文类加载器来加载类 ClassLoader cl = Thread.currentThread().getContextClassLoader(); return ServiceLoader.load(service, cl); }
示意图以下
连接分为三步,分别是验证、准备、解析
验证:确保Class文件中包含的信息符合Java虚拟机的规范
准备:设置类变量的默认初始值,注意:是类变量,而且被final修饰的变量会显式初始化
解析:将符号引用转化为直接引用
初始化此阶段是执行类的初始化器,进行类变量的初始化
public class MyTest2 { public static int a = 10; public static void main(String[] args) { System.out.println(MyTest2.a); } }
以下图,使用jclasslib Bytecode Viewer能够看到的信息,若是整个类没有类变量,是不会出现,若是该类有父类,会执行父类的
一直想整理出一份完美的面试宝典,可是时间上一直腾不开,这套一千多道面试题宝典,结合今年金三银四各类大厂面试题,以及 GitHub 上 star 数超 30K+ 的文档整理出来的,我上传之后,毫无心外的短短半个小时点赞量就达到了 13k,说实话仍是有点难以想象的。
内容涵盖:Java、MyBatis、ZooKeeper、Dubbo、Elasticsearch、Memcached、Redis、MySQL、Spring、SpringBoot、SpringCloud、RabbitMQ、Kafka、Linux等技术栈(485页)
内容涵盖:Java基础、JVM、高并发、多线程、分布式、设计模式、Spring全家桶、Java、MyBatis、ZooKeeper、Dubbo、Elasticsearch、Memcached、MongoDB、Redis、MySQL、RabbitMQ、Kafka、Linux、Netty、Tomcat、数据库、云计算等
因为篇幅限制,详解资料太全面,细节内容太多,因此只把部分知识点截图出来粗略的介绍,每一个小节点里面都有更细化的内容!
须要的小伙伴,能够一键三连,点击这里获取免费领取方式!