JVM学习_类加载篇

类加载机制

JVM中类的装载是由类加载器(ClassLoader)和它的子类来实现的,Java中的类加载器是一个重要的Java运行时系统组件,它负责在运行时查找和装入类文件中的类。

由于Java的跨平台性,经过编译的Java源程序并不是一个可执行程序,而是一个或多个类文件。当Java程序需要使用某个类时,JVM会确保这个类已经被加载、连接(验证、准备和解析)和初始化。

  • 类的加载是指把类的.class文件中的数据读入到内存中,通常是创建一个字节数组读入.class文件,然后产生与所加载类对应的Class对象。加载完成后,Class对象还不完整,所以此时的类还不可用。

当类被加载后就进入连接阶段,这一阶段包括:

  • 验证: 字节码文件是否正确
  • 准备: 为静态变量分配内存并设置默认的初始值;此阶段仅仅只为静态类变量(即static修饰的字段)分配内存和初始化默认值(比如 static int num = 5,这里只将num初始化为0,5的值将会在初始化的时候赋值);对于final static 修饰的常量,编译的时候就会分配了.也不会分配内存.
  • 解析: 将符号引用替换为直接引用

最后JVM对类进行初始化,包括:

  • 1: 如果类存在直接的父类并且这个类还没有被初始化,那么就先初始化父类;

ps:父类的静态字段>父类静态代码块>子类静态字段>子类静态代码块>父类成员变量(非静态字段>父类非静态代码块>父类构造器>子类成员变量>子类非静态代码块>子类构造器)

  • 2: 如果类中存在初始化语句,就依次执行这些初始化语句。

类的加载是由类加载器完成的,类加载器包括:

  • Bootstrap:一般用本地代码实现,负责加载JVM基础核心类库(rt.jar);

  • Extension:从java.ext.dirs系统属性所指定的目录中加载类库,它的父加载器是Bootstrap;

  • System:又叫应用类加载器,其父类是Extension。它是应用最广泛的类加载器。它从环境变量classpath或者系统属性java.class.path所指定的目录中记载类,是用户自定义加载器的默认父加载器。
    在这里插入图片描述

类加载器与双亲委派机制

上图
在这里插入图片描述

public static void main(String[] args) {
        System.out.println(String.class.getClassLoader());
        System.out.println(sun.net.spi.nameservice.dns.DNSNameService.class.getClassLoader().getClass().getName());
        System.out.println(ClassLoader_test.class.getClassLoader().getClass().getName());

        System.out.println();

        //通过ClassLoader的getSystemClassLoader获取系统默认的加载器,默认是sun.misc.Launcher$AppClassLoader@18b4aac2,应用类加载器
        ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println("the appClassLoader:" + appClassLoader);

        //获取应用加载器的上层加载器
        ClassLoader extClassLoader = appClassLoader.getParent();
        System.out.println("the extClassLoader:" + extClassLoader);

        //获取扩展类加载器的上层加载器
        ClassLoader bootStarapLaoder = extClassLoader.getParent();
        System.out.println("the bootStrapLoader:" + bootStarapLaoder);

    }

结果

D:\Java\jdk1.8.0_131\bin\java.exe ...
null  -- 这里应该是bootStarpClassLoader,但是这个根(引导类)加载器是有C ++ 实现的
sun.misc.Launcher$ExtClassLoader  -- sun.misc.Launcher-启动器实例
sun.misc.Launcher$AppClassLoader  -- ExtClassLoader和AppClassLoader都是Launcher类中的一个静态内部类

the appClassLoader:sun.misc.Launcher$AppClassLoader@18b4aac2  
the extClassLoader:sun.misc.Launcher$ExtClassLoader@45ee12a7
the bootStrapLoader:null

Process finished with exit code 0

比如我现在写了一个类: com.text.HelloWorld,那么他的加载过程:

图一

com.text.HelloWorld 应用类加载器(appClassLoader) 扩展类加载器(extClassLoader) 引导类加载器(bootStarpClassLoader) 判断有没有加载 我没有加载该类,你看看 我也没有加载,你看看 我也没加载,我去找了我的加载路径,没有这个类,你看看你的加载路劲 我去找了我的加载路径,也没有这个类,你看看你的加载路劲 我找到了,我来加载 com.text.HelloWorld 应用类加载器(appClassLoader) 扩展类加载器(extClassLoader) 引导类加载器(bootStarpClassLoader)

图二

com.text.HelloWorld 应用类加载器(appClassLoader) 扩展类加载器(extClassLoader) 引导类加载器(bootStarpClassLoader) 判断有没有加载 我没有加载该类,你看看 我也没有加载,你看看 我也没加载,我去找了我的加载路径,没有这个类,你看看你的加载路劲 我找到了,我来加载 com.text.HelloWorld 应用类加载器(appClassLoader) 扩展类加载器(extClassLoader) 引导类加载器(bootStarpClassLoader)

图三

com.text.HelloWorld 应用类加载器(appClassLoader) 扩展类加载器(extClassLoader) 引导类加载器(bootStarpClassLoader) 判断有没有加载 我没有加载该类,你看看 我也没有加载,你看看 我找到了,我来加载 com.text.HelloWorld 应用类加载器(appClassLoader) 扩展类加载器(extClassLoader) 引导类加载器(bootStarpClassLoader)

图四

com.text.HelloWorld 应用类加载器(appClassLoader) 扩展类加载器(extClassLoader) 引导类加载器(bootStarpClassLoader) 判断有没有加载 我加载了,你们歇着吧 com.text.HelloWorld 应用类加载器(appClassLoader) 扩展类加载器(extClassLoader) 引导类加载器(bootStarpClassLoader)

测试代码

package java.lang;

/**
 * @author 0101
 */
public class String {

    public static void main(String[] args) {
        System.out.println("双亲委派");
    }
}

jdk中的String类也是在这个路径下,我们现在运行这个min方法

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

Process finished with exit code 1

找不到min方法? 明明在这里啊

这里是因为jdk中也有一个同样路径的String类,是在rt.jar里,而tr.jar是被引导类加载器所加载的,对照上面的第三幅图,实际运行这个min方法后加载的是rt.jar下面的String类.显然那个类是没有min方法的

这样的加载过程称为双亲委派机制

为什么要设计双亲委派机制?

  • 避免类的重复加载:当父类已经加载了该类,就没必要子加载器再加载一次
  • 沙箱安全机制: 比如上面我们自己写的java.lang.String.class就不会被加载,这样便可以防止核心API库被随意篡改

为什么每次加载都要从应用类加载器往上找

  • 在实际开发中,95%的代码都是我们自己写的,那么这样做在这个类第一次加载的时候可能会很慢,但第二次就可以直接从应用类加载器拿,这样很快,上面图四

用户自定义类加载器

  • . 在Java的日常应用程序开发中,类的加载几乎是由上述3种类加载器相互配合执行的,在必要时,我们可以自定义类加载器,来定制类的加载方式

为什么要自定义类加载器

1.隔离加载类
2.修改类加载的方式
3.扩展加载源
4.防止源码泄漏

用户自定义类加载器实现步骤:

1.开发人员可以通过继承抽象类java.lang.ClassLoader类的方式,实现自己的类加载器满足一些特殊
  的需求.
2.在jdk1.2之前,在自定义类加载器时,总会继承ClassLoader类并重写loadClass()方法,从而实现自
  定义的类加载器.但在jdk1.2之后已不再建议用户去覆盖loadClass()方法,而是建议把自定义的类加
  载逻辑卸载findClass()方法中
3.在编写自定义类加载器时,如果没有太过于复杂的需求,可以直接继承URLClassLoader类,这样就可以
  避免自己去编写findClass()方法及获取字节流的方式,是自定义类加载器编写更加简洁

举个栗子

package JVM;

import java.io.*;

/**
 * 用户自定义类加载器
 * @author 0101
 */
public class CustomClassLoader extends ClassLoader{

    @Override
    //根据名称或位置加载.class字节码,然后使用defineClass
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            byte[] result = getClassFromCustomPath(name);
            if (result == null) {
                throw new FileNotFoundException();
            } else {
                //把字节码转化为Class
                return defineClass(name,result,0,result.length);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

        throw new ClassNotFoundException(name);
    }

    private byte[] getClassFromCustomPath(String name){
        //从自定义路径中加载制定类:细节略
        //如果指定路径的字节码文件进行加密,则需要在此方法中进行解密操作
        try{
            File filename = new File(name);
            BufferedInputStream in = new BufferedInputStream(new FileInputStream(filename));
            ByteArrayOutputStream out = new ByteArrayOutputStream(1024);
            byte[] temp = new byte[1024];
            int size = 0;
            while((size = in.read(temp)) != -1){
                out.write(temp, 0, size);
            }
            in.close();
            byte[] content = out.toByteArray();
            return content;
        } catch (IOException e){
            e.printStackTrace();
        }
        return null;
    }

    public static void main(String[] args) {
        CustomClassLoader customClassLoader = new CustomClassLoader();
        try {
            Class<?> clazz = Class.forName("JVM.CustomClassLoader",true,customClassLoader);
            Object obj = clazz.newInstance();
            System.out.println(obj.getClass().getClassLoader());

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

其他

在JVM中表示两个class对象是否为同一个类存在的两个必要条件

  1. 类的完整类名必须一致,包括包名
  2. 加载这个类的ClassLoader(指ClassLoader实例对象)必须相同

换句话说,在JVM中,即使这两个类对象(Class对象)来源同一个Class文件,被同一个虚拟机加载,但是只要他们的ClassLoader实例对象不同,那么这个类对象也是不相等的.

类加载器的引用

JVM必须知道一个类型是由引导类(启动)加载器加载的还是由用户类加载器加载的.如果一个类型是由用户类型加载器加载的,那么JVM会**将这个类加载器的一个引用作为类型信息的一部分保存在方法区中.**当解析一个类型到另一个类型引用的时候,JVM需要保证这两个类型的类加载器是相同的

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值