前言
准备写这份笔记的时候,想法还是较为简单的,就是希望能将学到的,听到的,查到的,看到的东西做一个总结,以免后面自己遗忘。同时我将会以段落和副标题的形式编写。有什么问题或者错误的地方,还请大家多指正,希望你能获得你想要的知识,或者指出我错误的地方,在巩固自己的同时帮助我更好的提升。感谢各位,同时也希望大家能多多转发,嘿嘿嘿。
JAVA类加载过程
1、通过C++实现代码,调用windows下的java.exe,调用jvm.dll,创建java虚拟机
2、创建一个引导类加载器实例(c++实现)
3、C++调用java代码,创建 JVM启动器实例,sun.mis.cLauncher,该类由类加载器负责加载,创建其他类加载器(JVM有很多类加载器)
4、sun.misc.Launcher.getLauncher(),获取运行类自己的加载器ClassLoader,是AppClassLoader的实例。
5、launcher.getClassLoader(),调用loadClass加载要运行的类Math。
6、运行用户类代码
7、JVM销毁。
类加载过程
加载、验证、准备、解析、初始化。
1、加载:将字节码文件放入内存。
2、验证:cafe babe 标准字节码开头文件。在类加载时,会先验证一下字节码的规范。
3、准备:会把静态变量初始赋值(int 为0 boolean为false),这个初始值是jvm规定的,并不会改变。(如果加了final,则变为常量,直接赋值入虚拟机)
4、解析:将符号引用替换为直接引用。(符号,例如类名,方法名等等。将这些符号替换内存指针或者句柄),这就是所谓的静态链接(动态链接是在程序运行期间将符号引用替换为直接引用。)
5、 初始化:把静态变量初始化为对应的值(初始化之前,静态变量都是默认值)类被加载到方法区中后,主要包含运行时常量池,类型信息,字段信息,方法信息,类加载器的引用,对应class实例的引用等等。
JVM的类加载机制,其实是一种懒加载
类加载器和双亲委派
引导类加载器:位于JDK lib目录下,由C++编写,用于加载支撑JVM运行的rt.jar(java.nio,java.time,java.util等工具类,都在rt.jar中) charsets.jar等类)
扩展类加载器:负责加载支撑JVM运行的,位于JRE exrt目录下的jar包
应用程序加载器:负责加载ClassPath路径下的类,主要是加载你自己写的那些类。
自定义加载器:负责加在用户自定义路径下的类
所有类加载器,都继承与ClassLoader类,没个类加载器都有一个参数parent,标定了类加载器的父亲(与双亲委派有关,但并不是父类)。
<-------------------------------------------------------------------------------------------------------> Launcher类初始化过程(参考上图的类加载过程):获取ExtClassLoader(父类为URLClassLoader),AppClassLoader(传入ExtClassLoader作为参数,父类也为URLClassLoader,但其parent,为传入的ExtClassLoader)。
引导类加载器通过C++代码加载,然后引导类加载器加载ExtClassLoader与AppClassLoader,同时构造两个类加载器的父级关系(通过parent属性)
ExtClassLoader的parent是BootStrapLoader是null,因为引导类加载器是通过C++加载进来的,并不存在JVM中
备注:上面这一部分我自己也理解的不是很清楚,等待大佬指正。 <----------------------------------------------------------------------------------------------------------->
双亲委派机制:
在没有自定义加载器的时候,一个类会先请求应用程序加载器AppClassLoader加载,
AppClassLoader扫描自己已经加载的类,如果有则返回,若没有,则委托parent加载,AppClassLoader委托ExtClassLoader。ExtClassLoader进行相同的操作,若不在自己的已加载列表中,则委托parent,引导类加载器 BootStrapClassLoader。BootStrapClassLoader发现这个类不在自己已加载列表里,会尝试从JDK,lib文件夹中加寻找这个类加载(引导类加载器只负责加载JDK lib文件夹下的加载器)。若没有,则向下委派扩展类加载器去加载。扩展类加载器扫描ext文件夹,若无则委托AppClassLoader。AppClassLoader会扫描target目录下(如果是IDEA的话),则必然找到,加载(调用LoadClassPath()方法)。
问题:为什么要从AppClassLoader开始先向上委托?
对于一个web项目来说,95%以上的类,都是由AppClassLoader进行加载的,而且很多类会重复加载非常多次。如果每次都从引导类开始向下委派,无疑效率很低。直接请求AppClassLoader,若存在则返回,会大大提高效率。仅仅类第一次加载的时候效率会较低而已。
为什么要设计双亲委派机制?
1、沙箱安全机制:避免底层API类被修改,影响安全性。
例如: 我们构建一个类叫java.lang.String.class,这个类与JDK的String同名。在加载这个类的时候,会先向上委托到BootStrapClassLoader,返回java本身的API类String,并不会加载你实际写的这个类。保证了java.lang.String只会加载JDK的API类而非同名自定义类。防止核心API库被修改。
2、避免类的重复加载:当父加载器已经加载过某个类,例如ExtClassLoader已经家在过这个类了,那么会直接返回这个类,不会让AppClassLoader再加载一次。
打破双亲委派机制
由于双亲委派机制是通过java.lang.ClassLoader的 LoaderClass函数完成的,打破双亲委派,就重写这个函数就可以了。
Tips:可能会遇到,比如所有类都继承于Object,如果所有类都打破双亲委派,用自定义加载器记载,会出现Object类加载不到的情况。所以要将API类的加载过滤掉,API类依旧遵循双亲委派,自己实现的类打破双亲委派。
例子: Tomcat上的每个war包,都会有一个对应的WebappClassLoader(他不是生成了一个新的ClassLoader类,是通过类,new了一个webClassLoader对象,通过这个对象加载类。),保证了项目之间的类的隔离(JVM区分相同类,除了包名、类名,还要看加载器是否 相同)。项目自己的类,自己用WebappClassLoader,项目中引用的tomcat公用类,还是委托上级加载。