【JVM】熟悉而又陌生的JVM (叁) - 类加载 详解

类加载可以说是每个Java程序员必须要知道的一个机制,他不仅在面试中遇到,更多的,是在开发中可以灵活的排查一些错误以及方便的编写自己的类加载器。

开局两张图,内容全靠编

类加载顺序
类加载器模型-双亲委派模型

类加载机制

类加载分成5个主要部分。
阶段:加载-验证-准备-解析-初始化
加载
编译过的java文件会变成class文件,加载就是把class文件读取到内存中。
加载过程会分配内存,方法区的内存和堆中的内存。
加载会在方法区存储具体的数据结构,堆中是对这个数据结构的封装,是一个对象。
加载的时候是根据包名+类名进行查找到具体的类。
类加载的来源可以有很多,本地,网上,jar中,zip中,数据库中。

验证
校验字节码的正确性,主要分为以下几个校验:
1、文件格式,表示是否是java的class文件,版本是否正确。
2、元数据,是为了校验字节码的正确性,就是是否是java语言规范,比如是否有父类。
3、字节码校验,最复杂,是真正执行代码的,查看代码是否可以正确执行。
4、符号引用,比如查看这个类的所用到别的类的数据,是否是private类型的。

准备
为所有的静态变量赋值一个默认值,public static int a = 1。
此时会将a赋值一个默认值,int的默认值是0。
为所有的常量赋值正确的值,public static final int a = 1.
此时会将a赋值1,因为是一个常量,后面不会修改。
准备工作也会为这些变量进行分配内存。

解析
将符号引用变成直接引用的过程。
符号引用:符号引用是一个规定死的一个符号,比如CONSTANT_Class_info这个就可以被所有的虚拟机识别,但是并不能做些什么,此时需要转换成直接引用。
直接引用:这就是具体的对某个目标的指针,可以说是一个偏移量,反正就是可以明确的指出所指的目标在内存中哪个位置。

初始化
最后阶段。
真正执行逻辑代码的地方。
赋值给变量真正的逻辑中的值。
初始化执行就是执行构造器的方法,目的就是给静态变量赋值。
方法是编译器在收集类中静态成员时候,自动产生的。
所以说如果没有静态变量和静态块,可以不执行方法,都没生产咋执行。
所以说static的声明和使用是有顺序的,因为编译器收集的时候就是按照顺序的。
以上几个步骤可以先执行,但是初始化并不是跟着上面一起执行的。
类加载器先把class文件加载然后准备解析等操作,就不管了,先不初始化。
那么什么时候初始化:
1、new。
2、反射。
3、序列化
4、调用一个类的静态方法的时候。
5、调用静态字段,对静态字段操作的时候。
6、发现父类没有被初始化的时候,去初始化父类。
7、JVM启动的时候,main方法所在类。
那么什么时候不进行初始化:
1、定义对象数组。
2、通过类名获取Class对象。
3、Class.forName的时候,参数设置为false。
4、ClassLoader.loadClass方法。
等。

类加载器

三个机制:委派、可见性、单一性。
分类:Bootstrap,Extension,Application,User。
Bootstrap是最高级加载器,加载JRE/lib下的jar。
Extension是第二层加载器,加载JRE/lib/ext下的jar。
Application是应用加载器,加载应用程序的所依赖的jar。
User的是自定义的,加载实现ClassLoad的自己写的加载器。

原理
原理就是拿三个机制,委派、可见性、单一性。
委派:
委派是交给父类进行加载,父类加载不了,子类才加载。
比如自定义一个加载器,然后指定父类,父类再去指定父类,直到最上面的Bootstrap都无法加载,然后一级一级向下走,到发起的类依然无法加载,会报错。
可见性:
子类加载器可以看到父类加载的东西,但是父类却看不到子类。
这样的好处和双亲委派有内在联系。
单一性:
单一性是一个好东西,可以理解为一个类只能保证是一个加载器加载的。
判断两个类是否一样,首先是判断是否一个加载器,比如A类先让Application加载了,又让自定义加载了,那么这两个加载的A类是不相等的,因为所加载之后,在内存中分配的内存都是不一样的。
父类加载过的类不能被子类进行加载,但是不确定哦。
热部署什么的,其实就是多个加载器进行不同的加载,达到使用最新的加载到的类。

双亲委派模型

何为委派,就是收到加载任务,自己先不动,交给父类。
何为双亲,因为加载器一共有四层,必然有多个父类。
那么组成一个模型就是最底层的加载器依次交由上面的父类,父类加载不了,自己才加载。
可以看到好处:
	确定了一个类是同一个加载器进行加载的,而不是多个加载器,因为每次都会派给父类。
	每个加载器都在自己的范围内查找,不会乱。
	避免相同的类而使用不同的加载器。
	安全,因为一些类不能被重新实现,爷爷加载器加载过一次就不加载了,就达到安全了。
	还有一些好处可以自己去悟出来的。

破坏双亲委派模型

破坏双亲委派,就要自己实现类加载器,但是一般不要破坏。
要破坏的时候,比如热部署,就是一个打破双亲委派的。

自定义类加载器

通过User.class.getClassLoader()可以得到User类加载的时候用的是哪个加载器。
User.class.getClassLoader().getParent()得到父加载器。这里有个坑:当当前类是Ext的时候,父加载器是Bootstrap,那么此时ext.parent得到的是一个null。null是对的。
因为父加载器并不一定是父类。Bootstrap加载器是C写的,是虚拟机内置的,当然获取不到了。而ext和app都是具体的java类。
Bootstrap加载器加载之后会生成一个ext和app,并且把ext加载器当做app的父加载器,逻辑是这样的。
自定义的加载器如果没有定义parent,那么他的parent就是AppClassLoader。

重要方法:loadClass(),findClass(),findLoadedClass(),defineClass();

loadClass():
	在ClassLoader类中,看源码可以得到,采用递归的形式执行父加载器的loadClass方法,如果都没有加载到,那么就要调用findClass方法,所以在自定义的时候我们一般只需要重写findClass方法,因为前面的都是一样的,找不到最终会调用findClass方法。
	
defineClass():
	是把字节转换成class对象。将二进制内容转换成class。
	
findLoadedClass():
	是在已经加载过得类中去找,看看有没有加载过。返回已经加载的该类。
	
findClass():
	自定义查找类文件。
	自定义步骤:继承ClassLoader,重写findClass方法,调用defineClass方法。
	继承ClassLoader不说了,ClassLoader是最上级的一个加载类。
	重写findClass方法刚才说过了,因为loadClass方法在各种加载器都找不到的时候会调用findClass,既然是我们自己的文件,所以最终还是会用到findClass这个方法的,所以只需要重写这个即可。
	然后就开始读取字节码,将读取到的字节码给defineClass方法,这个方法会把字节码转换成class对象,最终返回。
	自定义加载器是一个无底洞,用好了,很牛逼的,比如热部署,等各种技术。

ClassLoader类

类加载器的继承关系:
ClassLoader < SecureClassLoader < URLClassLoader < AppClassLoader...
AppClassLoader同级有很多个的。

如有错误,请多多指正,互相学习,互相进步。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值