JVM的类加载机制以及双亲委派模型

8 篇文章 0 订阅

1 类与类加载器 

类加载:通过一个类的全限定名来获取描述此类的二进制字节流

对于任意一个类,都需要由加载它的类加载器和这个类本身一同确定其在Java虚拟机中的唯一性。换句话说,如果两个类来源于同一个class文件,但是由不同的类加载器加载,那么这就是两个不同的类

1.1 代码验证

package basicKnowledge.jvm.classload;

import sun.misc.Launcher;

import java.io.IOException;
import java.io.InputStream;

/**
 * @基本功能:
 * @program:summary
 * @author:peicc
 * @create:2019-08-13 10:57:04
 **/
public class TestClassLoader {
    public static void main(java.lang.String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        ClassLoader myLoader=new ClassLoader() {
            @Override
            public Class<?> loadClass(java.lang.String name) throws ClassNotFoundException {
                java.lang.String fileName=name.substring(name.lastIndexOf(".")+1)+".class";
                InputStream is=getClass().getResourceAsStream(fileName);
                if(is==null){
                    return super.loadClass(name);
                }
                try {
                    byte[] b=new byte[is.available()];
                    is.read(b);
                    return defineClass(name,b,0,b.length);
                }
                catch (IOException e) {
                   throw new ClassNotFoundException(name);
                }
            }
        };
        //利用自定义的类加载器加载该类
        Class clas=myLoader.loadClass("basicKnowledge.jvm.classload.TestClassLoader");
        //通过该类实例化对象
        Object obj=clas.newInstance();
        //输出该对象所属的类
        System.out.println(obj.getClass());
        //判断该对象是否由basicKnowledge.jvm.classload.TestClassLoader类实例化而来
        System.out.println(obj instanceof TestClassLoader);
        //输出该对象所属的类的类加载器
        System.out.println(obj.getClass().getClassLoader());
        //输出basicKnowledge.jvm.classload.TestClassLoader类的类加载器
        System.out.println(basicKnowledge.jvm.classload.TestClassLoader.class.getClassLoader());
    }
}

1.2  运行结果

class basicKnowledge.jvm.classload.TestClassLoader
false
basicKnowledge.jvm.classload.TestClassLoader$1@591f989e
sun.misc.Launcher$AppClassLoader@18b4aac2

1.3 结果分析

  • 第一个输出结果很好理解,obj对象是类basicKnowledge.jvm.classload.TestClassLoader实例化得到的对象
  • 第二个输出结果可能不好理解,obj对象在与类basicKnowledge.jvm.classload.TestClassLoader进行类型检查时,输出了false。也就是说obj并非basicKnowledge.jvm.classload.TestClassLoader实例化得到的对象,貌似与第一个输出结果矛盾。
  • 接着,我们通过第三、四个输出结果对obj所属类的类加载器以及basicKnowledge.jvm.classload.TestClassLoader类的类加载器进行了输出,可以发现,两者的类加载器不同,所以类型检查时输出了false
  • 在java虚拟机中存在两个TestClassLoader,一个由系统应用程序类加载器加载,另一个由自定义的类加载器myLoader加载。虽然来自同一个字节码文件,但依然是两个独立的类。

2 双亲委派模型 

2.1 类加载器分类

从JVM的角度分:

  • 启动类加载器(Bootstrap ClassLoader):C++实现,是JVM的一部分
  • 其他类加载器:java语言实现,独立于虚拟机,全部继承自java.lang.ClassLoader

从java开发人员的角度:

  • 启动类加载器(Bootstrap ClassLoader):负责加载<JAVA_HOME>\lib目录中的,或者被-Xbootclasspath参数所指定的路径中,并且是JVM所识别的类(仅按照文件名识别,如rt.jar,名字不符合的类库及时放在lib目录中也不会被被加载)。开发人员无法直接使用启动类加载器,用户在编写类加载器时,如果需要把加载请求委派给引导类加载器,直接使用null代替即可。
  • 扩展类加载器(Extension ClassLoader):负责加载<JAVA_HOME>\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库。开发人员可以直接使用扩展类加载器
  • 应用程序类加载器(Application ClassLoader):负责加载用户路径(ClassPath)上所指定的类,一般情况下为程序的默认类加载器

2.2 双亲委派模型

  • 上图所示的类加载器的层次关系称为类加载器的双亲委派模型。 
  • 除了Bootstrap ClassLoader外,其余的类加载器都有自己的父类加载器(这里的父子关系以组合的方式体现)

2.3 双亲委派的工作过程

 如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载

3 扩展:能否自己写一个String类 

这是一个面试可能被问的问题,网上关于此问题的答案有不统一,有说可以的,也有说不行的。事实情况究竟是什么样的呢?经过我亲自测试,我觉得应该分情况讨论:

如果新建一个java.lang包,然后在里面自己定义一个String,那么我告诉你是不行的。网上很多答案都说可以,这些答案认为可以自己实现一个String,但由于双亲委派机制,自己实现的String只是无法被加载运行,因此启动类加载器在加载的时候会加载rt.jar中的String,而自己实现的String无法被使用到。但经过我测试,发现不仅不会被使用到,而且还会出现运行时的异常。进一步分析,只要你自己新建java包,这里面的类就不会被加载通过。 

 代码测试

package java.lang;

/**
 * @基本功能:
 * @program:untitled
 * @author:peicc
 * @create:2019-08-13 14:38:57
 **/
public class String {
    public static void main(String[] args) {
        System.out.println();
    }
}

运行结果 

错误: 在类 java.lang.Object 中找不到 main 方法, 请将 main 方法定义为:
   public static void main(String[] args)
否则 JavaFX 应用程序类必须扩展javafx.application.Application

如果新建一个其他包,然后在其他包里去自定义一个String,那么我告诉你是可以的。 

 自定义String类

package basicKnowledge.jvm.classload;

/**
 * @基本功能:
 * @program:summary
 * @author:peicc
 * @create:2019-08-13 13:15:34
 **/
public class String {
    public String() {
        System.out.println("自定义String");
    }
}

测试类 

package basicKnowledge.jvm.classload;

/**
 * @基本功能:
 * @program:summary
 * @author:peicc
 * @create:2019-08-13 15:10:06
 **/
public class Test {
    public static void main(java.lang.String[] args) {
        String a=new String();
    }
}

运行结果 

自定义String

参考文献

《深入理解java虚拟机》

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值