JVM的类加载学习
1.类的加载,即JVM把类的二进制数据加载到内存的时候,放在程序运行时的方法区内
二进制数据应该是包含了类中的所有数据内容,如方法\变量
同时JVM会自动在堆区创建类的唯一对象(java.lang.Class),这个对象封装了类在方法区内的数据结构
2.类的加载包含了三个过程:
装载,即把类的.class文件这些二进制数据加载到内存
然后进行连接,这个连接就是把已经加载到内存的这些类的二进制数据整合到虚拟机运行环境中
最后是初始化,也就是对类的静态变量进行初始化,这个初始化,是按顺序对静态变量即静态块进行初始化
3.类连接过程也包含三个过程:
连接(验证,准备,解析):
验证:即存在非javac编译的类文件,需要进行验证,是否满足JVM的字节码要求
准备:即对类静态变量分配内存,并赋默认值
解析:即把符号引用转换为直接引用
符号引用转换为直接引用
其实就是把java语言的引用转换为JVM能够识别的地址指针,即实际指向某块内存区的地址
public class InitTest {
//private static InitTest init = new InitTest();
private static int dog;
private static int cat = 3;
private static InitTest init = new InitTest();
public InitTest() {
dog++;
cat++;
}
public static void main(String arg[]) {
System.out.println("dog: " + dog);
System.out.println("cat: " + cat);
}
}
上面例子init对象所放置的位置将对dog和cat对象的值产生不同影响
4.对类文件.class 的加载有4种情况:
本地加载
通过网络加载(URLClassLoader)
通过jar、zip包来加载
从数据库加载
JVM动态加载,从.java源文件开始
5.类加载器即把类加载到内存的类
类加载器有两种:
5.1JVM本身自带的:
根类加载器(c++编写,无法访问) native代码,加载java核心库,不继承java.lang.ClassLoader
拓展类加载器,它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。
该类加载器在此目录里面查找并加载 Java 类
系统类加载器(应用加载器),他根据 Java 应用的类路径(CLASSPATH)来加载 Java 类
5.2用户自定义的类加载器
java.lang.ClassLoader的子类
CLassLoader也即作为反射的工具
JVM规范是允许加载器预先对类进行加载,在预料到某个类要被使用的时候
类只会被加载一次
5.3类加载步骤:
如果类没有加载就进行加载连接的操作
如果类有父类,且父类没有初始化则先初始化
如果类有初始化语句块就按顺序执行初始化语句块
6.加载器加载类时不会造成类的初始化
只有当类在首次主动使用的时候才会被初始化
类的使用包括主动使用和被动使用
主动使用有6种情况:
初始化类的子类
访问或对类的静态变量赋值
调用类的静态方法
实例化类的对象
Java虚拟机启动时被标明为启动类的类
使用反射,如Class.forName
除了以上的情况,其他均为被动使用
类被初始化的时机为 类被主动使用时进行的
编译型的变量不会导致类被初始化
public class InitTest {
public static void main(String arg[]) {
System.out.println(" test: " + test2.mouse);
}
}
class test {
public static int mouse = 1;
static {
System.out.println(" test init");
}
}
class test2 extends test {
public static int aa = 1;
static {
System.out.println(" test2 init");
}
}
上面例子只初始化了test类而没有初始化test2子类
7.
初始化一个类,同时要求其父类已经被初始化,但
初始化一个类的时候,不会初始化一个类的接口
初始化一个接口的时候,也不会初始化他的父接口
接口的初始化只会在他的静态变量被首次访问的时候
当访问的静态变量或静态方法确实在当前类或接口中定义时
才会被认为是首次使用,进行该类或接口的初始化
调用ClassLoader的loadClass加载类,不是对类的首次使用
8.
类加载过程使用了父亲委托机制(parent delegation)
当我们加载一个类时,会先调用父加载器进行加载,父加载器加载不了才会用当前加载器进行加载
这个机制主要是为了安全,通过父加载器的调用防止恶意代码的加载
根类加载器没有父加载器,其余类加载器有且仅有一个父加载器
父子加载器并非是继承关系,子加载器不一定继承父加载器
父子加载器间是包装的关系
定义一个类加载器时,如果没有指定父加载器,则系统类加载器会自动成为父加载器
如果一个类加载器能够加载一个类,则为定义加载器
定义加载器及其所有子加载器都称为初始化加载器
每个类加载器有自己的命名空间:类加载器及其父加载器所加载的类组成
运行时包:由同一个类加载器加载的属于相同包的类组成
决定两个类是否属于相同运行时包:一个看定义类加载器是否相同,另一个是包名是否相同
位于不同运行时包的类之间是无法直接进行调用的,即由不同类加载器加载的类间的地址空间区域不同,无法直接访问
需要通过反射机制进行访问
运行时包即在内存中管理类的包空间,而文件目录的包为管理文件目录的包
9.
自定义类加载器
定义自己的类加载器需要extends java.lang.ClassLoader 成为其子类
然后override 其findClass方法即可
10.
JVM自带的类加载器所加载的类始终不会被卸载
而用户自定义的类加载器所加载的类可以被卸载