类加载器

类加载器:

  • 简介:

Java类加载器(Java Classloader)是Java运行时环境(Java Runtime Environment)的一部分,负责动态加载Java类到Java虚拟机的内存空间中。

类通常是按需加载,即第一次使用该类时才加载。由于有了类加载器,Java运行时系统不需要知道文件与文件系统。

JVM中有3个默认的类加载器:

  1. 引导(Bootstrap)类加载器。由原生代码(如C语言)编写,不继承自java.lang.ClassLoader。负责加载核心Java库,存储在<JAVA_HOME>/jre/lib目录中。
  2. 扩展(Extensions)类加载器。用来在<JAVA_HOME>/jre/lib/ext,或java.ext.dirs中指明的目录中加载 Java的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。该类由sun.misc.Launcher$ExtClassLoader实现。
  3. Apps类加载器(也称系统类加载器)。根据 Java应用程序的类路径(java.class.path或CLASSPATH环境变量)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。该类由sun.misc.Launcher$AppClassLoader实现。

这三个类装载器存在父子层级关系,即根装载器是ExtClassLoader的父装载器,ExtClassLoader是AppClassLoader的父装载器。默认情况下使用AppClassLoader装载应用程序的类。

public class Demo {
	public static void CSta(int[] numbers) {
	ClassLoader c  = Demo.class.getClassLoader();  //获取Test类的类加载器
        System.out.println(c); 
      	ClassLoader c1 = c.getParent();  //获取c这个类加载器的父类加载器
        System.out.println(c1);
      	ClassLoader c2 = c1.getParent();//获取c1这个类加载器的父类加载器
        System.out.println(c2);
	}

}

运行结果:

 

  • 类加载过程:

每个编写的”.java”拓展名类文件都存储着需要执行的程序逻辑,这些”.java”文件经过Java编译器编译成拓展名为”.class”的文件,”.class”文件中保存着Java代码经转换后的虚拟机指令,当需要使用某个类时,虚拟机将会加载它的”.class”文件,并创建对应的class对象,将class文件加载到虚拟机的内存,这个过程称为类加载,这里我们需要了解一下类加载的过程,如下:

 

  1. 加载:类加载过程的一个阶段:通过一个类的完全限定名查找此类字节码文件,并利用字节码文件创建一个Class对象

  2. 验证:目的在于确保Class文件的字节流中包含信息符合当前虚拟机要求,不会危害虚拟机自身安全。主要包括四种验证,文件格式验证,元数据验证,字节码验证,符号引用验证。

  3. 准备:为类变量(即static修饰的字段变量)分配内存并且设置该类变量的初始值即0(如static int i=5;这里只将i初始化为0,至于5的值将在初始化时赋值),这里不包含用final修饰的static,因为final在编译的时候就会分配了,注意这里不会为实例变量分配初始化,类变量会分配在方法区中,而实例变量是会随着对象一起分配到Java堆中。

  4. 解析:主要将常量池中的符号引用替换为直接引用的过程。符号引用就是一组符号来描述目标,可以是任何字面量,而直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。有类或接口的解析,字段解析,类方法解析,接口方法解析(这里涉及到字节码变量的引用,如需更详细了解,可参考《深入理解Java虚拟机》)。

  5. 初始化:类加载最后阶段,若该类具有超类,则对其进行初始化,执行静态初始化器和静态初始化成员变量(如前面只初始化了默认值的static变量将会在这个阶段赋值,成员变量也将被初始化)。

 

  • 全盘负责委托机制 :

Java装载类使用“全盘负责委托机制”。“全盘负责”是指当一个装载器装载一个类时,除非显式的使用另外一个装载器,否则该类所依赖及引用的类也由这个装载器载入;“委托机制”是指先委托父类装载器寻找目标类,只有在找不到的情况下才从自己的类路径中查找并装载目标类。如果一个人写了一个恶意的基础类(如java.lang.String)并加载到虚拟机中将会引起严重的后果,但有了全盘负责制,java.lang.String永远是由根装载器来装载,避免以上情况发生。除了JVM默认的三个装载器以外,第三方可以编写自己的类装载器,以实现一些特殊的需求。

为什么要使用这种双亲委托模式呢?

因为这样可以避免重复加载,当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次。

双亲委派模式工作原理

双亲委派模式要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器,请注意双亲委派模式中的父子关系并非通常所说的类继承关系,而是采用组合关系来复用父类加载器的相关代码,类加载器间的关系如下 :

双亲委派模式是在Java 1.2后引入的,其工作原理的是,如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式,即每个儿子都很懒,每次有活就丢给父亲去干,直到父亲说这件事我也干不了时,儿子自己想办法去完成。

  • 使用类加载器加载属性文件 :

属性文件:src目录下

username=张三
age=20
address=北京
        //普通IO
        FileReader fileReader = new FileReader("src\\database.properties");
                                    //当前目录是项目目录,写哪个(src/bin)加载哪个下面的
                                    //eclipse下,同"bin\\database.properties"(idea不同)
//        FileReader fileReader = new FileReader("bin\\database.properties");
        BufferedReader bufferedReader = new BufferedReader(fileReader);
        String s = bufferedReader.readLine();
        System.out.println(s);
        bufferedReader.close();

Properties加载属性文件 :

eclipse下:

    public static void main(String[] args) throws Exception{
        //加载属性文件
        InputStream resourceAsStream = Demo.class.getClassLoader().
                               getResourceAsStream("database.properties");//当前目录是bin
        Properties properties = new Properties();
        properties.load(new InputStreamReader(resourceAsStream,"utf-8"));
                                            //解决属性文件中中文配置的乱码问题
        Set<String> pronames = properties.stringPropertyNames();
        for (String string : pronames) {
            System.out.println(string);
        }
        properties.list(System.out);
    }

idea下:

    public static void main(String[] args) throws Exception{
        //加载属性文件
        InputStream resourceAsStream = Demo.class.getClassLoader().
                getResourceAsStream("database.properties");//当前目录是out/production/项目名
        Properties properties = new Properties();
        properties.load(new InputStreamReader(resourceAsStream,"utf-8"));
                                            //解决属性文件中中文配置的乱码问题
        Set<String> pronames = properties.stringPropertyNames();
        for (String string : pronames) {
            System.out.println(string);
        }
        properties.list(System.out);
    }

运行结果:

ResourceBundle加载属性文件 :

import java.io.UnsupportedEncodingException;
import java.util.ResourceBundle;
public class Demo2 {
	public static void main(String[] args) throws UnsupportedEncodingException {
		ResourceBundle resourceBundle = ResourceBundle.getBundle("users");
		String name = resourceBundle.getString("username");
		String age = resourceBundle.getString("age");
		String address = resourceBundle.getString("address");
                //解决中文乱码问题
		System.out.println(new String(name.getBytes("iso8859-1"),"utf-8"));
		System.out.println(age);
		System.out.println(new String(address.getBytes("iso8859-1"),"utf-8"));
	}
}

运行结果:

注意:属性文件默认编码为ISO-88590-1,如果修改为utf-8需要代码中处理乱码问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值