美文网首页
学习Java类加载机制

学习Java类加载机制

作者: 肚皮怪_Sun | 来源:发表于2018-05-25 11:33 被阅读0次

加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)、卸载(unloading)

类的生命周期

下面主要详细的讲类加载的过程,也就是加载、验证、准备、解析和初始化这5个阶段所执行的具体动作。

加载

“加载”是"类加载"过程的一个阶段,在加载阶段虚拟机需要完成三件事情:

  1. 通过一个类的全限定名来定义这个类二进制字节流
  2. 将这个字节流所代表的静态存储结构转化为方法区的运行数据结构
  3. 在内存中生成一个代表这个类的java.lang.class对象,作为方法区这个类的各个数据访问入口

验证

验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。

准备

准备阶段是正式为类变量分配内存并设置类变量(被static修饰的变量,而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在Java堆中。)初始值的阶段,这些变量所使用的内存 都将在方法区中进行分配。
需要说明的一点事,这里所说的初始值“通常情况”下是数据类型的零值,
例如:

 public static int value=123;

在准备阶段过后的初始化value是0 而不是123,因为这个时候尚未执行Java方法,而把value赋值为123的putstatic指令是程序被编译后,存放在类构造器方法中的,所以把value赋值123的动作将在初始化阶段才会执行。

解析

解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,类或接口的解析、字段解析、类方法解析、接口方法解析

初始化

在准备阶段,变量已经赋过一次系统要求的初始值,而在初始化阶段,则根据程序员通过程序制定的主观计划去初始化类变量和其他资源,或者可以从另外一个角度来表达:初始化阶段是执行类构造器<clinit>()方法的过程。
<clinit>()方法是由编译器自动收集类中的所有变量的赋值动作和静态语句块(static{}块)中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的,静态语句块中只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块可以赋值,但是不能访问;

static {
    i = 0;//编译时可以赋值
    System.out.println(i);/编译时提示Illegl forward reference
}
static int i=1;

<clinit>()方法与类的构造函数或者说实例构造器<init>()方法不同,它不需要显示的调用父类构造器,虚拟机会保证在子类的<clinit>()方法执行之前,父类的<clinit>()方法已经执行完毕。因此在虚拟机中第一个被执行的<clinit>()方法的类肯定是java.lang.object。

由于父类的<clinit>()方法先执行,也就意味着父类中定义的静态语句块优先于子类的变量赋值操作

类加载器

在类加载阶段中提到——“通过一个类的全限定名来定义这个类二进制字节流”,这个动作是放在Java虚拟机外部实现的,以便程序自己决定如何去获取所需要的类,实现这一动作的代码模块就是"类加载器"。
类加载器的作用
类加载器虽然说只是用于类的加载,但还起到另一层作用——通过类加载器和这个类本身一同确定其在Java虚拟机的中唯一性,每个类加载器也有一个独立的类空间。换一句话说就,比较两个类是否相等,不只是这两个类是否是是来自同一个class文件,还要由同一个类加载器来完成加载过程,否则这两个类不相等。

下面通过一段代码来演示这一过程:

  public static void main(String[]args)throws Exception {
    ClassLoader myLoader = new ClassLoader() {
        @Override
        public Class<?> loadClass(String name) throws ClassNotFoundException {
            try {
                String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
                InputStream is = getClass().getResourceAsStream(fileName);
                if (is == null) {
                    return super.loadClass(name);
                }
                byte[] b = new byte[is.available()];
                is.read(b);
                return defineClass(name, b, 0, b.length);
            } catch (IOException e) {
                throw new ClassNotFoundException(name);
            }
        }
    };
    Object obj = myLoader.loadClass("com.captain.wds.classLoad.ClassLoadTest").newInstance();
    System.out.println(obj.getClass());
    ClassLoader classLoader = ClassLoadTest.class.getClassLoader();
    System.out.println("classLoader  :" + classLoader.toString());

    System.out.println("myLoader  :" + myLoader.toString());
    System.out.println(obj instanceof com.captain.wds.classLoad.ClassLoadTest);
}

打印结果:

class com.captain.wds.classLoad.ClassLoadTest
classLoader  :sun.misc.Launcher$AppClassLoader@74a14482
myLoader  :com.captain.wds.classLoad.ClassLoadTest$1@330bedb4
false

可以发现,虽然是同一个类,但通过instanceof关键字的结果是false,一个是通过系统应用程序的类加载器加载而成,另一个是我们自定义的类加载器,虽然都是来之同一个class文件,得到的对象却不同等。

双亲委派模型

从Java虚拟机的角度上讲,只存在两种不同的类加载器:一种启动类加载器,是虚拟机的一部分,由C/C++来实现的:另一类就是其他所有类加载器,这类加载器独立于虚拟机,由Java代码实现,并且全部继承至Java.lang.ClassLoader。


类加载器双亲委派模型

类加载器之间的这种层次关系,称为类加载器的双亲委派模型(ParentsDelegation Model)。双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器。这里类加载器之间的父子关系一般不会以继承(Inheritance)的关系来实现,而是都使用组合(Composition)关系来复用父加载器的代码。
双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去加载,而是把这个请求委派给父类加载器完成,依次类推,每个类加载请求都会传递到顶层的启动类加载中,只有当父类加载器无法完成这个类加载请求时,子类加载器才会尝试自己去加载。

在java.lang.ClassLoader这个类中,实现双亲委派的主要代码也相当简单,先检查是否已经被加载过,若没有加载则调用父加载器的loadClass()方法,若父加载器为空则默认使用启动类加载器作为父加载器。如果父类加载失败,抛出ClassNotFoundException异常后,再调用自己findClass()方法进行加载。

protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
        // First, check if the class has already been loaded
        //首先先检查这个类是否已经被加载
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            try {
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader  
                //如果父类加载器抛出ClassNotFoundException 
                //说明父类加载器无法完成这个加载请求
            }

            if (c == null) {
                // If still not found, then invoke findClass in order
                // to find the class.
                //在父类加载器无法完成的情况下,调用自身的findClass来进行加载请求
                c = findClass(name);
            }
        }
        return c;
}

好了关于类加载机制的内容就学习到这了😄😄😄


风后面是风,天空上面是天空,而你的生活可以与众不同

相关文章

网友评论

      本文标题:学习Java类加载机制

      本文链接:https://www.haomeiwen.com/subject/mqtdjftx.html