反射
反射
本章目标
- 掌握类的加载、连接和初始化过程
- 掌握类加载机制
- 掌握反射机制
- 重点掌握反射的使用
- 掌握 GC 垃圾回收机制
类加载机制
类加载过程
类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载、验证、准备、解析、初始化、使用和卸载七个阶段
加载就是将类的 class 文件读入内存后,然后创建一个 Class 对象,这个过程是由类加载器完成,类加载器分为三类
- 启动类加载器:该加载器负责加载存放在 JDK\jre\lib 类库,启动类加载器是无法被 Java 程序直接引用
- 扩展类加载器:该加载器负责加载 JDK\jre\lib\ext 目录中,或者由 java.ext.dirs系统变量指定的路径中的所有类库(如javax.*开头的类),开发者可以直接使用扩展类加载器
- 应用类加载器:该类加载器负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器(如果应用程序中没有自定义类加载器,一般情况下这个就是程序中默认的类加载器)
初始化
JVM 负责对类进行初始化,也就是对类静态属性进行初始化。在 Java 类中,对静态属性指定初始值的方式有两种:
- 声明静态属性时指定初始值
- 使用静态初始化块为静态属性指定初始值
类加载过程中主要是将 Class 文件(二进制字节流)加载到虚拟机内存中,真正执行字节码的操作(用户代码)是在类加载完成后才开始
反射机制
什么是反射
Java 反射机制的核心是在程序运行时动态加载类并获取类的详细信息,从而操作类或对象的属性和方法(本质是 JVM 得到 Class 对象之后,再通过 Class 对象进行反编译,从而获取对象的各种信息)
- Java 属于先编译再运行的语言,对象类型在编译期确定
- 当程序在运行时可能需要动态加载某些类,通过反射可以在运行时动态地创建对象并调用其属性,不需要提前在编译期确定对象类型
反射的原理
-
传统静态加载类
Person person = new Person();
编译时已确定对象类型,程序运行时通过类加载器加载 Person 类,并创建该类的 Class 对象
-
反射动态加载类
Class object = Class.forName( "Person" );
编译时未确定对象类型,程序运行时通过类加载器无法加载 Person 类,只有到执行以上代码时,类加载器才知道对象类型,这时才开始加载 Person 类,创建该类的 Class 对象,并返回给 object 变量
Class 对象
每一个类通过类加载器完成加载后都将创建唯一的 Class 对象,用于保存对应类的元信息(属性、方法以及构造器等)
注意:Class 对象属于 Class 类型,是类加载器创建的特殊对象,不是类的实例化对象
Class object = Class.forName( "Person" );
Person person = (Person)object.newInstance();
反射通过获得的 Class 对象就可以得到类的元信息,调用 newInstance( ) 方法就是通过类的元信息来动态创建 Person 对象实例
三种方式获取 Class 对象
-
通过类对象实例的 getClass() 方法
Student student = new Student(); Class object = student.getClass();
-
通过任何数据类型(包括基本数据类型)的静态 class 属性
Class object = Student.class;
-
通过 Class 类的静态方法:forName(String className)
Class object = Class.forName("Student");
第一种没必要使用反射,第二种需要导入类包依赖太强,第三种简单灵活(反射推荐使用)
二种方式创建类对象实例
-
使用 Class 对象的 newInstance() 方法
Class object = Class.forName("Student"); Student student = (Student)object.newInstance();
-
使用 Class 对象获取 Constructor 对象,再调用 newInstance() 方法
Class object = Class.forName("Student"); Constructor constructor = object.getConstructor(Student.class); Student student = (Student)constructor.newInstance("张三", 27);
第一种简单但只能调用无参构造器,第二种复杂但可以调用有参构造器
获取 Field 对象和 Method 对象
-
使用 Class 对象获取 Field 对象
Class object = Class.forName("Student"); Field fields[] = object.getDeclearedFields();
-
使用 Class 对象 Method 对象
Class object = Class.forName("Student"); Method methods[] = object.getDeclearedMethods();
Field 对象数组保存类的所有属性,Method 对象数组保存类的所有方法
反射的优缺点
优点
- 反射能够很方便的创建灵活的代码,这些代码可以在运行时动态加载
- 反射能够降低类与类之间的耦合度,常用于框架设计
缺点
- 反射会消耗一定系统资源,运行效率低
- 反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题
反射的用途
- 反编译
- 访问对象
- IDE 智能提示
- 开发通用框架(主要用途)
- 加载数据库驱动
Java 程序员一般很少使用反射机制,除非为了获得某种灵活性
GC 垃圾回收机制
为什么需要垃圾回收
如果不进行垃圾回收,内存迟早都会被耗尽,因为我们在不断的分配内存空间而不进行回收。除非内存无限大,我们可以任性的分配而不回收,但是事实并非如此。所以,垃圾回收是必须的。
什么是 GC 垃圾回收机制
GC 是 Java 虚拟机 ( JVM ) 垃圾回收器提供的一种用于在系统空闲时间,不定时自动回收无任何引用的对象占据的内存空间的一种机制。
GC 由 JVM 负责,程序无法控制
GC 垃圾回收机制原理
垃圾回收(Garbage Collection)机制的原理就是利用一些算法进行内存的管理,从而有效的防止内存泄漏、有效的利用空闲空间(内存空间)。
垃圾回收机制所涉及的算法
- 计数法(Reference Counting Collector)
- Tracing 算法
- 标记-清除算法
- 标记-整理算法
- Copying 算法(Compacting Collector)
- Generation 算法(Generational Collector)
垃圾回收方法
gc() 方法
System.gc( );
Runtime.getRuntime( ).gc( );
- 以上两种方式都是主动调用 gc() 方法,两者效果上是等价的
- Java 的 GC 是由 JVM 自行调动的,在需要的时候才执行,上面的指令只是催促 JVM 尽快 GC 一次,是否执行 GC 最终还是由 JVM 决定
finalize() 方法
protected void finalize() throws Throwable { }
- finalize( ) 方法是 Object 类中提供的一个方法,在 GC 准备释放对象所占用的内存空间之前,它将首先调用 finalize( )方法
- 在 Java 中,由于 GC 的自动回收机制,因而并不能保证 finalize 方法会被及时地执行(垃圾对象的回收时机具有不确定性)
- finalize() 方法中一般用于释放非 Java 资源(如打开的文件资源、数据库连接等),或是调用非 Java 方法(native 方法)时分配的内存(比如 C 语言的 malloc() 系列函数)
- finalize() 方法的调用时机具有不确定性,从一个对象变得不可到达开始,到 finalize() 方法被执行,所花费的时间这段时间是任意长的
面试题:final、finally、finalize 三者的区别
注意:gc() 方法和 finalize() 方法都不可靠,只要 JVM 还没有快到耗尽内存的地步,GC 是不会浪费时间进行垃圾回收