Java类加载与垃圾回收

类加载

当我们运行java.exe命令执行某个Java程序时,由于Java程序本身以.class字节码的形式存在,它不是一个可执行文件,所以需要JVM将类文件加载到内存中。

类的加载由类加载器完成。JVM本身包含了一个类加载器,称为根类加载器(Bootstrap ClassLoader)。和JVM一样,根类加载器是用本地代码实现的,它负责加载核心Java类(即所有java.*开头的类)。

另外,JVM还会提供两个类加载器,它们都是用Java语言编写的,由根类加载器加载。其中,扩展类加载器(Extension ClassLoader)负责加载扩展的Java类,包括所有javax.*开头的类和存放在JRE的扩展目录下(JAVA_HOME/jre/lib/ext)中JAR的类包;系统类加载器(Application ClassLoader)负责加载应用程序自身的类。

此外,Java API中还提供了一个ClassLoader抽象类,开发者还可以通过继承ClassLoader基类来创建自定义的类加载器。

当我们运行Java.exe命令执行一个Java程序时,程序最基本的加载流程如下:

  • java.exe程序搜索jre目录,寻找JVM.dll,并启动JVM。
  • JVM运行根类加载器,该根类加载器加载Java核心API。
  • 根类加载器运行后,它会自动加载扩展类加载器和系统类加载器,并将扩展类加载器的父类设置为根类加载器,将应用加载器的父类设置为扩展类加载器。
  • 扩展类加载器加载搜索JAVA_HOME/jre/lib/ext目录,加载扩展API。
  • 应用加载器搜索CLASSPATH目录,加载我们要运行的类。
  • 类的class文件读入内存后,就会创建一个java.lang.Class对象。也就是说,当Java程序中使用任何类时,系统都会为之创建一个java.lang.Class对象。一旦某个类被载入JVM中,同一个类就不会再次被载入。

一个类加载后,对应的Class对象,可以通过该类的实例的getClass()方法得到。Class对象有一个getClassLoader()方法,可以得到加载该类所用到的类加载器。

通过使用不同的类加载器,可以从不同来源加载类的二进制数据。通常有如下几种来源:

  • 从本地文件系统加载class文件,这是我们前面绝大部分示例程序的类加载方式。
  • 从JAR包中加载class文件,这种方式也很常见。例如,我们后面要学习的JDBC数据库编程要用到的数据库驱动类就是放在JAR文件中,JVM可以从JAR文件中直接加载该class文件。
  • 通过网络加载class文件。
  • 把一个Java源文件动态编译,并执行加载。

连接

当类被加载后,系统就为之创建一个对应的Class对象,接着就会进入连接阶段。连接阶段会负责把类的二进制数据合并到JRE中。类连接又可以分为如下三个阶段:

  • 验证:检验被加载的类是否有正确的内部结构,并和其它类协调一致。
  • 准备:负责为类的静态属性分配内存,并设置默认初始值。
  • 解析:将类的二进制数据中的符号引用替换成直接引用。

初始化

随后,进行类初始化阶段。JVM负责对类进行初始化,也就是对静态属性进行初始化。在Java类中,对静态属性指定初始值的方式有两种:(1)声明静态属性时指定初始值;(2)使用静态初始化块为静态属性指定初始值。这两种方式我们在前面章节中都已经讲述。

JVM初始化一个类,一般包含如下几个步骤:

  1. 假如这个类还没有被加载和连接,程序先加载并连接该类。
  2. 假如该类的直接父类还没有被初始化,则先初始化其直接父类。
  3. 假如该类中有初始化语句,则系统依次执行这些初始化语句。

当执行第二步时,系统对直接父类的初始化步骤也遵循这三个步骤。如果该直接父类又有直接父类,系统再次重复这三个步骤,以此类推。所以,JVM最先初始化的总是java.lang.Object类。当程序主动使用任何一个类时,系统会保证该类以及它的所有父类都会被初始化。

当Java程序首次通过下面六种方式来使用某个类或者接口时,系统就会初始化该类或者接口:

  • 创建类的实例。
  • 调用某个类的静态方法。
  • 访问某个类或接口的静态属性,或者为静态属性赋值。
  • 使用反射方式强制创建某个类或接口对应的java.lang.Class对象。
  • 初始化某个类的子类。
  • 直接使用java.exe命令运行某个主类。

垃圾回收

在Java中,当对象被创建后,就会拥有一块内存。在程序运行时,JVM会陆陆续续创建很多对象。如果所有对象都永久占有内存,那么系统内存有可能很快被消耗光,最后引发内存空间不足的错误。因此,必须采用某种方法及时回收哪些无用对象的内存,以保证内存可以被重复利用。

      1. 垃圾回收的时机

在Java中,当程序中的一个对象不再可以获得时,就被标记为垃圾回收。不可获得并不意味着不再有任何引用引用该对象。我们可以认为垃圾回收器知道对一个对象的引用的确切数目,当引用计数为零时,就释放该对象。

但是,我们很容易想到另一种情况,就是有两个对象需要垃圾回收,但是每个对象都有一个对另一个对象的引用。如果使用引用计数,那么这两个对象永远不会被释放。

那么,如何使一个对象不可获得呢?我们需要确保仍然在Java应用程序范围内的引用不再引用将要被垃圾回收的对象。我们将这些引用赋值为null,或者将引用赋值为其它对象,或使引用脱离范围。

对象的finalize()方法

有时候,Java对象在运行时,会占用一些资源。当垃圾回收器要回收无用的对象时,会自动调用该对象的finalize()方法,来完成一些释放对象所占用的资源等收尾工作。

所有Java对象的根类Object中,提供了proteced类型的finalize()方法。因此,任何Java类都可以重写finalize()方法,在重写的finalize()方法中进行释放对象所占用相关资源的操作。

然而,如果在程序终止之前,垃圾回收器始终没有执行垃圾回收操作,那么垃圾回收器也不会调用无用对象的finalize()方法。

程序即使显式地调用 System.gc()或Runtime.gc()方法,也不能保证垃圾回收操作一定执行。因此,也无法保证无用对象的finalize()方法一定被调用。也就是说,垃圾回收器是否会执行 finalize()方法、以及何时执行该方法,都是不确定的。

所以,程序不能完全依赖对象的finalize()方法来完成收尾工作,而应该在那些确信可以执行的方法中完成收尾工作。finalize()方法主要用来充当第二层安全保护网,当程序忘记显式执行收尾工作时,finalize()方法可以完成收尾工作。尽管finalize() 方法不一定会被执行,但是有可能会执行总比永远不会执行更安全。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值