java类加载器

来源:互联网 发布:页面加载执行js方法 编辑:程序博客网 时间:2024/06/05 13:44

什么是类加载器?

先了解类的加载过程:

这里写图片描述

类加载器(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。

原创粉丝点击