反射

类加载机制

类加载过程

类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载、验证、准备、解析、初始化、使用和卸载七个阶段

image-20210802105758169

加载就是将类的 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 是不会浪费时间进行垃圾回收

posted @ 2021-09-13 20:24  追こするれい的人  阅读(20)  评论(0编辑  收藏  举报