JVM(二):编译常量,类加载器深度解析

调用类的静态常量并不会导致类的初始化。

先看一个示例:

package com.lory.jvm;

class Example{

    public static final int a = 6/3;

    static {
        System.out.println("Example 类初始化。");
    }
}
public class Test1 {

    public static void main(String[] args) {
        System.out.println(Example.a);
    }
}

输出:2

package com.lory.jvm;

import java.util.Random;

class Example2{
    public static final int a = new Random().nextInt(100);

    static {
        System.out.println("Example2 类初始化。");
    }
}
public class Test2 {

    public static void main(String[] args) {
        System.out.println(Example2.a);
    }
}
输出:Example2 类初始化。
87

当Java虚拟机初始化一个类时,要求必须先初始化它的所有父类,但这条规则并不适用于接口,在初始化一个类时,并不会先初始化它所实现的接口。初始化一个接口时,并不会先初始化它的父接口,因此,一个接口并不会因为他的实现类或子接口的初始化而初始化,只有当程序首次使用特定接口的静态变量时,才会导致接口的初始化。

看几个案例:

package com.lory.jvm;

class Parent{
    static int a = 5;

    static {
        System.out.println("Parent static block");
    }
}

class Child extends Parent{
    static int b = 1;
    static {
        System.out.println("Child static block");
    }
}
public class Test3 {

    static {
        System.out.println("Test3 static block");
    }

    public static void main(String[] args) {
        System.out.println(Child.b);
    }
}
输出:
Test3 static block
Parent static block
Child static block
1
package com.lory.jvm;

class Parent2{
    static int a = 3;
    static {
        System.out.println("Parent2 static block");
    }
}

class Child2 extends Parent2{
    static int b = 4;
    static {
        System.out.println("Child2 static block");
    }
}
public class Test4 {
    static {
        System.out.println("Test4 static block");
    }

    public static void main(String[] args) {
        Parent2 parent2;
        System.out.println("------------------");
        parent2 = new Parent2();
        System.out.println(Parent2.a);
        System.out.println(Child2.b);
    }
}
输出
Test4 static block
------------------
Parent2 static block
3
Child2 static block
4

package com.lory.jvm;

class Parent3{
    static int a =3;

    static {
        System.out.println("Parent3 static block");
    }

    static void doSomethimg(){
        System.out.println("do something");
    }
}

class Child3 extends Parent3{
    static {
        System.out.println("Child3 static block");
    }
}
public class Test5 {

    public static void main(String[] args) {
        System.out.println(Child3.a);
        Child3.doSomethimg();
    }
}
输出
Parent3 static block
3
do something


调用ClassLoader类的loadClass方法并不是对类的主动使用,不会导致类的初始化。

package com.lory.jvm;

class CL{
    static {
        System.out.println("CL static block");
    }
}
public class ClassLoaderTest2{

    public static void main(String[] args) throws ClassNotFoundException {
        ClassLoader loader = ClassLoader.getSystemClassLoader();
        Class<?> clazz = loader.loadClass("com.lory.jvm.CL");
        System.out.println("------------------------");
        clazz = Class.forName("com.lory.jvm.CL");
    }

}
输出:
------------------------
CL static block


类加载器:

        类加载器负责把类加载到虚拟机内存当中,从JDK1.2版本开始,类的加载过程采用父委托机制,这种机制能更好的保证Java平台的安全性,在此委托机制当中,除了Java虚拟机自带的根类加载器外,其余的类加载器都有且只有一个父类加载器,例如,当Java程序请求loader1加载器加载Test类时,loader1会先请求loader1的父类加载器加载Test类,若父加载器能够完成加载任务,则由父加载器加载完成,否则才由loader1加载器本身完成加载任务。


Java虚拟机自带了以下几种类加载器:

        一:根类加载器(BootStrap):该加载器没有父加载器,它负责加载虚拟机的核心类库,根类加载器从系统指定的sun.boot.class.path所指定的目录当中加载类库,根类加载器的实现依赖于底层操作系统,属于虚拟机实现的一部分,没有继承java.lang.ClassLoader类。

        二:扩展(Extension)类加载器:它的父加载器为根类加载器,它从java.ext.dirs系统属性所指定的目录当中加载类库,或者从jdk的安装目录jre\lib\ext子目录下加载类库,如果把用户自定义的jar文件放在该目录下,也会自动由扩展加载器加载,扩展类加载器是纯java编写,是Java.lang.ClassLoader类的子类。

        三:系统(System)类加载器:也称为应用类加载器。它的父加载器为扩展类加载器,它从环境变量classpath或者系统属性的,java.class.path所指定的目录中加载类,他是用户自定义的加载器的父类加载器,系统类加载器是纯java编写,是Java.lang.ClassLoader类的子类。

用户自定义的类加载器在没指定父类加载器的情况下,其父类加载器默认为系统类加载器。

        jvm自带类加载器之间的关系:              

如果所有的父加载器以及加载器本身都无法加载该类,那么会跑出ClassNotFound异常。

若有一个类加载器能够成功加载类,那么这个加载器称为定义类加载器,所有能成功返回Class对象的引用的加载器(包括定义类加载器)都被称为初始类加载器。

为什么要用父加载机制?

        JDK的这种实现主要是考虑了安全性。因为在此机制下,用户自定义的加载器不可能加载应该由父类加载器加载的可靠类。

从而防止不可靠甚至恶意的代码代替由父加载器加载的可靠代码。例如:java.lang.Object类总是由根类加载器加载,其它用户自定义的任何加载器类都不可能加载含有恶意代码的Object类。


命名空间

        每个类加载器都有自己的命名空间,命名空间由该加载器以及所有的父类加载器所加载的类组成,在同一个命名空间下,不可能出现类的完整名字(包括包名)相同的两个类。在不同的命名空间当中,则有可能出现。


运行时包:

        由同一个类加载器加载的属于相同包的类组成了运行时包,决定两个类是不是同一个运行时包,不仅仅要看两个类是否有相同的包名,还要看定义类加载器是否相同,只有属于同一运行时包的类才能相互访问包可见的类和类的成员,这样能限制用户自定义的类冒充核心类库的类,去访问核心类库的包可见成员,假如用户自定义了一个java.lang.SPY类,并由用户自定义的类加载器加载,由于该类和java.lang.*核心类库不时由一个类加载器加载,那么SPY类就不能访问java.lang.*核心类库的包可见成员。



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值