https://www.toutiao.com/a6852280188907029000/
1 类加载流程
-
类的生命周期一共分为5个阶段,加载、连接(包括验证、准备和解析)、初始化、使用(类得实例化)、卸载(垃圾回收),最终形成可以被JVM直接使用的Java类型,这就是JVM的类加载机制。
-
Java语言里面,类的加载、连接和初始化过程都是在程序运行期间完成的。
-
类的加载:最常见的一种情况是将已存在的类的Class文件(也就是字节码文件)从磁盘上面加载到内存里面,在内存中创建一个java.lang.Class对象用来封装类在方法区中的数据结构。
-
通过一个类的全限定名(如:java.lang.String)来获取定义此类的二进制字节流。
-
将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
-
在内存中生成一个代表这个类的java.lang.Class的对象实例,作为方法区在这个类的各种数据访问的入口。
-
-
类的连接(又细分了三个阶段):连接就是将已经读入到内存的类的二进制数据合并到虚拟机的运行环境中去。
-
验证:确保被加载类的正确性。类文件的结构检查、语义检查、字节码验证、二进制兼容性的验证
-
准备:为类的静态变量(也可以称为类变量)分配内存,并将其初始化为默认值(比如int 的默认值就是0)
-
解析:将类中的符号引用转换为直接引用
-
-
类的初始化:为类的静态变量进行赋值(从代码从上到下执行)
【初始化类时机】
在每个类或接口被Java程序"首次主动使用"时才初始化他们,是首次并且还是主动使用得时候才会初始化类。我这里总结了6种主动使用:
-
创建类的实例
-
访问某个类或接口的静态变量,或者对该静态变量赋值
-
调用类的静态方法
-
反射(如class.forName())
-
初始化一个类的子类
-
Java虚拟机启动时被表明为启动类的类
2 类加载器
类加载器是用来把类加载到Java虚拟机的内存空间中。从JDK1.2版本开始,类的加载过程采用双亲委托机制。这种机制能更好的保证Java平台的安全。在此委托机制中,除了Java虚拟机自带的根类加载器之外(因为根类加载器本身是没有父加载器的),其余的类加载器都有且只有一个父加载器。
【分类】
-
启动类加载器(BootstrapClassLoader)
-
扩展类加载器(ExtensionClassLoader)
-
系统(应用)类加载器(SystemClassLoader或者AppClassLoader)
-
用户自定义的类加载器
3 双亲委派模型
其实这里的父子关系是组合模式,不是继承关系来实现
【定义】
所谓双亲委派是指每次收到类加载请求时,先将请求委派给父类加载器完成(所有加载请求最终会委派到顶层的Bootstrap ClassLoader加载器中),如果父类加载器无法完成这个加载(该加载器的搜索范围中没有找到对应的类),子类尝试自己加载, 如果都没加载到,则会抛出 ClassNotFoundException 异常
【为什么】
-
安全性,避免核心类被修改,保证JVM加载类的一致性。
-
性能,避免重复加载。
【四问】
-
为什么不能定义java.lang.Object的Java文件?
-
答案:因为java.lang.Object已经被启动类加载器加载了。双亲委派模型只会进行一次加载
-
-
在多线程的情况下,类的加载为什么不会出现重复加载的情况?
-
答案:类加载器的加载类的loadClass方法有同步锁机制,保证线程安全
-
-
JVM是怎么初始化注册MySQL的驱动Driver?代码:Connection conn= DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb?characterEncoding=GBK", "root", "root");
-
怎么实现自定义类加载器:https://www.toutiao.com/a6912270959114273293/
-
不破坏双亲委派模型:重写findClass方法
public class MyClassLoaderParentFirst extends ClassLoader{ private Map<String, String> classPathMap = new HashMap<>(); public MyClassLoaderParentFirst() { classPathMap.put("com.java.loader.TestA", "/Users/hansong/IdeaProjects/OhMyJava/CodeRepository/target/classes/com/java/loader/TestA.class"); classPathMap.put("com.java.loader.TestB", "/Users/hansong/IdeaProjects/OhMyJava/CodeRepository/target/classes/com/java/loader/TestB.class"); } // 重写了 findClass 方法 @Override public Class<?> findClass(String name) throws ClassNotFoundException { String classPath = classPathMap.get(name); File file = new File(classPath); if (!file.exists()) { throw new ClassNotFoundException(); } byte[] classBytes = getClassData(file); if (classBytes == null || classBytes.length == 0) { throw new ClassNotFoundException(); } return defineClass(classBytes, 0, classBytes.length); } private byte[] getClassData(File file) { try (InputStream ins = new FileInputStream(file); ByteArrayOutputStream baos = new ByteArrayOutputStream()) { byte[] buffer = new byte[4096]; int bytesNumRead = 0; while ((bytesNumRead = ins.read(buffer)) != -1) { baos.write(buffer, 0, bytesNumRead); } return baos.toByteArray(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return new byte[] {}; } }
-
破坏双亲委派模型:重写loadClass方法。
-
-
为什么要破坏双亲委派模型:https://www.toutiao.com/a6830396363566481933/
-
它无法解决解决基础类又要调用用户代码的问题。
-
对程序的动态性的追求导致的。为了实现热插拔,热部署,模块化,意思是添加一个功能或减去一个功能不用重启,只需要把这模块连同类加载器一起换掉就实现了代码的热替换。
-