java类加载器

什么是类加载器?

先了解类的加载过程:

这里写图片描述

类加载器(class loader)用来加载 Java 类到 Java 虚拟机中。他也是一个类。

一般来说,Java 虚拟机使用 Java 类的方式如下:
Java 源程序(.java 文件)在经过 Java 编译器编译之后就被转换成 Java 字节代码(.class 文件)。类加载器负责读取 Java 字节代码,并转换成java.lang.Class类的一个实例。每个这样的实例用来表示一个 Java 类。通过此实例的 newInstance()方法就可以创建出该类的一个对象。

这里写图片描述


java.lang.ClassLoader类

public abstract class ClassLoader extends Object
类加载器是负责加载类的对象。ClassLoader 类是一个抽象类。
如果给定类的二进制名称,那么类加载器会试图查找或生成构成类定义的数据。一般策略是将名称转换为某个文件名,然后从文件系统读取该名称的“类文件”。
每个 Class 对象都包含一个对定义它的 ClassLoader 的引用。

ClassLoader 双亲委托模型
ClassLoader 类使用委托模型来搜索类和资源。每个 ClassLoader 实例都有一个相关的父类加载器。需要查找类或资源时,ClassLoader 实例会在试图亲自查找类或资源之前,将搜索类或资源的任务委托给其父类加载器。
loadClass(String name)是ClassLoader的一个重要方法,加载名称为 name的类,返回的结果是 java.lang.Class类的实例。


类加载器是用来把类(class)装载进内存的。JVM 规范定义了两种类型的类加载器:启动类加载器(bootstrap)和用户自定义加载器(user-defined class loader)。 JVM在运行时会产生3个类加载器组成的初始化加载器层次结构 ,如下图所示:

这里写图片描述

引导类加载器Bootstap:用C++编写的,是JVM自带的类加载器,负责Java平台核心库,用来加载核心类库。该加载器无法直接获取
扩展类加载器Extension:负责jre/lib/ext目录下的jar包或 –D java.ext.dirs 指定目录下的jar包装入工作库
系统类加载器System:负责java –classpath 或 –D java.class.path所指的目录下的类与jar包装入工作 ,是最常用的加载器

举个例子:
为了方便直接看截图
这里写图片描述

可以看出
ClassLoader cl = ClassLoader.getSystemClassLoader();
cl = Class.forName("com.ClassLoader.Test").getClassLoader();

这两种调用方法结果是一样的,
第一次打印出sun.misc.Launcher$AppClassLoader系统类加载器
第二次打印出sun.misc.Launcher$ExtClassLoader扩展类加载器
第三次打印出null

但是加载Object类
第一次打印出null
第二次出现空指针异常,因为Object类属于虚拟机的内置类加载器(称为 “bootstrap class loader”)本身没有父类加载器,但是可以将它用作 ClassLoader 实例的父类加载器。


关于类加载器的一个主要方法:getResourceAsStream(String str):获取类路径下的指定文件的输入流

InputStream in = null;
in = this.getClass().getClassLoader().getResourceAsStream(“exer2\test.properties”);
System.out.println(in);


自定类加载器

第一步,自定义一个实体类Person.java,我们把它编译后的Person.class放在E盘根目录下。

package com.ClassLoader;

public class Person {

    private String name;

    public Person() {
    }

    public Person(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String toString() {
        return "I am a person, my name is " + name;
    }
}

第二步,自定义一个类加载器,另外注意一下 defineClass方法可以把二进制流字节组成的文件转换为一个java.lang.Class—-只要二进制字节流的内容符合Class文件规 范。我们自定义的MyClassLoader继承自java.lang.ClassLoader,就像上面说的,只实现findClass方法:

package com.ClassLoader;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class MyClassLoader extends ClassLoader {
    public MyClassLoader() {
    }

    protected Class findClass(String name) throws ClassNotFoundException {
        File file = new File("E:\\Person.class");
        FileInputStream fis = null;
        Class c = null;
        try {
            fis = new FileInputStream(file);
        } catch (FileNotFoundException e1) {
            e1.printStackTrace();
        }
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        int b = 0;
        try {
            while ((b = fis.read()) != -1) {
                baos.write(b);
            }
        } catch (IOException e1) {
            e1.printStackTrace();
        }
        try {
            byte[] bytes = baos.toByteArray();
            c = this.defineClass(name, bytes, 0, bytes.length);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return c;
    }

}

注意:1、如果不想打破双亲委派模型,那么只需要重写findClass方法即可.2、如果想打破双亲委派模型,那么就重写整个loadClass方法.当然,我们自定义的ClassLoader不想打破双亲委派模型,所以自定义的ClassLoader继承自java.lang.ClassLoader并且只重写findClass方法。

第三步,Class.forName有一个三个参数的重载方法,可以指定类加载器,平时我们使用的Class.forName(“XX.XX.XXX”)都是使用的系统类加载器Application ClassLoader。

public class TestMyClassLoader {
    public static void main(String[] args) throws Exception {
        MyClassLoader mcl = new MyClassLoader();
        Class c1 = mcl.findClass("com.ClassLoader.Person");
        Object obj = c1.newInstance();
        System.out.println(obj);
        System.out.println(obj.getClass().getClassLoader());
    }
}

结果
I am a person, my name is null
com.ClassLoader.MyClassLoader@3918d722

这里比较容易出问题的是第二行的打印出来的是”sun.misc.Launcher$AppClassLoader”。造成这个问题的关键在于MyEclipse是自动编译的,Person.java这个类在ctrl+S保存之后或者在Person.java文件不编辑若干秒后,MyEclipse会帮我们用户自动编译Person.java,并生成到CLASSPATH也就是bin目录下。在CLASSPATH下有Person.class,那么自然是由Application ClassLoader来加载这个.class文件了。解决这个问题有两个办法:

1.删除CLASSPATH下的Person.class,CLASSPATH下没有Person.class,Application ClassLoader就把这个.class文件交给下一级用户自定义ClassLoader去加载了。

2.TestMyClassLoader类的第5行这么写“MyClassLoader mcl = new MyClassLoader(ClassLoader.getSystemClassLoader().getParent());”, 即把自定义ClassLoader的父加载器设置为Extension ClassLoader,这样父加载器加载不到Person.class,就交由子加载器MyClassLoader来加载了。


.class()和getClass()的区别

.class方法和getClass()二者都可以获取一个唯一的java.lang.Class对象,但是区别在于:

1、.class用于类名,getClass()是一个final native的方法,因此用于类实例

2、.class在编译期间就确定了一个类的java.lang.Class对象,但是getClass()方法在运行期间确定一个类实例的java.lang.Class对象

Java 虚拟机是如何判定两个 Java 类是相同的?

Java 虚拟机不仅要看类的全名是否相同,还要看加载此类的类加载器是否一样。只有两者都相同的情况,才认为两个类是相同的。即便是同样的字节代码,被不同的类加载器加载之后所得到的类,也是不同的。比如一个 Java 类 com.example.Sample,编译之后生成了字节代码文件 Sample.class。两个不同的类加载器ClassLoaderA和 ClassLoaderB分别读取了这个 Sample.class文件,并定义出两个 java.lang.Class类的实例来表示这个类。这两个实例是不相同的。对于 Java 虚拟机来说,它们是不同的类。试图对这两个类的对象进行相互赋值,会抛出运行时异常 ClassCastException。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值