JVM2-类加载机制

本文参考的《深入理解Java虚拟机》一书第七章,属于个人对这一章节的总结笔记。《深入理解Java虚拟机》有更多的代码解释。
什么是java虚拟机的类加载机制?
就是java虚拟机讲描述类的数据从class文件中读取到内存中,对数据进行验证,转换解析和初始化,最终转换成能够被java虚拟机直接使用的java类型。
生命周期?
一个类从加载到卸载,经过7个步骤
加载–>验证–>准备–>解析–>初始化–>使用–>卸载
何时加载类?何时初始化类?
对于何时加载类,《JAVA虚拟机规范》并没有进行强制的规范,由虚拟机去自由把握。
但是对于初始化,《Java虚拟机规范》是有严格规范的,有且只有6种其情况会初始化类。
1、new,putstatic、setstatic,invokestatic指令的时候,而生成这些指令有以下几个

	a、new一个对象
	b、读取或设置类静态字段(final static 修饰的除外)
	c、调用静态方法

2、反射方法获取类的时候
3、初始化子类的时候,其父类还没有初始化,会优先初始化父类
4、当虚拟机启动,主类main方法所在类会优先初始化
5、 当使用jdk1.7动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getstatic,REF_putstatic,REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行初始化,则需要先出触发其初始化
6、当一个接口中定义了默认方法,如果该接口的实现类发生了初始化,则该接口要在其之前触发初始化

public class ClassLoading0 {
/*
    类的初始化会执行静态块,这里用静态块来测试类是否被加载

 */
    public static void main(String[] args) throws IllegalAccessException, InstantiationException {
        //使用new关键字时--》结果:会初始化类
        //new ClassLoading01();
        //读取或设置静态字段--》结果:会初始化类
        //int attr1=ClassLoading01.staticAttr;
        //读取final修饰的静态字段--》结果:不会初始化类,原因是final修饰的变量是常量,放于常量池,并且有static修饰。直接从常量池中拿。
        //int attr2=ClassLoading01.finalStaticAttr;
        //反射读取---》会初始化类
        //ClassLoading01.class.newInstance();
        //初始化子类时,会先去初始化父类
        new ClassLoading02();

    }
}
class ClassLoading01 {

    public static int staticAttr=1;
    public  final static int finalStaticAttr=1;
    public  static void classLoading01Test(){
        System.out.println("调用静态方法");
    }

    static {
        System.out.println("ClassLoading01静态块");
    }
}

class ClassLoading02 extends ClassLoading01{
    static {
        System.out.println("ClassLoading02静态块");
    }
}


被动引用案例
1、当子类调用父类的静态变量时,子类不会初始化
2、当创建一个引用数组时,不会初始化该类

public class ClassLoading0 {
/*
    类的初始化会执行静态块,这里用静态块来测试类是否被加载

 */
    public static void main(String[] args)  {
       //使用子类引用父类的静态变量或者常量时---》结果是:子类并不会初始化
        //int attr1 = ClassLoading02.staticAttr;
        //int attr2 = ClassLoading02.finalStaticAttr;
        //通过数组定义引用类,不会初始化该类,其父类也不会初始化
        ClassLoading02[] classLoading02s = new ClassLoading02[1];
    }
}
class ClassLoading01 {

    public static int staticAttr=1;
    public  final static int finalStaticAttr=1;
    public  static void classLoading01Test(){
        System.out.println("调用静态方法");
    }

    static {
        System.out.println("ClassLoading01静态块");
    }
}

class ClassLoading02 extends ClassLoading01{
    static {
        System.out.println("ClassLoading02静态块");
    }
}

类加载的步骤
上面讲了类加载的整个生命周期是7步
类加载的过程是5步
加载–>验证–>准备–>解析–>初始化

加载:
虚拟机完成了以下3个过程
1、通过类的全限定名来获取此类的二进制字节流
2、将字节流的静态数据结构转成方法区的运行时数据结构
3、在堆区生成Class对象
验证:

文件格式验证
元数据验证
字节码验证
符号引用验证
准备:
正式为类中的静态变量分配内存,并进行初始化赋值

public static int a=1;
这时候会额为a进行赋初值,也就是为0。这个阶段一直都是0。把1赋值给a是在初始化阶段。
但是如果是
public final static a=1;
这个时候,由于final修饰,并不会赋初值,而是直接把1赋值给a
解析:
符号引用变成直接引用
初始化:
虚拟机执行类中的java程序
执行静态块和为静态变量赋值

类加载器
每个类加载器都有自己的独立命名空间(在方法区)
也就是加载器加载的类,是放在方法区的不同位置。因此不同类加载器加载统一个类,由于内存地址不同,则这两个类不相等。但是统一个类加载器只会加载一次同一个类,在进行加载类时,会根据类的全限定名寻找该类是否被加载。

Hotspot类加载器分为几种?
从虚拟机角度来看,分为两种,一种是由c++编写的启动类加载器,另一种就是由java编写的类加载器,继承自抽闲类java.Lang.ClassLoader
从开发人员的角度分为3种,启动类加载器,拓展类加载器,应用类加载器
*****启动类加载器加载位置–><JAVA_HOME>\lib目录或者-Xbooyclasspath参数指定的路径下存放的。
*****拓展类加载器加载位置–><JAVA_HOME>\lib\ext目录或被java.ext.dirs系统变量所指定的路径中的所有类库。
*****应用类加载器–>加载用户类路径(ClassPath)上的所有类库
以上3个加载器是虚拟机为我们提供加载类库的位置。当我们想从其他位置加载类时。我们可以自定义类加载器。

public class ClassLoaderTest {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        ClassLoader myClass = new ClassLoader() {
            @Override
            public Class<?> loadClass(String name) throws ClassNotFoundException {
                try {
                    String fileName = name.substring(name.lastIndexOf("." )+1) + ".class";
                    InputStream stream = getClass().getResourceAsStream(fileName);
                    if (null == stream) {
                        return super.loadClass(name);
                    }

                    byte[] bytes = new byte[stream.available()];
                    stream.read(bytes);
                    return defineClass(name,bytes,0, bytes.length);
                } catch (IOException e) {
                    throw new ClassNotFoundException(name);
                }
            }
        };

        Object instance = myClass.loadClass("com.cjx.cjxtest.String").newInstance();
        System.out.println(instance.getClass());
       

    }
}


双亲委派机制
在这里插入图片描述
双亲委派机制就是,当一个类加载器收到请求加载某个类时,这个类并不会马上去加载这个类,而是委派给父类去加载,每一层类加载器都是如此,最终加载请求都会委派到最顶层的启动类加载器,当父类在自己的搜索范围内找不到该类时,会反馈给子类加载器,子类加载器才会自己去尝试加载。

为何要有双亲委派机制?
如果每一个类都被多个加载器加载,这时候在虚拟机就会存在多个该类,但是这个类并不是同一个,也不相等,在使用起来分不清。
例如:Java.Lang.Object类是在rt.jar包中,无论哪个类加载器加载这个类都会委派到最顶层的启动类加载器中,确保了这个类在虚拟机中只有一份,

如果我在classpath下创建一个Java.Lang.Object类,编译会通过,但是并不会去加载这个类。

打破双亲委派
双亲委派很好的解决了各个类加载器加载基础类的一致性问题。但是基础类如果需要回调用户代码,那就成了成模型的弊端。例如,Driver接口是定义在jdk中的,DriverManger也在jdk中,启动类加载器只能加载JAVA_HONE的lib下的文件,而数据库厂商提供的Driver接口的实现类在classPath路径下,由应用类加载器加载,所以启动类加载器委派子类来加载Driver实现。

SPI机制
在MATE_INF下的services下创建文件,文件中放接口实现类的权限定名,而文件名则为接口的全限定名。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值