JAVA的类加载机制

类加载机制:参考:
https://blog.csdn.net/zhangliangzi/article/details/51319033
https://www.jianshu.com/p/3556a6cca7e5
https://blog.csdn.net/m0_38075425/article/details/81627349
https://blog.csdn.net/briblue/article/details/54973413
https://blog.csdn.net/qq_16216221/article/details/71600535
https://blog.csdn.net/xyang81/article/details/7292380
https://blog.csdn.net/ln152315/article/details/79223441
https://blog.csdn.net/w760079528/article/details/77845267
https://blog.csdn.net/p10010/article/details/50448491

1,什么是类加载?
2,类加载过程?
3,类加载过程都是都干了什么?
4,tomcat的类加载机制?

1,什么是类加载?
类加载就是JVM将class字节码文件加载到内存中,class字节码来源,可以通过jar包、war包、网络中获取、JSP文件生成等方式
JVM中类的装载是由类加载器(ClassLoader)和它的子类来实现的,
类加载器: 启动类加载器(BootStrap)、扩展加载器(Extension)、系统加载器(System)和用户自定义类加载器(java.lang.ClassLoader的子类)
启动类加载器(BootStrap): 一般用本地代码实现,负责加载JVM基础核心类库(rt.jar)
扩展加载器(Extension):从java.ext.dirs系统属性所指定的目录中加载类库,它的父加载器是Bootstrap
系统加载器(System ClassLoader\APP ClassLoader): 又叫应用类加载器,其父类是Extension。加载当前应用的classpath的所有类。
用户自定义类加载器(java.lang.ClassLoader的子类):
为什么会有自定义类加载器:一方面是由于java代码很容易被反编译,如果需要对自己的代码加密的话,可以对编译后的代码进行加密,
然后再通过实现自己的自定义类加载器进行解密,最后再加载。
另一方面也有可能从非标准的来源加载代码,比如从网络来源,那就需要自己实现一个类加载器,从指定源进行加载。

2,类加载过程
先说一下类的生命周期:
加载 —> 校验 —> 准备 --> 解析 —> 初始化 —>使用 —> 卸载
类加载过程 就是前5个阶段 加载 —> 校验 —> 准备 --> 解析 —> 初始化

3,什么时候类加载
Java虚拟机有预加载功能。类加载器并不需要等到某个类被"首次主动使用"时再加载它,JVM规范规定JVM可以预测加载某一个类,
如果这个类出错,但是应用程序没有调用这个类, JVM也不会报错;如果调用这个类的话,JVM才会报错,(LinkAgeError错误)。其实就是一句话,Java虚拟机有预加载功能。

4,类加载过程都是都干了什么?
**

(1)加载

**: 主要是类加载器工作,类加载器的加载顺序 启动类加载器(Bootstrap CLassloder),扩展加载器(Extention ClassLoader),应用加载器(AppClassLoader)
加载中主要干了三件事:
(1)通过一个类的全限定名(包名和类名)来获取定义此类的二进制字节流(class文件),如果没有找到这个类,则会报出ClassNotFoundException
(2)将字节流所代表的静态存储结构转化为方法区的运行时结构,如果输入数据不是ClassFile的结构,则会抛出ClassFormatError
这里只是转化了数据结构,并未合并数据.(方法区就是用来存放已被加载的类信息,常量,静态变量)
(3) 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区中这个类的各种数据的访问入口
(这个Class对象并没有规定是在Java堆内存中,它比较特殊,虽为对象,但存放在方法区中)
这个过程主要就是类加载器完成。
双亲委派就是发生在这一步,
什么是双亲委派,
如果一个类加载器收到了类加载器的请求,先检查这个class是不是已经加载过,若没有加载则调用父加载器的loadClass()方法,
如果父类加载成功就返回加载的这个, 如果父类加载失败,抛出ClassNotFoundException异常后,再调用自己的findClass()方法进行加载。
这种机制就叫做双亲委派。
这里需要注意的是上述三个JDK提供的类加载器虽然是父子类加载器关系,但是没有使用继承,而是使用了组合关系

双亲委派实现源码:
		 protected Class loadClass(String name, boolean resolve)
			throws ClassNotFoundException
		{
			synchronized (getClassLoadingLock(name)) {
				// First, check if the class has already been loaded
				Class c = findLoadedClass(name);
				if (c == null) {
					long t0 = System.nanoTime();
					try {
						if (parent != null) {
							c = parent.loadClass(name, false);
						} else {
							c = findBootstrapClassOrNull(name);
						}
					} catch (ClassNotFoundException e) {
						// ClassNotFoundException thrown if class not found
						// from the non-null parent class loader
					}

					if (c == null) {
						// If still not found, then invoke findClass in order
						// to find the class.
						long t1 = System.nanoTime();
						c = findClass(name);

						// this is the defining class loader; record the stats
						sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
						sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
						sun.misc.PerfCounter.getFindClasses().increment();
					}
				}
				if (resolve) {
					resolveClass(c);
				}
				return c;
			}
		}
		
双亲委派的优缺点:一个可以避免类的重复加载,另外也避免了java的核心API被篡改
关于双亲委派面试题: 来源: https://blog.csdn.net/u010312474/article/details/91046318
能不能自己写个类叫java.lang.System
答案:通常不可以,但可以采取另类方法达到这个需求。
解释:为了不让我们写System类,类加载采用委托机制,这样可以保证爸爸们优先,爸爸们能找到的类,儿子就没有机会加载。
	而System类是Bootstrap加载器加载的,就算自己重写,也总是使用Java系统提供的System,自己写的System类根本没有机会得到加载。
	但是,我们可以自己定义一个类加载器来达到这个目的,为了避免双亲委托机制,这个类加载器也必须是特殊的。
	由于系统自带的三个类加载器都加载特定目录下的类,如果我们自己的类加载器放在一个特殊的目录,那么系统的加载器就无法加载,
	也就是最终还是由我们自己的加载器加载。

(2) 链接

内容主要来源: https://blog.csdn.net/SilenceOO/article/details/77876123
链接包含验证,准备,解析
验证: 验证的目的在于确保class文件的字节流包含信息复核当前虚拟机要求,不会危害虚拟机自身安全
主要进行:
1.文件格式验证 验证class文件格式规范,例如: class文件是否以0xCAFEBABE开头 ,主、次版本号是否在当前虚拟机处理范围之内等
2.元数据验证 这个阶段是对字节码描述的信息进行语义分析,以保证起描述的信息符合java语言规范要求。
验证点可能包括:这个类是否有父类(除了java.lang.Object之外,所有的类都应当有父类)、这个类是否继承了不允许被继承的类(被final修饰的)、如果这个类的父类是抽象类,是否实现了起父类或接口中要求实现的所有方法。
3.字节码验证 进行数据流和控制流分析,这个阶段对类的方法体进行校验分析,这个阶段的任务是保证被校验类的方法在运行时不会做出危害虚拟机安全的行为。如:保证访法体中的类型转换有效,例如可以把一个子类对象赋值给父类数据类型,这是安全的,但不能把一个父类对象赋值给子类数据类型、保证跳转命令不会跳转到方法体以外的字节码命令上。
4.符号引用验证
准备: 准备阶段是正式为类变量分配内存,设置类变量初始值的阶段.这些内存在方法区中进行分配
请注意1,这里的内存分配仅包括类变量 static 修饰的变量,而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在java堆中
2, 这里所说的初始值是指通常情况下数据类型的零值,如 public static int value = 12; 这里赋值是0,只有在初始化阶段类构造器执行时,才会把value赋值为12

解析:解析阶段是虚拟机常量池内的符号引用替换为直接引用的过程。符号引用就是一组符号来描述目标,可以是任何字面量,
而直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。有类或接口的解析,字段解析,类方法解析,
接口方法解析(这里涉及到字节码变量的引用

(3)初始化

类的初始化的主要工作是为静态变量赋程序设定的初值。如static int a = 100;在准备阶段,a被赋默认值0,在初始化阶段就会被赋值为100。
类的初始化时机:
创建类的实例,也就是new的方式
访问某个类或接口的静态变量,或者对该静态变量赋值
调用类的静态方法
反射 如Class.forName(“com.shengsiyuan.Test”)
初始化某个类的子类,则其父类也会被初始化
Java虚拟机启动时被标明为启动类的类 Java Test ,直接使用java.exe命令来运行某个主类
注意: 对于一个final类型的静态变量,如果该变量的值在编译时就可以确定下来,那么这个变量相当于“宏变量”。
Java编译器会在编译时直接把这个变量出现的地方替换成它的值,因此即使程序使用该静态变量,也不会导致该类的初始化。
反之,如果final类型的静态Field的值不能在编译时确定下来,则必须等到运行时才可以确定该变量的值,如果通过该类来访问它的静态变量,则会导致该类被初始化。

tomcat的类加载机制:

参考: https://www.cnblogs.com/aspirant/p/8991830.html
https://blog.csdn.net/qq_38182963/article/details/78660779
https://blog.csdn.net/dshf_1/article/details/105019733

tomcat为什么要破坏双亲委派?
1, 一个web容器可能需要部署两个应用程序,不同的应用程序可能会依赖同一个第三方类库的不同版本, 不能要求同一个类库在同一个服务器只有一份,因此要保证每个应用程序的类库都是独立的,保证相互隔离。默认的类加载器机制,那么是无法加载两个相同类库的不同版本的,默认的类加载器只在乎你的全限定类名,并且只有一份
2, web容器也有自己依赖的类库,不能于应用程序的类库混淆。基于安全考虑,应该让容器的类库和程序的类库隔离开来。
3, jsp热部署,如果使用双亲委派,修改了jsp文件,但类名还是一样,类加载器会直接取方法区中已经存在的,修改后的jsp文件是不会重新加载的
tomcat实现热部署: https://my.oschina.net/u/4030990/blog/3073234
tomcat如何打破双亲委派的呢?
https://blog.csdn.net/caox_nazi/article/details/91047307
https://imgconvert.csdnimg.cn/aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2Jsb2cvMzg2ODM1LzIwMTgxMi8zODY4MzUtMjAxODEyMDkxMDM0MzAzNzEtMTM2NDU0NTI0MS5wbmc?x-oss-process=image/format,png

tomcat自己定义的类加载器:
   		CommonClassLoader:tomcat最基本的类加载器,加载路径中的class可以被tomcat和各个webapp访问
   		CatalinaClassLoader:tomcat私有的类加载器,webapp不能访问其加载路径下的class,即对webapp不可见
   		SharedClassLoader:各个webapp共享的类加载器,对tomcat不可见
   		WebappClassLoader:webapp私有的类加载器,只对当前webapp可见
   		JspClassLoader:    jsp类加载器

注意: 每一个web应用程序对应一个WebappClassLoader,每一个jsp文件对应一个JspClassLoader,所以这两个类加载器有多个实例

工作原理:
	a. CommonClassLoader能加载的类都可以被Catalina ClassLoader和SharedClassLoader使用,从而实现了公有类库的共用
	b. CatalinaClassLoader和Shared ClassLoader自己能加载的类则与对方相互隔离
	c. WebAppClassLoader可以使用SharedClassLoader加载到的类,但各个WebAppClassLoader实例之间相互隔离,多个WebAppClassLoader是同级关系
	d. 而JasperLoader的加载范围仅仅是这个JSP文件所编译出来的那一个.Class文件,
	   它出现的目的就是为了被丢弃:当Web容器检测到JSP文件被修改时,会替换掉目前的JasperLoader的实例,并通过再建立一个新的Jsp类加载器来实现JSP文件的HotSwap功

加载过程:
	对于一些未加载的非基础类(Object,String等),各个web应用自己的类加载器(WebAppClassLoader)会优先加载,
	加载不到时再交给commonClassLoader走双亲委托。具体的加载逻辑位于WebAppClassLoaderBase.loadClass()方法中,
	这里以文字描述加载一个类过程:WebappClassLoader.loadClass源码及注解 https://blog.csdn.net/iteye_18979/article/details/82604271
	1,先在本地缓存中查找是否已经加载过该类(对于一些已经加载了的类,会被缓存在resourceEntries这个数据结构中),如果已经加载即返回,否则 继续下一步。
	2,让系统类加载器(system/AppClassLoader)尝试加载该类,主要是为了防止一些基础类会被web中的类覆盖,如果加载到即返回,否则继续。
	3,前两步均没加载到目标类,那么web应用的类加载器将自行加载,如果加载到则返回,否则继续下一步。
	4,最后还是加载不到的话,则委托父类加载器(Common ClassLoader)去加载。

请参考这里源码看 https://blog.csdn.net/iteye_18979/article/details/82604271
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值