文章目录
一、类的加载过程
当程序主动使用某个类时,如果该类还未被加载到内存中,系统会通过加载、链接、初始化三个步骤来对该类进行初始化,这三个步骤统称为类加载。
加载:load
就是指将类型的class字节码数据读入内存
链接:link
链接分为三步
① 验证
校验被加载的class文件的合法性,并且不会危害虚拟机的自身安全(文件格式验证,语义分析等)
② 准备
为类变量分配内存(在方法区中)并设置初始值(0,null,false),为静态常量赋初始值(常量池中)
③ 解析
把字节码中的符号引用替换为对应的直接地址引用
初始化:initialize
为类的静态变量赋予正确的初始值,JVM负责对类进行初始化,主要对类变量进行初始化。
类初始化执行的是<clinit>()
,该方法会执行
①类变量的显式赋值代码
②静态代码块中的代码
二、哪些操作会导致类的初始化?
- loaderClass, forName等
- java.exe 命令运行的类
- 使用 new 创建类的实例
- 访问类的静态变量,调用类的静态方法
- 初始话其子类
- 使用反射创建类
三、类加载器
Java类如何加载到内存中?类的加载通过类加载器完成。JVM启动会先创建类加载器ClassLoader对象用来加载Java类。
类加载器分类
-
引导类加载器(Bootstrap ClassLoader)
又称为根类加载器
它负责加载 jre/lib/rt.jar 核心库
它本身不是Java代码实现的(HotSpot VM 中C++实现的),也不是ClassLoader的子类,获取它的对象时往往返回null -
扩展类加载器(Extension ClassLoader)
它负责加载 jre/lib/ext 扩展库
它是 ClassLoader 的子类
Java 代码编写 -
应用程序类加载器(Application ClassLoader)
也称为系统类加载器 System Class Loader
它负责加载项目的 classpath 路径下的类
它是ClassLoader的子类
Java 代码编写 -
自定义类加载器
当你的程序需要加载“特定”目录下的类,可以自定义类加载器,必须继承自java.lang.ClassLoader类
当你的程序的字节码文件需要加密时,那么往往会提供一个自定义类加载器对其进行解码
tomcat服务器中会应用到
小知识
ClassLoader.getSystemClassLoader方法无论何时均会返回ApplicationClassLoader,其只加载classpath下的class文件。在javaSE环境下,一般javaSE项目的classpath为bin/目录,因此只要编译后的class文件在classpath下就可以。此时ApplicationClassLoader就可以加载动态生成的类。
但在javaEE环境下,我们的项目里的类是通过WebAppClassLoader类来加载的,此时我们获取了ApplicationClassLoader,因此自然找不到class文件。
Java系统类加载器的双亲委托模式
在JVM虚拟机中,如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成。每个类加载器都是如此,只有当父加载器在自己的搜索范围内找不到指定的类时,子加载器才会尝试自己去加载,自己也加载不了那么会抛异常(即ClassNotFoundException)。
对于每个类加载器,只有父类(依次递归)找不到时,才自己加载 。这就是双亲委派模式
① 这样可以避免重复加载
前面已经说明,当一个类要加载时先看父级类加载器是否加载,如果已经加载过,则无需再重复加载。
② 考虑到安全因素
假设我们使用一个第三方Jar包,该Jar包中自定义了一个java.lang.String类,它的功能和系统String类的功能相同,但是加入了恶意代码。那么,JVM会加载这个自定义的String类,从而在我们所有用到String类的地方都会执行该恶意代码。如果有双亲委派模型,自定义的String类是不会被加载的,因为最顶层的类加载器会首先加载系统的java.lang.String类,而不会加载自定义的String类,防止了恶意代码的注入。
获取类加载器的方式
//获取当前类的加载器
ClassLoader classLoader = Demo.class.getClassLoader();
//获取当前线程上下文类加载器
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
//获取系统类加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
四、java.lang.Class类
万物皆对象,每个类编译后生成的字节码文件,即class文件,在类加载后JVM会为每个class文件创建一个对象,也就是Class类的一个对象。这个Class对象封装了类在方法区内的数据信息
Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。
获取Class对象的四种方式
-
类型名.class
要求编译期间已知类型,所有的Java类型都可通过此方式获取Class对象 -
对象.getClass()
获取对象的运行时类型 -
Class.forName(类型全名称)
可以获取编译期间未知的类型 -
ClassLoader的类加载器对象.loadClass(类型全名称)
可以用系统类加载对象或自定义加载器对象加载指定路径下的类型
五、反射
可以获取:包、修饰符、类型名、父类(包括泛型父类)、父接口(包括泛型父接口)、成员(属性、构造器、方法)、注解