本文转载自微信公众号《粥下雪》,作者帅小凡凡。转载本文请联系粥夏雪公众号。您熟悉双亲委派模型吗?熟人,我来回答这个问题。机会来了,我是八股文高手,可是特地练了这个。为什么不能在Java项目中定义一个与String同名的Java文件呢?(⊙o⊙)...为什么多线程加载类时没有线程问题?卧槽。。。首先,在解释这个问题之前,我们先关注类加载的时机和过程。从加载到虚拟机内存到卸载内存,其整个生命周期包括:加载、验证、准备)、解析、初始化、使用和卸载。其中准备、验证、分析三部分统称为Linking。确定加载、验证、准备、初始化和卸载五个阶段的顺序。类的加载过程必须按这个顺序一步一步开始,而分析阶段不一定:在某些情况下可以在初始化阶段之后开始,这是为了支持Java语言的运行时绑定(也称为动态绑定或latebinding)在加载阶段加载(参见java.lang.ClassLoader的loadClass()方法),虚拟机需要完成以下三件事:通过类的全限定名获取定义该类的二进制字节流(它没有指定要从Class文件中获取,可以从其他渠道获取,如:网络、动态生成、数据库等);将这个字节流表示的静态存储结构转换成方法区的运行时数据结构;在内存中生成一个代表该类的java.lang.Class对象,作为该类在方法区的各种数据的访问入口;验证验证是连接阶段的第一步,这个阶段的目的是确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危及虚拟机的安全机器本身。验证阶段大致完成四个阶段的检查动作:文件格式验证;元数据验证;字节码验证;所有使用的内存都将分配在方法区中。此时内存分配只包括类变量(static修饰的变量),不包括实例变量,实例变量会在对象实例化时随对象一起分配到堆中。其次,这里所说的初始值“通常”是数据类型的零值,例如:publicstaticinta=1此时,变量a经过准备阶段后的初始值为0,而不是1,因为它还没有开始执行任何java方法,而给1赋值的指令是在程序编译后保存在类constructor()方法中的,所以给1赋值的动作会在初始化阶段执行.而当:publicstaticfinalinta=2时,情况就不同了。当类字段的属性为ConstantValue时,会在准备阶段初始化为指定的值,所以flag为final后,准备阶段会使用a的值。初始化为2而不是1。重点:这里其实很容易理解。finalflag的常量表示不变,所以在准备阶段直接赋值就可以了。没有必要做更多。先赋初值,初始化的时候再做一遍。解析解析阶段是虚拟机将常量池中的符号引用替换为直接引用的过程。解析动作主要针对类或接口的符号引用、字段、类方法、接口方法、方法类型、方法句柄、调用点限定符这7类进行。Initialization在引入初始化的时候,首先要引入两个方法:和:在编译生成一个class文件的时候,会自动生成两个方法,一个是类的初始化方法,一个是实例的初始化方法:在jvm中首先在加载class文件时调用,包括静态变量初始化语句和静态块的执行:在创建实例时调用,包括调用new操作符;调用newInstance(类或Java.lang.reflect.Constructor对象的)方法;调用任何现有对象的clone()方法;通过java.io.ObjectInputStream类的getObject()方法反序列化。类初始化阶段是类加载过程的最后一步,类中定义的java程序代码实际上是在初始化阶段执行的。在准备阶段,变量已经赋过一次系统要求的初始值,而在初始化阶段,根据开发者通过程序对类变量和其他资源进行初始化,或者说:初始化阶段就是执行的过程类的构造函数()方法.()方法是由编译器自动收集的类中所有类变量的赋值动作和静态语句块static{}中的语句组合生成的。编译器收集的顺序是由语句在源文件中出现的顺序决定的,静态语句块只能访问静态语句块之前定义的变量,定义在它之后的变量可以赋值,但是无法访问。》总结了一大堆美女:其实这个过程不需要记住,最好靠理解。想想看,如果要加载一个类到虚拟机中,是否应该有一个加载过程?这个过程就是把你的类加载到虚拟机中,把类对应的二进制字节流加载到虚拟机中。加载虚拟机后,是否需要验证你给的规则是否有毒?之后验证,要不要帮我们设置一些类变量的初始值??设置好之后,还有一些对象标志等等,要不要帮忙解析成直接引用,最后,有没有下台我们班的初始化方法?”双亲委托模型所谓双亲委托是指每次加载一个类请求时,先将请求委托给父类加载器完成(所有的加载请求最终都会委托给顶层的BootstrapClassLoader加载器),如果父类加载器无法完成加载(没有找到对应的类),子类尝试自己加载,如果没有加载就会抛出ClassNotFoundException异常,看到这里其实就解释了第一个问题提出的文章开头,父加载器已经加载了JDK的String.class文件,所以我们不能定义一个同名的Stringjava文件。这样做的好处是什么?因为可以避免重复加载,所以当parent已经加载了class的时候,ClassLoader就不需要再加载了。考虑到安全因素,我们试想一下,如果不使用这种委托模式,我们可以使用自定义的String随时动态替换java核心API中定义的类型,这样会存在非常大的安全隐患,而parentsentrust这种方式,可以避免这种情况,因为String在启动的时候已经被引导类加载器(BootstrcpClassLoader)加载了,所以用户自定义的ClassLoader永远加载不了自己写的String,除非你改变里面的ClassLoaderJDK搜索类的默认算法。我们发现除了引导类加载器(BootStrapClassLoader)之外,每个类都有它的“父类”加载器。这种组合关系是由组合方式决定的,而不是由继承关系决定的。三个类加载器的职责是什么?“BootstrapClassLoader”:这个加载器不是Java类,而是由c++底层实现的,负责加载Jdk核心类库(如:rt.jar、resources.jar、charsets.jar等)和两者加载后的类加载器。这个ClassLoader完全由JVM自己控制。需要加载哪个类,如何加载,由JVM自己控制,其他人无法访问这个类“ExtensionClassLoader”:它是一个普通的Java类,继承自ClassLoader类,负责加载{所有jarJAVA_HOME}/jre/lib/ext/目录下的包。“AppClassLoader”:是ExtensionClassLoader的子对象,负责加载应用classpath目录下的所有jar和class文件。通常,这两个方法用于动态加载一个java类,“Class.forName()”和“ClassLoader.loadClass()”,但是这两个方法之间也有一些细微的差别。详见上一篇:Object说说双亲委派模型的源码分析,看看核心的loadClass方法。可以看到该方法有一个同步块(synchronized),这样即使在多线程的情况下,也不会出现重复加载的情况。JAVA热部署实现什么是热部署(hotswap)?Java类是通过Java虚拟机加载的。某个类的class文件被classloader加载后,会生成一个对应的Class对象,然后就可以创建该类的实例了。热部署是在运行时自动检测类文件的变化并更新类而无需重启Java虚拟机的行为。实际上,对于同一个全限定名的java类,只能加载一次,不能卸载。所以班不能卸,那能不能换个她妈?那就是类加载器,答案是肯定的,我们可以自定义类加载器,重写ClassLoader的findClass方法,然后热部署步骤是:销毁自定义的ClassLoader(加载器加载的类也会自动卸载);更新类并使用新的ClassLoader加载类并最终实现它。其实就是覆盖ClassLoader。如何覆盖和更新同一个ClassLoader就是这样。”总结一大波:虽然我们不能篡改加载的类,但是我们可以篡改ClassLoader,去掉它,然后使用新的ClassLoader来加载类。”那么旧类什么时候回收呢??毕竟换掉了,就会有新老问题,还会有内存泄露。通常,在JSP、OSGI等支持热替换的库中,需要对类进行卸载和回收。否则替换类后,旧的类没有用但还在内存中,会造成内存丢失。泄漏。一个类的卸载需要满足以下三个条件:该类的所有实例都已经GC过,即JVM中没有该类的实例。加载此类的ClassLoader已被GC。这个类的java.lang.Class对象在任何地方都没有被引用,比如这个类的方法在任何地方都不能通过反射访问到。所以大家在自定义类加载器的时候,一定要注意这一点。如果要在使用后卸载,需要特别注意类加载器和类的作用域。原文链接:https://mp.weixin.qq.com/s/JT4Vh_tXzNAQdLSAosy2cQ