一、类加载子系统的作用
- 类加载器子系统负责从文件系统或者网络中加载class文件,class文价在文件开头有特定的文件标识。
- ClassLoader只负责class文件的加载,至于它是否可以运行,则又Execution Engine决定。
- 加载的类信息存放在一块称为方法区的内存空间。除了类的信息外,方法区中还会存放运行时常量池信息,可能还包含字符串常量和数字常量(这部分常量信息是Class文件中常量池部分的内存映射)
举例来说:
二、类的加载过程
step1:加载
- 通过一个类的全限定名获取定义此类的二进制字节流
- 将这个字节流所代表的的静态存储结构转化为方法区的运行时数据结构
- 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
step2:链接(验证、准备、解析)
step3:初始化
类构造器方法<cinit>():
- 初始化阶段就是执行类构造器方法<cinit>()的过程
- 这个方法不需要定义,是javac编译器自动收集类中所有类变量的赋值动作和静态代码块中的语句合并而来。如果没有静态变量的赋值以及没有静态代码块,就不会有<cinit>()方法
- 构造器方法中指令按语句在源文件中出现的顺序执行。
- <cinit>()不同于类的构造器
- 若该类具有父类,JVM会保证子类的<clinit>()执行前,父类的<clinit>()已经执行完毕。
- 虚拟机必须保证一个类的<cinit>()方法在多线程下被同步加锁。
可以看一个例子:
public class ClassInitTest {
private static int num = 1;
static {
num = 2;
}
public static void main(String[] args) {
System.out.println(ClassInitTest.num);
}
}
这是在IDEA安装了jclasslib Bytecode viewer插件之后的文件字节码显示,我们对cinit没有做任何的定义,但是会发现在cinit方法中,先将num赋值为1,又赋值为2,。
再看一种比较特殊的情况。
public class ClassInitTest {
private static int num = 1;
static {
num = 2;
number = 20;
}
private static int number = 10;
public static void main(String[] args) {
System.out.println(ClassInitTest.num+ClassInitTest.number);
}
}
即使number的赋值语句在初始化语句后面,也是不报错的,这是因为在加载过程的step2链接的准备阶段,以及赋予number默认初始值0了,在初始化阶段会按照代码顺序重新覆盖,也就是number的值变化历程为0--->20--->10,最终控制台显示结果为12。但是如果在static代码块内试图打印number就会报错——“非法的前项引用”,就是说,因为只是分配了内存空间,没有声明变量,不符合JVM规范,所以可以赋值,但不能调用。
类构造器init(): 除了匿名内部类,任何一个类声明以后,内部至少存在一个类的构造器
类加载器的分类
JVM支持两种类型的类加载器,分别为引导类加载器(Bootstrap Class Loader)和自定义加载器。
从概念上来讲,自定义类加载器一般指的是程序中由程序开发人员自定义的一类类加载器,但是Java虚拟机规范却没有这么定义,而是将所有派生于抽象类ClassLoader的类加载器都划分为自定义类加载器。
public class ClassLoaderTest {
public static void main(String[] args) {
//获取系统类加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader); //sun.misc.Launcher$AppClassLoader@18b4aac2
//获取系统类加载器的上层加载器——扩展类加载器
ClassLoader extensionClassLoader = systemClassLoader.getParent();
System.out.println(extensionClassLoader); //sun.misc.Launcher$ExtClassLoader@330bedb4
//试图获取扩展类加载器的上层——引导类加载器,发现获取不到
ClassLoader bootstrapClassLoader = extensionClassLoader.getParent();
System.out.println(bootstrapClassLoader); //null
//用户自定义类是默认使用系统类加载器进行加载的
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
System.out.println(classLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2 同systemClassLoader
//String的类加载器——引导类加载器
ClassLoader classLoader1 = String.class.getClassLoader();
System.out.println(classLoader1);//null 获取不到,说明是引导类加载器
}
}
启动类加载器(引导类加载器,Bootstrap ClassLoader):
- 使用C/C++语言实现的,嵌套在JVM内部
- 用来加载Java的核心类库(JAVA_HOME/jre/lib/rt.jar、resource.jar或者sun.boot.class.path路径下的内容),用于提供JVM自身需要的类。
- 因为是C/C++语言实现的,所以并不继承自java.lang.ClassLoader,没有父加载器
- 加载扩展类和应用程序类加载器,并指定为他们的父类加载器出于安全考虑,Bootstrap ClassLoader只加载包名为java、javax、sun等开头的类。
扩展类加载器(Extension ClassLoader):
- Java 语言编写,由sun.misc.Launcher$ExtClassLoader实现
- 派生于ClassLoader类
- 父类加载器为BootstrapClassloader
- 从java.ext.dirs系统属性指定的目录中,或者从JDK的安装目录JAVA_HOME/jre/lib/ext子目录下加载类库。如果用户创建的JAR放在此目录下,也会自动由扩展类加载器加载
应用程序类加载器(系统类加载器,AppClassLoader):
- Java 语言编写,由sun.misc.Launcher$AppClassLoader实现
- 派生于ClassLoader类
- 父类加载器为ExtensionClassloader
- 它负责加载环境变量classpath或者系统属性,java.class.path指定路径下的类库
- 该类加载器是程序中默认的类加载器,一般来说,java应用类都是由他来加载完成的
- 通过ClassLoader.getSystemClassLoader()可以获取到该类加载器
用户自定义类加载器:
在日常Java应用程序开发中,类的加载几乎是由上述3种类加载器相互配合执行的,必要时可以自定义类加载器来定制类的加载方式。
为什么要自定义类加载器呢?
- 隔离加载类
- 修改类加载方式
- 扩展加载源
- 防止源码泄露
关于ClassLoader
ClassLoader类是一个抽象类,其后所有的类加载器都继承自ClassLoader(不包括启动类加载器)
sun.misc.Launcher他相当于一个Java虚拟机的入口应用
ClassLoader的一些方法概述:
获取ClassLoader的途径