面试官再问你如何破坏双亲委派,就把这篇文章扔给他
我们每天都在写Java代码,最常见的User.java这种文件,但是这种文件是如何被虚拟机识别的呢,你有没有思考过这个过程
首先我们写的Java文件会被编译成class文件,因为机器不认识getName这种,机器只认识010101
编译完成的class文件会被加载到虚拟机中,用于完成我们的任务,那这个加载是什么时候开始呢,加载的过程是什么样子的呢
先来肝个图,类的加载过程
类从被加载到虚拟机开始,到卸载出内存为止,它的整个的生命周期包括:加载、验证、准备、解析、初始化、使用和卸载七个阶段
而验证、准备、解析三个阶段被统称为连接阶段
类的加载时机,也就是类是在何时开始加载这一过程的呢
这一点虚拟机并没有明确规范,虚拟机可以自由实现,但是对于其中有一个步骤叫做初始化,虚拟机规范则是严格规定了有且只有5种情况立即对类进行初始化,而在此之前的过程也需要提前开始
遇到new、getstatic、putstatic或者invokestatic这四条字节码指令时,生成这四个字节码最常见的Java代码场景就是使用new关键字实例化对象的时候、读取或者设置一个类的静态字段,以及调用一个类的静态方法的时候
使用java.lang.reflect包的方法进行反射调用的时候,类如果没有初始化则触发初始化
初始化一个类的时候,发现其父类未进行初始化,需要先初始化父类
JVM启动时用户指定执行包含main方法的类,会先被初始化
当使用JDK1.7的动态语言支持的时候,如果一个java.lang.invoke.MethodHandle实例最后的解析结果是REF_getStatic、REF_putStatic、REF_putStatic、REF_invokeStatic的方法句柄的时候,并且这个方法句柄所对应的类没进行初始化,需要先初始化
类加载过程,其实这是属于纯理论知识,大家不知道这些工作中照常开发,为什么呢,因为一般这种东西在实际工作中确实也派不上什么用场
那我看这个还有啥用啊,拜拜了您嘞
话不是这么说的,仔仔,你不能说你用不到他就一点也不去了解他,有一句话叫做,书到用时方很少,多读读书,学学这些基础知识,有利于你更好的打牢你的基础
在外面面试时也能够神挡杀神,佛挡杀佛
面试官还就爱问这玩意,这玩意能看出你学习的深度,有没有自己去思考过这些东西
类加载过程:加载、连接、初始化
加载过程
将编译之后的Class文件加载至虚拟机,并存储在方法区(不知道为什么存储在方法区的仔仔,可以去我的面试官问我平时写的Bug的存储位置(逃逸分析、标量替换、锁消除)这一篇中学一波拉
连接过程
连接其实也是被分为了验证、准备、解析三部分组成,验证就是保证该Class文件中的信息是否符合Java虚拟机的要求,是否安全(可能会做出危害虚拟机的行为)准备就是为类变量分配内存,并设置类变量的初始值,不包含实例变量的内存,实例变量的内存将会被分配到堆中
这里的初始值指的是数据类型的零值,并非程序员设置的值,比如static int i = 1;此时得到的i是0,并非我们想要的1
接下来就是连接过程的解析阶段,这一阶段就是将常量池中的符号引用替换为直接引用(内存地址)的过程
解析阶段啥玩意啊,说了个寂寞,符号引用是啥,直接引用又是啥
符号引用,就是以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用的时候能够无歧义的定位到目标即可
直接引用,可以直接指向目标的指针、相对偏移量或者是一个能够间接定位到目标的句柄,直接引用是和内存布局相关的,同一个符号引用在不同虚拟机翻译出来的直接引用一般不同,因为地址不一样
如果有了直接引用,则引用的目标必定已经存在于内存中了
初始化
初始化是类加载的最后一步,前面的类加载过程中,除了在加载阶段用户应用程序可以通过自定义类加载器参与之外,其余的动作其实都是由虚拟机主导和控制的
而初始化阶段才是真正开始执行程序中代码的步骤,也就是上面说的i赋值为1的过程,初始化类变量,加载类的静态语句块的过程
在准备阶段赋值过一次了,那次赋值是系统要求的初始值,跟我们个人的设定无关,而初始化的赋值才是真正根据程序员设计而主导的初始化
使用和卸载
使用类的阶段就是大家new对象的过程,我们会使用到各种类型对象,以及使用完毕对这个类型的卸载,卸载一般发生在程序关闭的时候
程序正常执行结束、遇到异常关闭、或者操作系统出问题导致程序关闭这些都会卸载这些类的加载
类加载器
从JVM的角度看,类加载器主要有两类:
Bootstrap ClassLoader和其他类加载,Bootstrap ClassLoader是C++语言实现,是虚拟机自身的一部分;
其他类加载器都是Java语言实现,不属于虚拟机,全部继承自抽象类java.lang.ClassLoader
从Java开发者的角度看,需要了解类加载器的双亲委派模型,如下图所示:
Bootstrap ClassLoader:启动类加载器,这个类加载器将负责存放在/lib目录中,并且是虚拟机会识别的jar类库加载到内存中。更直白点说,就是我们常用的java.lang开头的那些类,一定是被Bootstrap ClassLoader加载的
Extension ClassLoader:扩展类加载器,它负责加载/lib/ext目录中的、或者被java.ext.dirs系统变量指定的路径中的所有类库。
Application ClassLoader:应用程序类加载器,它负责加载用户CLASSPATH环境变量指定的路径中的所有类库。如果应用程序中没有自定义过自己的类加载器,这个就是一个Java程序中默认的类加载器。
用户自定义的类加载器:用户在需要的情况下,可以实现自己的自定义类加载器,一般而言,在以下几种情况下需要自定义类加载器
隔离加载类。某些框架为了实现中间件和应用程序的模块的隔离,就需要中间件和应用程序使用不同的类加载器
修改类加载的方式。类加载的双亲委派模型并不是强制的,用户可以根据需要在某个时间点动态加载类
扩展类加载源,例如从数据库、网络进行类加载
防止源代码泄露。Java代码很容易被反编译和篡改,为了防止源码泄露,可以对类的字节码文件进行加密,并编写自定义的类加载器来加载自己的应用程序的类
听说过双亲委派吗
至于双亲委派,我觉得可能很多人都听说过,或者说简单的了解过,大鱼这里也继续给大家啰嗦一下
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类
见过一个问题,说为什么叫双亲委派,而不是单亲委派,在Java中应该是单继承的,哪里来的双亲呢
我个人的猜测是一个类加载大概会向上委托两次,两次来让父加载器加载,所以是双亲委派,当然这也是个人猜测,网上查阅了很多资料,也是模棱两可,其实大家也不必要对这种问题纠结,听听就好
双亲委派可以破坏吗
这个问题很经典,面试如果问到JVM,这个问题大概率会被问到,这个时候就到了你装*的时候了,这个时候就轮到给面试官好感,以及涨工资的时刻了
回答问题,一定要循序渐进,按照条理思路来分析,这样会大大增加你在面试官心中的好感,别问我怎么知道的
当然可以破坏了,我们知道类的加载方式默认是双亲委派,如果我们有一个类想要通过自定义的类加载器来加载这个类,而不是通过系统默认的类加载器,说白了就是不走双亲委派那一套,而是走自定义的类加载器
我们知道双亲委派的机制是ClassLoader中的loadClass方法实现的,打破双亲委派,其实就是重写这个方法,来用我们自己的方式来实现即可
当然这里要注意一下,Object.class这是对象的顶级类,改变类的类加载器的时候要注意,如果全部改了,Object.class就找不到了,加载不了了
所以呢,这里重写的时候,要注意分类解决,把你想要通过自定义类加载器加载的和想通过默认类加载器加载的分隔开
好了,以上就是全部内容了,我是小鱼仙,你们的学习成长小伙伴
我希望有一天能够靠写字养活自己,现在还在磨练,这个时间可能会有很多年,感谢你们做我最初的读者和传播者。请大家相信,只要给我一份爱,我终究会还你们一页情的。
再次感谢大家能够读到这里,我后面会持续的更新技术文章以及一些记录生活的灵魂文章,如果觉得不错的,觉得【大鱼同学】有点东西的话,求点赞、关注、分享三连
哦,对了!后续的更新文章我都会及时放到这里,欢迎大家点击观看,都是干货文章啊,建议收藏,以后随时翻阅查看
https://github.com/DayuMM2021/Java
● 消息队列入门