八、双亲委派
前置:类的加载
package com.yanqun.parents;
class MyClass{
static int num1 = 100 ;
static MyClass myClass = new MyClass();
public MyClass(){
num1 = 200 ;
num2 = 200 ;
}
static int num2 = 100 ;
public static MyClass getMyClass(){
return myClass ;
}
@Override
public String toString() {
return this.num1 + "\t" + this.num2 ;
}
}
public class MyClassLoader {
public static void main(String[] args) {
MyClass myc = MyClass.getMyClass() ;
System.out.println(myc);
}
}
分析
static int num1 = 100 ; 【 0 】-> 【100】->【200】
static MyClass myClass = new MyClass();【null】 ->【引用地址0x112231】
public MyClass(){
num1 = 200 ;
num2 = 200 ;
}
static int num2 = 100 ; 【0】->【200】->【100】
连接:static静态变量并赋默认值
初始化:给static变量 赋予正确的值
总结:在类中 给静态变量的初始化值问题,一定要注意顺序问题(静态变量 和构造方法的顺序问题)
双亲委派:
JVM自带的加载器(在JVM的内部所包含,C++)、用户自定义的加载器(独立于JVM之外的加载器,Java)
-
JVM自带的加载器
- 根加载器,Bootstrap : 加载 jre\lib\rt.jar (包含了平时编写代码时 大部分jdk api);指定加载某一个jar( -Xbootclasspath=a.jar)
- 扩展类加载器,Extension:C:\Java\jdk1.8.0_101\jre\lib\ext\*.jar ;指定加载某一个jar(-Djava.ext.dirs= …)
- AppClassLoader/SystemClassLoader,系统加载器(应用加载器):加载classpath;指定加载(-Djava.class.path= 类/jar)
-
用户自定义的加载器
- 都是抽象类java.lang.ClassLoader的子类
- 都是抽象类java.lang.ClassLoader的子类
双亲委派:
当一个加载器要加载类的时候,自己先不加载,而是逐层向上交由双亲去加载;当双亲中的某一个加载器 加载成功后,再向下返回成功。如果所有的双亲和自己都无法加载,则报异常。
package com.yanqun.parents;
//classpath: .; ..lib,其中“.”代表当前(自己写的类)
class MyClass2{
}
public class TestParentsClassLoader {
public static void main(String[] args) throws Exception {
Class myClass1 = Class.forName("java.lang.Math") ;
ClassLoader classLoader1 = myClass1.getClassLoader();
System.out.println(classLoader1);
/* JDK中的官方说明:
Some implementations may use null to represent the bootstrap class loader
*/
Class myClass2 = Class.forName("com.yanqun.parents.MyClass2") ;
ClassLoader classLoader2 = myClass2.getClassLoader();
System.out.println(classLoader2);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a8TBgUzz-1606381470118)(C:\Users\17122\AppData\Roaming\Typora\typora-user-images\image-20201124134842936.png)]
null: bootstrap class loader
小结:如果类是 rt.jar中的,则该类是被 bootstrap(根加载器)加载;如果是classpath中的类(自己编写的类),则该类是被AppClassLoader加载。
定义类加载:最终实际加载类的 加载器
初始化类加载类:直接面对加载任务的类
package com.yanqun.parents;
import java.net.URL;
import java.util.Enumeration;
class MyCL{
}
public class JVMParentsCL {
public static void main(String[] args) throws Exception {
Class<?> myCL = Class.forName("com.yanqun.parents.MyCL");
ClassLoader classLoader = myCL.getClassLoader();
System.out.println(classLoader);
System.out.println("---");
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);
ClassLoader parent1 = systemClassLoader.getParent();
System.out.println(parent1);
ClassLoader parent2 = parent1.getParent();
System.out.println(parent2);
System.out.println("----");
ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
Enumeration<URL> resources = appClassLoader.getResources("com/yanqun/parents/MyCL.class");// a/b/c.txt
while(resources.hasMoreElements()){
URL url = resources.nextElement();
System.out.println(url);
}
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SX54X5PM-1606381470124)(C:\Users\17122\AppData\Roaming\Typora\typora-user-images\image-20201124134903946.png)]
1 自定义类的加载器
二进制名binary names:
"java.lang.String"
"javax.swing.JSpinner$DefaultEditor"
"java.security.KeyStore$Builder$FileBuilder$1"
"java.net.URLClassLoader$3$1"
$代表内部类:
$数字:第几个匿名内部类
The class loader for an array class, as returned by {
@link* Class#getClassLoader()} is the same as the class loader for its element* type; if the element type is a primitive type, then the array class has no* class loader.
1.数组的加载器类型 和数组元素的加载器类型 是相同
2.原声类型的数组 是没有类加载器的
如果加载的结果是null: 可能是此类没有加载器(int[]) , 也可能是 加载类型是“根加载器”
<p> However, some classes may not originate from a file; they may originate* from other sources, such as the network, or they could be constructed by an* application. The method {@link #defineClass(String, byte[], int, int)* <tt>defineClass</tt>} converts an array of bytes into an instance of class* <tt>Class</tt>. Instances of this newly defined class can be created using* {@link Class#newInstance <tt>Class.newInstance</tt>}.
xxx.class文件可能是在本地存在,也可能是来自于网络 或者在运行时动态产生(jsp)
<p> The network class loader subclass must define the methods {
@link
* #findClass <tt>findClass</tt>} and <tt>loadClassData</tt> to load a class
* from the network. Once it has downloaded the bytes that make up the class,
* it should use the method {
@link #defineClass <tt>defineClass</tt>} to
* create a class instance. A sample implementation is:
*
* <blockquote><pre>
* class NetworkClassLoader extends ClassLoader {
* String host;
* int port;
*
* public Class findClass(String name) {
* byte[] b = loadClassData(name);
* return defineClass(name, b, 0, b.length);
* }
*
* private byte[] loadClassData(String name) {
* // load the class data from the connection
* . . .
* }
* }
如果class文件来自原Network,则加载器中必须重写findClas()和loadClassData().
2 自定义类加载器的实现
重写findClas()和loadClassData()
package com.yanqun.parents;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
//public class MyException extends Exception{...}
public class MyClassLoaderImpl extends ClassLoader{
//优先使用的类加载器是:getSystemClassLoader()
public MyClassLoaderImpl(){
super();
}
public MyClassLoaderImpl(ClassLoader parent){
//扩展类加载器
super(parent);
}
//com.yq.xx.class
public Class findClass(String name) {
System.out.println(name);
byte[] b = loadClassData(name);
return defineClass(name, b, 0, b.length);
}
//“com/yq/xxx.class” -> byte[]
private byte[] loadClassData(String name) {
name = dotToSplit("out.production.MyJVM."+name)+".class" ;
byte[] result = null ;
FileInputStream inputStream = null ;
ByteArrayOutputStream output = null ;
try {
inputStream = new FileInputStream( new File( name) );
//inputStream -> byte[]
output = new ByteArrayOutputStream();
byte[] buf = new byte[2];
int len = -1;
while ((len = inputStream.read(buf)) != -1) {
output.write(buf, 0, len);
}
result = output.toByteArray();
}catch (Exception e){
e.printStackTrace(); ;
}finally {
try {
if(inputStream != null )inputStream.close();
if(output != null ) output.close();
}catch (Exception e){
e.printStackTrace();
}
}
return result ;
}
public static void main(String[] args) throws Exception{
//自定义加载器的对象
MyClassLoaderImpl myClassLoader = new MyClassLoaderImpl();//默认在双亲委派时,会根据正规流程:系统——》扩展->根
// MyClassLoaderImpl myClassLoader = new MyClassLoaderImpl();//直接指定某个 具体的的委派
Class<?> aClass = myClassLoader.loadClass("com.yanqun.parents.MyDefineCL");
System.out.println(aClass.getClassLoader());
MyDefineCL myDefineCL = (MyDefineCL)(aClass.newInstance() );
myDefineCL.say();
}
public static String dotToSplit(String clssName){
return clssName.replace(".","/") ;
}
}
class MyDefineCL{
public void say(){
System.out.println("Say...");
}
}
实现流程:
1.public class MyClassLoaderImpl extends ClassLoader
2.findClass(String name){…} :直接复制文档中的NetworkClassLoader中的即可
3.loadClassData(String name){…} :name所代表的文件内容->byte[]
4.细节:
loadClassData(String name): 是文件形式的字符串a/b/c.class,并且开头out.production..
findClass(String name):是全类名的形式 a.b.c.class,并且开头 是: 包名.类名.class
操作思路:
要先将 .class文件从classpath中删除,之后才可能用到 自定义类加载器;否在classpath中的.class会被 APPClassLoader加载
package com.yanqun.parents;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
//public class MyException extends Exception{...}
public class MyClassLoaderImpl extends ClassLoader{
private String path ; //null
//优先使用的类加载器是:getSystemClassLoader()
public MyClassLoaderImpl(){
super();
}
public MyClassLoaderImpl(ClassLoader parent){
//扩展类加载器
super(parent);
}
//com.yq.xx.class
public Class findClass(String name) {
System.out.println("findClass...");
byte[] b = loadClassData(name);
return defineClass(name, b, 0, b.length);
}
//“com/yq/xxx.class” -> byte[]
private byte[] loadClassData(String name) {
System.out.println("加载loadClassData...");
if(path != null){
//name: com.yanqun.parents.MyDefineCL
name = path+ name.substring( name.lastIndexOf(".")+1 )+".class" ;
}else{
//classpath ->APPClassLoader
name = dotToSplit("out.production.MyJVM."+name)+".class" ;
}
byte[] result = null ;
FileInputStream inputStream = null ;
ByteArrayOutputStream output = null ;
try {
inputStream = new FileInputStream( new File( name) );
//inputStream -> byte[]
output = new ByteArrayOutputStream();
byte[] buf = new byte[2];
int len = -1;
while ((len = inputStream.read(buf)) != -1) {
output.write(buf, 0, len);
}
result = output.toByteArray();
}catch (Exception e){
e.printStackTrace(); ;
}finally {
try {
if(inputStream != null )inputStream.close();
if(output != null ) output.close();
}catch (Exception e){
e.printStackTrace();
}
}
return result ;
}
public static void main(String[] args) throws Exception {
System.out.println("main...");
//自定义加载器的对象
MyClassLoaderImpl myClassLoader = new MyClassLoaderImpl();//默认在双亲委派时,会根据正规流程:系统——》扩展->根
myClassLoader.path = "d:/" ;
//MyClassLoaderImpl myClassLoader = new MyClassLoaderImpl();//直接指定某个 具体的的委派
Class<?> aClass = myClassLoader.loadClass("com.yanqun.parents.MyDefineCL");
System.out.println(aClass.getClassLoader());
// MyDefineCL myDefineCL = (MyDefineCL)( aClass.newInstance()) ;
}
public static String dotToSplit(String clssName){
return clssName.replace(".","/") ; }
}
class MyDefineCL{
public void say(){
System.out.println("Say...");
}
}
代码流程:
loadClass() ->findClass()->loadClassData()
一般而言,启动类加载loadClass();
实现自定义加载器,只需要:
1.继承ClassLoader
2重写的 findClass()
情况一:用APPClassLoader
classpath中的MyDefineCL.class文件:
1163157884
1163157884
d盘中的MyDefineCL.class文件:
356573597
说明:类加载器 只会把同一个类 加载一次; 同一个class文件 加载后的位置
结论:
自定义加载器 加载.class文件的流程:
先委托APPClassLoader加载,APPClassLoader会在classpath中寻找是否存在,如果存在 则直接加载;如果不存在,才有可能交给 自定义加载器加载。
package com.yanqun.parents;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
//public class MyException extends Exception{...}
public class MyClassLoaderImpl extends ClassLoader{
private String path ; //null
//优先使用的类加载器是:getSystemClassLoader()
public MyClassLoaderImpl(){
super();
}
public MyClassLoaderImpl(ClassLoader parent){
//扩展类加载器
super(parent);
}
//com.yq.xx.class
public Class findClass(String name) {
// System.out.println("findClass...");
byte[] b = loadClassData(name);
return defineClass(name, b, 0, b.length);
}
//“com/yq/xxx.class” -> byte[]
private byte[] loadClassData(String name) {
// System.out.println("加载loadClassData...");
if(path != null){
//name: com.yanqun.parents.MyDefineCL
// System.out.println("去D盘加载;;");
name = path+ name.substring( name.lastIndexOf(".")+1 )+".class" ;
}
byte[] result = null ;
FileInputStream inputStream = null ;
ByteArrayOutputStream output = null ;
try {
inputStream = new FileInputStream( new File( name) );
//inputStream -> byte[]
output = new ByteArrayOutputStream();
byte[] buf = new byte[2];
int len = -1;
while ((len = inputStream.read(buf)) != -1) {
output.write(buf, 0, len);
}
result = output.toByteArray();
}catch (Exception e){
e.printStackTrace(); ;
}finally {
try {
if(inputStream != null )inputStream.close();
if(output != null ) output.close();
}catch (Exception e){
e.printStackTrace();
}
}
return result ;
}
public static void main(String[] args) throws Exception {
// System.out.println("main...");
//自定义加载器的对象
// MyClassLoaderImpl myClassLoader = new MyClassLoaderImpl();//默认在双亲委派时,会根据正规流程:系统——》扩展->根
// myClassLoader.path = "d:/" ;
// Class<?> aClass = myClassLoader.loadClass("com.yanqun.parents.MyDefineCL");
// System.out.println(aClass.hashCode());
MyClassLoaderImpl myClassLoader2 = new MyClassLoaderImpl();//默认在双亲委派时,会根据正规流程:系统——》扩展->根
Class<?> aClass2 = myClassLoader2.loadClass("com.yanqun.parents.MyDefineCL");
System.out.println(aClass2.hashCode());
// System.out.println(aClass.getClassLoader());
// MyDefineCL myDefineCL = (MyDefineCL)( aClass.newInstance()) ;
}
public static String dotToSplit(String clssName){
return clssName.replace(".","/") ; }
}
class MyDefineCL{
public void say(){
System.out.println("Say...");
}
}
通过以下源码可知,在双亲委派体系中,“下面”的加载器 是通过parent引用 “上面”的加载器。即在双亲委派体系中,各个加载器之间不是继承关系。
public abstract class ClassLoader {
private static native void registerNatives();
static {
registerNatives();
}
// The parent class loader for delegation
// Note: VM hardcoded the offset of this field, thus all new fields
// must be added *after* it.
private final ClassLoader parent;
ClassLoader源码解读
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 {
//如果“父类”为空,说明是双亲委派的顶层了,就调用顶层的加载器(BootstrapClassLoader)
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
//如果“父类”加载失败,则只能自己加载(自定义加载器中的findClass()方法)
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;
}
}
3 双亲委派机制优势:
可以防止用户自定义的类 和 rt.jar中的类重名,而造成的混乱
自定义一个java.lang.Math(和jdk中rt.jar中的类重名)
package java.lang;
public class Math {
public static void main(String[] args) {
System.out.println("hello Math...");
}
}
运行结果:
原因:
根据双亲委派, 越上层的加载器越优先执行。
最顶层的加载器是 根加载器,根加载器就会加载rt.jar中的类。
因此rt.jar中的Math会被优先加载。
即程序最终加载的是不是我们自己写的Math,而是jdk/rt.jar中 内置的Math;
而内置的Math根本没有提供main()方法,因此报 无法找到main()。
4 实验:
将相关联的类A.class和B.class分别用 不同的类加载器加载
A和B是继承关系
public class B{
public B(){
System.out.println("B被加载了,加载器是: "+ this.getClass().getClassLoader());//对象使用之前,必然先把此对象对应的类加载
}
}
public class A extends B
{
public A(){
super();
System.out.println("A被加载了,加载器是: "+ this.getClass().getClassLoader());//对象使用之前,必然先把此对象对应的类加载
}
}
//AppClassLoader.class : TestMyClassLoader2
//自定义加载器: A.class/B.class
public class TestMyClassLoader2 {
public static void main(String[] args) throws Exception{
MyClassLoaderImpl myClassLoader = new MyClassLoaderImpl() ;
//自定义加载路径
myClassLoader.path = "d:/" ;
Class<?> aClass = myClassLoader.loadClass("com.yanqun.parents.A");
Object aObject = aClass.newInstance();//newInstance()会调用 该类的构造方法(new 构造方法())
System.out.println(aObject);
}
}
A和B不是继承关系
public class Y {
public Y(){
System.out.println("Y被加载了,加载器是: "+ this.getClass().getClassLoader());//对象使用之前,必然先把此对象对应的类加载
}
}
public class X {
public X(){
new Y() ;//加载Y(系统加载器)
System.out.println("X被加载了,加载器是: "+ this.getClass().getClassLoader());//对象使用之前,必然先把此对象对应的类加载
}
}
//AppClassLoader.class : TestMyClassLoader2
//自定义加载器: A.class/B.class
public class TestMyClassLoader3 {
public static void main(String[] args) throws Exception{
MyClassLoaderImpl myClassLoader = new MyClassLoaderImpl() ;
//自定义加载路径
myClassLoader.path = "d:/" ;
//程序第一次加载时(X),使用的是 自定义加载器
Class<?> aClass = myClassLoader.loadClass("com.yanqun.parents.X");
Object aObject = aClass.newInstance();//newInstance()会调用 该类的构造方法(new 构造方法())
System.out.println(aObject);
}
}
存在继承关系
A.class: classpath
B.class: classpath
原因
同一个AppClassLoader 会同时加载A.class和B.class
--
A.class: d:\
B.class: classpath
原因
A.class:自定义加载器加载
B.class:被AppClassLoader加载
因此,加载A.class和B.class的不是同一个加载器
IllegalAccess
---
A.class: classpath
B.class: d:\
NoClassDefFoundError
原因:
A.class: 被AppClassLoader加载
B.class: 自定义加载器加载
因此,加载A.class和B.class的不是同一个加载器
--
A.class d:\
B.class d:\
TestMyClassLoader2 can not access a member of class com.yanqun.parents.A with modifiers "public"
A.class/B.class: 自定义加载器加载
原因是 main()方法所在类在 工程中(APPClassLoader),而A和B不在工程中(自定义加载器)。
造成这些异常的核心原因: 命名空间(不是由同一个类加载器所加载)
----
没有继承关系
X.class: D: 自定义加载器
Y.class: classpath 系统加载器
Y被加载了,加载器是: sun.misc.Launcher$AppClassLoader@18b4aac2
X被加载了,加载器是: com.yanqun.parents.MyClassLoaderImpl@74a14482
---
X.class: classpath 系统加载器
Y.class: D: 自定义加载器
java.lang.NoClassDefFoundError: com/yanqun/parents/Y
--
-
如果存在继承关系:
继承的双方(父类、子类)都必须是同一个加载器,否则出错;
-
如果不存在继承关系:
子类加载器可以访问父类加载器加载的类(自定义加载器,可以访问到 系统加载器加载的Y类);反之不行(父类加载器 不能访问子类加载器)
核心: 双亲委派
如果都在同一个加载器 ,则不存在加载问题; 如果不是同一个,就需要双亲委派。
如果想实现各个加载器之间的自定义依赖,可以使用ogsi规范
OSGi:
1.网状结构的加载结构
2.屏蔽掉硬件的异构性。例如,可以将项目部署在网络上,可以在A节点上 远程操作B节点。在操作上,可以对硬件无感。也可以在A节点上 对B节点上的项目进行运维、部署,并且立项情况下 在维护的期间,不需要暂时、重启。
5 类的卸载
1.系统自带(系统加载器、扩展加载器、根加载器):这些加载器加载的类 是不会被卸载。
2.用户自定义的加载器,会被GC卸载GC