当程序主动使用某个类时,如果该类还未被加载到内存中,则JVM会通过加载、连接、初始化3个步骤来对该类进行初始化。如果没有意外,JVM将会连续完成3个步骤,所以有时也把这个3个步骤统称为类加载或类初始化。
类加载子系统负责从文件系统或者网络中加载Class文件,class文件在文件开头有特定的文件标识;
ClassLoader只负责class文件的加载,至于它是否可以运行,则由Execution Engine(执行引擎)决定(我只负责把你带过来,具体你执行的时候会不会出现空指针异常之类的我管不了,那就是你的事情了)
类加载器在JVM中所处的层次结构
类加载器
作用:加载Class文件
类是抽象的,是一个模板,而对象是具体的。
类的引用是放在栈里面的,实例对象是在堆里面的,Class类信息是在方法区里面的。
图 类在经过Class Loader之后的变化
加载
加载指的是将类的class文件读入到内存,并为之创建一个java.lang.Class对象,也就是说,当程序中使用任何类时,系统都会为之建立一个java.lang.Class对象。
类的加载由类加载器完成,类加载器通常由JVM提供,这些类加载器也是前面所有程序运行的基础,JVM提供的这些类加载器通常被称为系统类加载器。
加载器类别
层次的机制,类加载器的体系并不是“继承”体系,而是委派体系,下面会具体阐述双亲委派机制。
除了启动类加载器Bootstrap ClassLoader,其他的类加载器都是ClassLoader的子类。
类加载器负责加载所有的类,其为所有被载入内存中的类生成一个java.lang.Class实例对象。一旦一个类被加载如JVM中,同一个类就不会被再次载入了。正如一个对象有一个唯一的标识一样,一个载入JVM的类也有一个唯一的标识
1.启动类(根)加载器
也可以称为虚拟机自带的加载器,位于 jre/lib/rt.jar。
嵌套在JVM内部,它用来加载Java的核心库
2.扩展类加载器
该加载器器是用JAVA编写,且它的父类加载器是Bootstrap,位于 jre/lib/ext
3.应用程序加载器
java.lang”包下的ClassLoade
ClassLoader
是java.lang包下的一个抽象类
顾名思义,它是用来加载 Class 的。它负责将 Class 的字节码形式转换成内存形式的 Class 对象。字节码可以来自于磁盘文件
*.class,也可以是 jar 包里的 *.class,也可以来自远程服务器提供的字节流,字节码的本质就是一个字节数组 []byte,它有特定的复杂的内部格式
classLoader是一个类加载器 具象化就是一个classLoader对象
java中的”所有类"都是通过类加载器加载出来的的,但并不要求,是同一个类加载器 但是数组类是一个例外
获取类加载器
public ClassLoader getClassLoader 获取当前类的加载器
public final ClassLoader getParent() 获取当前类的父类加载器
public class Car {
public String name = "宝马";
public static void main(String[] args) {
//实例化对象
Car car1 = new Car();
Car car2 = new Car();
Car car3 = new Car();
//三个对象是不同的
System.out.println(car1.hashCode());
System.out.println(car2.hashCode());
System.out.println(car3.hashCode());
//获取模板的class
//三个对象反射的class是一个
//获取当前类的类加载器
//其实本质都是要getClassLoader只是因为在这之前要先获取class字节码,由于获取class字节码的不同所以会不同
//第一种通过
System.out.println(Car.class.getClassLoader());
//第二种
Class<? extends Car> Class = car1.getClass();
ClassLoader classLoader = Class.getClassLoader();
System.out.println(classLoader);//AppClassLoader 应用程序加载器
System.out.println(classLoader.getParent());//ExtClassLoader 扩展类加载器
//原因是java调用不到这里不是说ExtClassLoader没有父加载器,
// 而是因为Bootstrap ClassLoader使用C++写的。
System.out.println(classLoader.getParent().getParent()); //null
}
}
类加载器的机制
加载机制很多,主要是双亲委派机制
双亲委派机制
Java虚拟机对class文件采用的是按需加载的方式,也就是说当需要使用该类时才会将它的class文件加载到内存生成class对象。
加载某个类的class文件时,Java虚拟机采用的是双亲委派机制,即把请求交由父类处理,它是一种任务委派模式。当某个类加载器需要加载某个.class文件时,它首先把这个任务委托给他的上级类加载器(向上查找的操作),递归这个操作,如果上级的类加载器没有加载,自己才会去加载这个类。
主要的两个关键词是加载和查找的过程 。查找是从下往上查找,加载是从上往下加载,父类加载到子类就不用加载。
作用:
1.可以确保安全的机制,防止核心API被随意篡改
2.避免类的重复加载
工作原理
每个 ClassLoader 都很懒,尽量把工作交给父亲做,父亲干不了了自己才会干。每个 ClassLoader 对象内部都会有一个 parent 属性指向它的父加载器。
APP–>EXC—>B0OT(最终执行)
如果没有再继续往下查找B0OT-> EXC-> APP
1.类加载器收到类加载的请求
2.将这个请求向上委托给父类加载器去完成,一 直向上委托,直到启动类(根)加载器(Boot)
3.启动加载器检查是否能够加载当前这个类,能加载就结束, 使用当前的加载器,否则, 抛出异常,通知子加载器进行加载。
详解:
如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成加载任务,子加载器才会尝试自己去加载,这就是双亲委派机制.父类加载器一层一层往下分配任务,如果子类加载器能加载,则加载此类,如果将加载任务分配至系统类加载器也无法加载此类,则抛出异常(Class Not Found 就是所有的类加载器都找不到产生的原因。)
代码解释双亲委派机制
/**
解析一下这个错误
* 错误: 在类 java.lang.String 中找不到 main 方法, 请将 main 方法定义为:
* 明明我写了main方法 却还是出现了这个原因,说明肯定执行的不是我写的逻辑
* public static void main(String[] args)
*
* 自定义了一个包 java.long 然后写了一个类String
* 然后写了toString 的方法
* 但是出现上面的错误就是因为一个原因叫做双亲委派机制
*
* java应用程序将我当前写的代码javac编译成class文件
* 然后交给类加载器区去加载,刚开始交给的是应用程序加载器
* 然后查找扩展类加载器,然后再去根加载器里面去寻找
* 层层往上的过程,最后在根加载器(也即是 rt.jar 里面找到了java的String 类,所以就去执行自带的)
* 而如果是自己new 一个类的话,层层往上找是找不到的,那么就在层层往下找
* 最后找打了应用程序加载器就加载就可以了
*
*/
package classloder.java.lang;
public class String {
public String toString() {
return "hello";
}
public static void main(String[] args) {
String s = new String();
System.out.println(s.getClass().getClassLoader());
s.toString();
}
}
为什么需要双亲委派机制
上面代码的错误就是自己写了一个String类 出现了问题。如果不以这种机制区去加载,不加载官方的String 而是加载自己的就不能保证安全。
优点
保证安全,保证java程序的稳定运行
缺陷
无法满足灵活的类加载机制
解决方案 :自己重写loadClass破坏双亲委派模型
实例:SPI机制
原理大概就是如下的代码,暂时深入理解不了了,大概就先到这吧。
其他
遵循双亲委派寄机制,重写findclass方法
如果不要遵循就重写ClassLoder方法
类的反射机制
在程序运行时动态地获取信息以及动态调用对象方法的功能称为反射
- 对于任意一个类,都能够获取这个类的所有属性和方法
- 对于任意一个对象,都能调用这个对象的任意一个属性和方法
第一步:获取反射中的Class对象(三种)
在反射中,要获取一个类或调用一个类的方法,我们首先需要获取到该类的 Class 对象。
什么是class类对象
java文件的三个阶段
1、Source源代码阶段
2、Class类对象阶段,这个阶段会生成一个类型.class的字节码文件,会把所有运行类的成员变量放到Filed[]数组中,类的构造方法放到Constructor[]数组中,类的所有成员方法放到Method[]数组中
3、Runtime代码运行阶段
获取字节码class对象的三种方法
1、文件写好了,未运行时,使用Class.forName(“导包全类名”)。将字节码文件加载进内存,返回class对象(使用场景:多用于配置文件,将类名定义在配置文件中,读取文件,加载类)
2、类名.class:通过类名的属性class获取,(使用场景:多用于参数的传递)
3、对象已经创建,使用对象名.getClass()获取,getClass方法在Object类中定义着。(使用场景:多用于对象的获取字节码的方式)
public class Car {
public String name = "宝马";
public static void main(String[] args) throws ClassNotFoundException {
//获取class字节码对象的三种方式
//1. 利用类的反射特性,通过类的名称获取一个类。
System.out.println(Class.forName("classloder.Car")); //由于我的类所在的包是classloder所以这样写了
//2类名.class
System.out.println(Car.class);
//3.实例化对象
Car car1 = new Car();
System.out.println(car1.getClass());
}
}
同一个字节码文件(*.class)在一次程序运行中,只会被加载一次,不论通过哪种方式获取的class对象,都是同一个,地址值都一样。
这里解释一个Class.forName(className) 这个类加载器,是采用默认按照当前代码所在类的加载器进行加载的获取类对象。
public class GetClassDemo {
public static void main(String[] args) throws ClassNotFoundException {
//这是类名称
String className = "com.qiruxie.webapps.dictionary.TranslateServlet";
//利用反射的特性 通过类名称得到一个列
//通过类的名称得到一个类
//第一种
Class<?> clazz = Class.forName(className);
//forName的加载方式是默认按照当前代码所在类的加载器进行加载的
//第一种的效果和下面的一样
ClassLoader classLoader = GetClassDemo.class.getClassLoader();
Class<?> clazz2 = classLoader.loadClass(className);
//第二种相对第一种会灵活的 用其他的类加载器进行加载获取类对象。
/* classLoader是一个类加载器 具象化就是一个classLoader对象
java中的”所有类"都是通过类加载器加载出来的的,但并不要求,是同一个类加载器
但是数组类是一个例外*/
ClassLoader classLoader ="";
Class<?> clazz3 = classLoader.loadClass(className);
}
第二步:通过反射创建类对象
通过反射创建类对象主要有两种方式:通过 Class 对象的 newInstance() 方法、通过 Constructor 对象的 newInstance() 方法。
Class 对象的 newInstance() 方法
//通过反射类实例化对象
Class<Car> carClass = Car.class;
Car car = carClass.newInstance();
反射的应用
Java中的对象有两种类型:编译时类型和运行时类型。编译时类型指在声明对象时所采用的类型,运行时类型指为对象赋值时所采用的类型。
在如下代码中,person对象的编译时类型为Person,运行时类型为Student,因此无法在编译时获取在Student类中定义的方法
Person person = new Student();
因此,程序在编译期间无法预知该对象和类的真实信息,只能通过运行时信息来发现该对象和类的真实信息,而其真实信息(对象的属性和方法)通常通过反射机制来获取,这便是Java语言中反射机制的核心功能。
延迟加载
JVM 运行并不是一次性加载所需要的全部类的,它是按需加载,也就是延迟加载。程序在运行的过程中会逐渐遇到很多不认识的新类,这时候就会调用 ClassLoader 来加载这些类。加载完成后就会将 Class 对象存在 ClassLoader 里面,下次就不需要重新加载了。
比如你在调用某个类的静态方法时,首先这个类肯定是需要被加载的,但是并不会触及这个类的实例字段,那么实例字段的类别 Class 就可以暂时不必去加载,但是它可能会加载静态字段相关的类别,因为静态方法会访问静态字段。而实例字段的类别需要等到你实例化对象的时候才可能会加载。
理解还很基础,后续补充~