Java类生命周期
Java类加载分为以下几个步骤:
只有加载步骤中的读取二进制流与初始化部分,能够被上层开发者,也就是大部分的Java程序员控制,而剩下的所有步骤,都是由JVM掌控,其中细节由JVM的开发人员处理,对上层开发者来说是个黑盒。
面向对象设计原则SOLID的五个原则
面向对象SOLID: 单一功能、开闭、里氏替换、接口隔离、依赖反转。
1. 单一功能原则(Single Responsibility Principle - SRP)
一个类应该只有一个单一的功能或责任。换句话说,一个类应该只有一个引起它变化的原因。这样设计可以使类更加可维护、可扩展,并降低代码耦合度。
2. 开闭原则(Open/Closed Principle - OCP)
软件实体(类、模块、函数等)应该对扩展开放、对修改关闭。这意味着在不修改原有代码的情况下,通过扩展它们来引入新的功能或行为。这样设计可以保持代码的稳定性和可维护性。
3. 里氏替换原则(Liskov Substitution Principle - LSP)
子类应该能够替换掉父类并且不影响程序的正确性。也就是说,子类应该是对父类的扩展而不是替换。遵循LSP可以保证类的继承体系的稳定性和一致性。
4. 接口隔离原则(Interface Segregation Principle - ISP)
客户端不应该被强迫依赖于它们不使用的接口。这意味着一个类不应该实现它不需要的接口。通过拆分庞大臃肿的接口为更小粒度的接口,可以提高代码的灵活性和可复用性。
5. 依赖反转原则(Dependency Inversion Principle - DIP)
高层模块不应该依赖于低层模块,二者都应该依赖于抽象。抽象不应该依赖于具体实现,具体实现应该依赖于抽象。这样设计可以减少类之间的直接依赖,提高代码的松耦合性,从而更容易修改和维护。
主要内容
类加载器的分类
类加载器的分类属于JVM规范,属于一种抽象的概念。各个不同的JVM实现方式不一定是一样的。
JVM规范中类加载器分为两大类,分为启动类加载器和非启动类加载器。
本文主要关注HotSpot虚拟机。
【补充】HotSpot虚拟机通常指的是JVM的HotSpot实现。它是Java虚拟机规范一种实现,它使用了即时编译(Just-In-Time Compilation,JIT)的技术来提高Java程序的执行性能。即时编译是一种将Java字节码直接编译成本地机器代码的技术,这样可以避免在每次执行时都解释字节码,提高程序的执行效率。
HotSpot的虚拟机的名称来源于设计目标和实现中的一个特性,即“热点代码”。热点代码通常指的是在应用程序中频繁执行的代码段。HotSpot 虚拟机通过动态编译和即时编译技术,专门优化和加速这些热点代码的执行,以提高整个应用程序的性能。
Bootstrap ClassLoader
Bootstrap ClassLoader是嵌套在JVM内部的,它主要用于加载java的核心类库。比如<JAVA_HOME>/lib
下的jar包。或者是由启动参数来指定路径下的核心类库。
此外,为了安全性,Bootstrap ClassLoader只加载了包名在白名单中的文件。
非Bootstrap ClassLoader
1. Extension ClassLoader
是由内部类ext classloader来实现的。主要加载<JAVA_HOME>/lib/ext
目录下或者是由系统变量指定的路径中的类库。
Extentsion ClassLoader希望加载的是Java API的拓展,是对Java类库的一些补充能力。
2. Application ClassLoader
是由内部类app classloader来实现的。主要加载classpath/java.class.path
目录下或者是系统属性指定的路径中的类库。
Application ClassLoader希望加载的是上层程序员编写的一些代码以及一些第三方的类库。可以说程序员平时编写的代码都是由Application ClassLoader来加载的。
3. User ClassLoader
可以让用户获取任何地方的字节码,并对它们进行加载。这就印证了在Java类加载机制中,允许用户从各个渠道获取class文件的二进制流来进行加载的结论。
用户自定义的类加载器只需要继承java.lang.ClassLoader, 然后单独实现获取二进制流的逻辑。而后续的步骤必须让java.lang.ClassLoader中内置的逻辑来处理。用户无权进行重写和干涉。
类加载的命名空间
问题提出
1.不同的类加载器,除了读取二进制流的动作和范围不一样,后续的加载逻辑是否也不一样?
2. 遇到限定名一样的类,这么多类加载器会不会产生混乱?
JVM规范:每个类加载器都有属于自己的命名空间。
双亲委派机制
在被动的情况下,当一个类加载器收到加载请求,他不会首先自己去加载,而是传递给自己的父亲加载器。
这样所有的类都会首先传递到最上层的Bootstrap ClassLoader,只有父亲加载器无法完成加载,那么儿子加载器才会自己去尝试加载。
无法加载:根据类的限定名,类的加载器没有在自己负责的加载路径中找到该类。
【补充】在Java中,类加载器采用一种层次结构,形成了一个父子关系的链。当一个类加载器收到加载请求时,它首先会检查自己是否已经加载了这个类。如果已经加载了,那么加载过程结束。如果没有加载,类加载器会将加载请求传递给它的父类加载器。这个过程就是类加载的被动性。
被动的情况指的是在使用某个类的时候才会触发类加载过程,而不是在类被加载到内存中的时候。例如,当你在程序中使用了某个类的静态成员(静态字段、静态方法)或者创建了这个类的实例时,类加载器会被触发,尝试加载这个类。在这个加载过程中,类加载器会按照一定的规则(通常是双亲委派模型)向上层加载器寻求帮助,直至根加载器。如果所有的父加载器都无法完成加载请求,那么最终由当前类加载器自己尝试加载。
这种被动加载的机制有助于保证类的唯一性和一致性,避免了重复加载,同时也利于安全性和隔离性的实现。
问题解答
-
不同的类加载器,除了读取二进制流的动作和范围不一样,后续的加载逻辑是否也不一样?
我们认为除了Bootstrap ClassLoader,所有的非Bootstrap ClassLoader都继承了
java.lang.ClassLoader,都由这个类的defineClass进行后续处理。 -
遇到限定名一样的类,这么多类加载器会不会产生混乱?
越核心的类库被越上层的类加载器加载,而某限定名的类一旦被加载过了,被动情况下,就
不会再加载相同限定名的类。这样,就能够有效避免混乱。
破坏双亲委派
但是双亲委派模型并不是一个具有强约束力的模型,因为他存在设计权限。在大部分被动情况下,他是生效并且好用的。但在一些情况可以被主动破坏。
破坏双亲委派-第一次
手动load代码破坏第一次破坏双亲委派。
破坏双亲委派-第二次
上层的BootstrapClassLoader对下层的Application ClassLoader进行了委派加载。
思考题
能不能自己写一个限定名为java.lang.String的类,并在程序中调用它?
如果在项目中单纯定义一个java.lang.String类,那么根据双亲委派原则会加载源码中的String,自定义类无法加载报错。如果使用自定义加载器去加载自定义的String类也不行,JDK源码好像限制了"java"开头的类,也就是保护核心源码的安全机制,防止乱改核心源码。文章来源:https://www.toymoban.com/news/detail-425331.html
参考资料:【JVM】Java双亲委派、类加载器这块算是玩明白了文章来源地址https://www.toymoban.com/news/detail-425331.html
到了这里,关于Java双亲委派和类加载器的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!