反射与JVM

                                            **反射与JVM**

      **类加载**

什么是类加载?
将.java编译过后的.class文件中,二进制代码加载到内存中取得这么一个过程,叫做类加载。

类的生命周期
在这里插入图片描述
加载:
查找并加载类的二进制数据
加载分为了以下三个步骤:

  1. 根据类的全限定名(包名+类名)来获取类的二进制字节流
  2. 将类中的所有静态数据的存储结构都转换到方法区中去,作为程序运行时所需要的数据机构
    注意:方法区也是堆,方法区中放置更多的是跟类相关的数据
    哪些数据跟类相关?
    类的代码、静态域(属性)、静态初始化块、静态方法、常量池(字符串常量池)
  3. 在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问

连接:
1 验证 作用:确保加载类的正确性
文件格式验证
元数据验证
字节码验证
符号引用验证
2 准备 为类的静态变量分配内存,并将其初始化为默认值
基本数据类型都是赋值为0,引用数据类型都是为null
3 解析 将类中的符号引用,全部替换为直接引用

初始化:
为类的静态变量赋初始值, clinit()方法,类的构造函数
以下情况,都会完成类的初始化:
1 产生类实例的时候
2 调用类的静态方法的时候
3 调用类的类变量的时候
4 反射,动态加载某一个类的时候
5 子类初始化,父类也会完成初始化
以下情况,不会执行类的初始化:
1 使用final修饰的常量调用
2 数组定义类的引用,也不会初始化所在类
3 通过子类去引用父类的静态变量,也不会使子类初始化

使用

卸载:
执行了System.exit()方法
程序运行结束
程序遇到异常或错误终止
由于操作系统错误而终止

类缓存:
使用标准javaSE版本的类加载器,加载的类一旦被加载完成,在一定时间范围内是有缓存效果的,但是也不是说永远都不销毁,当然什么时候销毁,由垃圾回收器说了算

类加载器:
在这里插入图片描述
不同的类加载器,加载相同的类的时候,产生的Class对象不一定一样

       **反射**
 Java程序中的对象在运行时会出现两种类型:编译时类型和运行时类型。例如:
Employee s = new Salary();
这行代码会生成一个引用变量s,该变量的编译时类型为Employee,运行时类型为Salary。
除此以外,还有在程序运行时接收到外部传入的一个对象,该对象的编译时类型为Object,但程序运行时又需要调用该对象运行时类型的属性或方法。
为了解决这些问题,程序需要在运行时发现对象和类的真实信息。在Java中,为了解决此问题,提供了两种方法:
第一种是假设在编译和运行时都完全知道类型的具体信息,在这种情况下,我们可以直接使用instanceof运算符进行判断,再利用强制类型转换将其转换成运行时类型的变量即可。
第二种是编译时根本无法预知该对象和类可能属于哪些类,程序只依靠运行时信息来发现该对象和类的真实信息,这就必须使用反射。
所谓反射,是指在Java中,可以在运行期载入、探知和使用编译期完全未知的类。换句话说,Java程序可以装载一个运行期才得到名称的类,获取其完整结构,并创建对象、或者对类的成员变量设定值、或者调用其方法。这种“看透”类的能力,被称为反省、内省或自审。
在Java API中的java.lang包和java.lang.reflect包中,提供了Class类、Field类、Method类、Constructor类、Array类等用于实现反射机制。
下面我们分别详细讲述Java中如何使用反射机制,获取类的信息,并动态创建对象、设定成员变量的值、调用方法以及动态创建和访问数组。
     
        使用反射查看类信息
 前面我们已经讲过,每个Java类被加载后,系统都会为该类生成一个对应的java.lang.Class对象。通过Class对象,我们就可以访问JVM中的这个类。在Java程序中获得Class对象的方式有三种:
第一种方式:在编译期不知道类名,但是在运行期可以获得该类名的时候,使用Class类的forName()静态方法可以获得Class对象。
第二种方式:如果在编译期知道类名的情况,可以调用该类的class属性来获得该类对象的Class对象。
第三种方式:如果一个类的实例对象已经得到,则调用该对象的getClass()方法返回该对象所属类对应的Class对象。getClass()方法是java.lang.Object类的方法之一,所以所有对象都可以调用该方法。
一旦获得了某个类对应的Class对象,程序就可以调用Class对象的方法来获取该对象和该类的真实信息。
在Class类中,通过getFields()、getMethods()、GetConstructors()方法,可以获得Class所含的类的public属性、public方法和public构造器,而通过getDeclaredFields()、getDeclaredMethods()、GetDeclaredConstructors()方法,可以获得Class所包含的类的所有属性、方法和构造器信息。这些方法分别返回Field、Method和Constructor类,可以分别用于描述类的属性、方法和构造器。Class类以及Field、Method和Constructor类的相关方法,请参阅JDK API中java.lang包以及java.lang.reflect中的类文档说明。

         使用反射生成并操纵对象
通过Class对象获取某个类的属性、方法和构造器后,程序就可以通过Constructor对象调用相应的构造器创建对象,通过Field对象访问对象并修改对象的属性,通过Method对象来执行对象相应的方法。此外,Class类还有一个newInstance()方法用于创建此Class对象所表示的类的一个新实例。
一)创建对象
通过反射创建对象的方式两种:
使用Class对象的newInstance()方法来创建该Class对象对应类的实例。这种方式要求Class对象的对应类有默认构造器,执行newInstance()方法时,实际上是调用默认的构造器来创建实例。用这种方式创建对象比较常见,后面我们要学习到很多JavaEE开源框架都是采用这种方式创建对象。
先使用Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建该Class对象对应类的实例。通过这种方式可以选择使用某个类的指定构造器来创建实例。
二)调用方法
当获得某个类对应的Class对象后,就可以通过该Class对象的getMethods()方法或getMethod()方法获取全部方法或者指定方法。getMethods()和getMethod()方法返回值是一个Method对象数组或者Method对象。每个Method对象对应一个方法,获得Method对象后,程序就可以通过该Method对象的invoke()方法来调用对应的方法。
三)访问属性值
通过Class对象的getFields()方法或者getField()方法可以获取一个类全部属性或指定属性。这两个方法的返回值是一个Field对象数组或Field对象。Field对象提供了如下两组方法来访问属性:
getXxx(Object o):获取o对象该Field的属性值。这里的Xxx对应八种基本数据类型,如果是引用类型,则取消get后面的Xxx。
setXxx(Object o, Xxx val):将o对象的该Field的值设置为val。这里的Xxx对应八种基本数据类型,如果是引用类型,则取消get后面的Xxx。
使用这两个方法可以随意访问指定对象的所有属性。
四)动态创建和访问数组
在《数组》一章中,我们学习了如何创建和使用数组。这里创建的数组,数组大小都是固定的。但是有时候,我们并不能确定数组的大小。比如,发工资程序中,开始时候员工有200人,但是公司发展过程中,肯定存在员工离职、或者员工人数增加的情况,所以员工的数目是不确定,会动态地改变。如果我们声明一个固定大小200的数组来存储员工,可能会存在浪费空间或者数组大小不够的情况。正确的做法是应该根据运行时员工的人数,确定数组的大小。利用反射机制,我们可以动态创建数组并访问数组元素。
java.lang.reflect包中的Array类提供了动态创建及访问数组元素的方法,这些方法都是静态方法。

           **垃圾回收**
在Java中,当对象被创建后,就会拥有一块内存。在程序运行时,JVM会陆陆续续创建很多对象。如果所有对象都永久占有内存,那么系统内存有可能很快被消耗光,最后引发内存空间不足的错误。因此,必须采用某种方法及时回收哪些无用对象的内存,以保证内存可以被重复利用。
在C/C++等其它编程语言中,内存是由程序员来负责释放的。例如,C++有一个delete关键字用于释放对象的内存。如果在C++中忘记删除一个对象,并且失去对该对象的任何引用,那么该对象所占用的内存将不会被释放。这就是内存泄露,它会对程序造成严重破坏,特别是对要使用大量内存或运行很长时间的程序。
在Java中,没有用于从内存中删除对象的关键字或运算符。Java语言在设计时,就考虑到要避免在其它语言中出现的内存泄露问题。JVM有一个称为垃圾回收器的低级线程,这个线程在后台不断地运行,自动寻找在Java程序中不再被使用的对象,并释放这些对象的内存。这种内存回收的过程被称为垃圾回收(Garbage Collection)。
自动垃圾回收的概念使程序员既激动又不安。垃圾回收令人激动,是因为程序员不再需要花时间来考虑如何修正内存泄露。垃圾回收令人不安,是因为程序员失去了在程序任何地方释放内存的控制权。在Java程序中,只有在垃圾回收器判断内存不再被使用时,内存才会被释放。
那么,垃圾回收器是如何知道什么时候要从内存中移除对象呢?
                
                垃圾回收的时机
在Java中,当程序中的一个对象不再可以获得时,就被标记为垃圾回收。不可获得并不意味着不再有任何引用引用该对象。我们可以认为垃圾回收器知道对一个对象的引用的确切数目,当引用计数为零时,就释放该对象。
但是,我们很容易想到另一种情况,就是有两个对象需要垃圾回收,但是每个对象都有一个对另一个对象的引用。如果使用引用计数,那么这两个对象永远不会被释放。
那么,如何使一个对象不可获得呢?我们需要确保仍然在Java应用程序范围内的引用不再引用将要被垃圾回收的对象。我们将这些引用赋值为null,或者将引用赋值为其它对象,或使引用脱离范围。

               对象的finalize()方法
有时候,Java对象在运行时,会占用一些资源。当垃圾回收器要回收无用的对象时,会自动调用该对象的finalize()方法,来完成一些释放对象所占用的资源等收尾工作。
所有Java对象的根类Object中,提供了proteced类型的finalize()方法。因此,任何Java类都可以重写finalize()方法,在重写的finalize()方法中进行释放对象所占用相关资源的操作。
然而,如果在程序终止之前,垃圾回收器始终没有执行垃圾回收操作,那么垃圾回收器也不会调用无用对象的finalize()方法。
程序即使显式地调用 System.gc()或Runtime.gc()方法,也不能保证垃圾回收操作一定执行。因此,也无法保证无用对象的finalize()方法一定被调用。也就是说,垃圾回收器是否会执行 finalize()方法、以及何时执行该方法,都是不确定的。
所以,程序不能完全依赖对象的finalize()方法来完成收尾工作,而应该在那些确信可以执行的方法中完成收尾工作。finalize()方法主要用来充当第二层安全保护网,当程序忘记显式执行收尾工作时,finalize()方法可以完成收尾工作。尽管finalize() 方法不一定会被执行,但是有可能会执行总比永远不会执行更安全。
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值