-
Java运行时数据区域
-
对象在内存中的状态
-
强制垃圾回收
-
finalize 方法
-
对象的软、弱和虚引用
-
- 1. 强引用 (StrongReference)
-
2. 软引用 (SoftReference)
-
3. 弱引用 (WeakReference)
-
4. 虚引用 (PhantomReference)
=============================================================================
图一:Java运行时数据区域示意图
Java 内存运行时区域中的程序计数器、虚拟机栈、本地方法栈随线程而生灭;因此这几个区域的内存分配和回收都具备确定性,不需要过多考虑回收的问题,因为方法结束或者线程结束时,内存自然就跟随着回收了。
而 Java 堆不一样,一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也可能不一样,只有在程序处于运行期间时才能知道会创建哪些对象,这部分内存的分配和回收都是动态的,Java的垃圾回收机制所关注的是这部分内存。
Java垃圾回收机制具有如下特征 :
-
垃圾回收机制只负责回收堆内存中的对象,不会回收任何物理资源(例如数据库连接、网络IO等资源) 。
-
程序无法精确控制垃圾回收的运行,垃圾回收会在合适的时候进行。当对象永久性地失去引用后,系统就会在合适的时候回收它所占的内存 。
-
在垃圾回收机制回收任何对象之前,总会先调用它的 finalize()方法,该方法可能使该对象重新复活(让一个引用变量重新引用该对象) ,从而导致垃圾回收机制取消回收。
===========================================================================
当 一个对象在堆内存中运行时,根据它被引用变量所引用的状态,可以把它所处的状态分成如下三种:
-
可达状态 : 当 一个对象被创建后,若有一个以上的引用变量引用它,则这个对象在程序中处于可达状态,程序可通过引用变量来调用该对象的实例变量和方法。
-
可恢复状态:如果程序中某个对象不再有任何引用变量引用它 ,它就进入了可恢复状态。在这种状态下,系统的垃圾回收机制准备回收该对象所占用的内存,在回收该对象之前,系统会调用所有可恢复状态对象的 finalize()方法进行资源清理 。 如果系统在调用 finalize()方法时重新让一个引用变量引用该对象,则这个对象会再次变为可达状态;否则该对象将进入不可达状态。
-
不可达状态:当对象与所有引用变量的关联都被切断,且系统已经调用所有对象的 finalize()方法后依然没有使该对象变成可达状态,那么这个对象将永久性地失去引用,最后变成不可达状态。只有当一个对象处于不可达状态时,系统才会真正回收该对象所占有的资源 。
图二显示了对象的三种状态的转换示意图 。
图二:对象状态转换示意图
例如,下面程序简单地创建了两个字符串对象,并创建了 一个引用变量依次指向两个对象 。
StatusTranfer.java
public class StatusTranfer {
public static void test () {
String a = new String(“马作的卢飞快”); //①
a = new String(“弓如霹雳弦惊”) ; //②
}
public static void main(String []args) {
test(); //③
}
}
-
当程序执行 test 方法的①代码时,代码定义了 一个 a 变量,并让该变量指向"马作的卢飞快"字符串,该代码执行结束后,"马作的卢飞快"字符串对象处于可达状态 。
-
当程序执行了 test 方法的②代码后,代码再次创建了"弓如霹雳弦惊"字符串对象,并让 a 变量指向该对象 。 此时,"马作的卢飞快"字符串对象处于可恢复状态,而"弓如霹雳弦惊"字符串对象处于可达状态。
一个对象可以被一个方法的局部变量引用, 也可以被其他类的类变量引用 ,或被其他对象的实例变量引用 。
-
当某个对象被其他类的类变量引用时, 只有该类被销毁后,该对象才会进入可恢复状态;
-
当某个对象被其他对象的实例变量引用时,只有当该对象被销毁后 ,该对象才会进入可恢复状态 。
========================================================================
当一个对象失去引用后,系统何时调用它的 finalize()方法对它进行资源清理,何时它会变成不可达状态,系统何时回收它所占有的内存,对于程序完全透明。程序只能控制一个对象何时不再被任何引用变量引用,但不能控制它何时被回收 。
虽然程序无法精确控制 Java 垃圾回收的时机,但可以强制系统进行垃圾回收一一这种强制只是通知系统进行垃圾回收,但系统是否进行垃圾回收依然不确定 。 大部分时候,程序强制系统垃圾回收后是有效的。
强制系统垃圾回收有如下两种方式 :
-
调用 System 类的 gc()静态方法: System.gc()
-
调用 Runtime 对象的 gc()实例方法: Runtime.getRuntime().gc()
下面程序创建了 4 个匿名对象 , 每个对象创建之后立即进入可恢复状态,等待系统回收 , 但直到程序退出,系统依然不会回收该资源 。
GcTest.java
public class GcTest {
public static void main(String[] args) {
for (int i = 0 ; i < 4 ; i++) {
new GcTest();
}
}
public void finalize () {
System.out.println (" 系统正在清理 GcTest 对象的资源 . . . ") ;
}
}
编译、运行上面程序 , 看不到任何输出,可见直到系统退出 , 系统都不曾调用 GcTest 对象的 finalize()方法 。 但如果将程序修改成如下形式 :
GcTest.java
public class GcTest {
public static void main(String[] args) {
for (int i = 0 ; i < 4 ; i++) {
new GcTest();
// 下面两行代码的作用 完全相同,强制系统进行垃圾回收
//System.gc() ;
Runtime.getRuntime() . gc();
}
}
public void finalize () {
System.out.println (" 系统正在清理 GcTest 对象的资源 . . . ") ;
}
}
上面程序与前一个程序相比,增加了强制系统进行垃圾回收代码 。 编译上面程序,使用如下命令来运行此程序 :
java -verbose:gc GcTest
图三:垃圾回收的运行提示信息
运行 Java 命令时指定 -verbose :gc 选项,可以看到每次垃坡回收后的提示信息,如图三所示 。
从图三 中可以看出,每次调用了 Runtime.getRuntime(). gc()代码后 , 系统垃圾回收机制 还是"有所动作"的,可以看出垃圾回收之前、回收之后的内存占用对 比 。
虽然图三显示了程序强制垃圾回收的效果,但仍然要认识到这种强制只是建议系统立即进行垃坡回收 , 系统完全有可能并不立即进行垃圾回收,垃圾回收机制也不会对程序的建议完全置之不理 : 垃圾回收机制会在收到通知后,尽快进行垃圾回收 。
=============================================================================
在垃圾回收机制回收某个对象所占用的内存之前,通常要求程序调用适当的方法来清理资源 , 在没有明确指定清理资源的情况下, Java 提供了默认机制来清理该对象的资源,这个机制就是 finalize()方法 。
该方法是定义在 Object 类里的实例方法,方法原型为 :
protected void finalize() throws Throwable
当 finalize()方法返回后,对象消失,垃圾回收机制开始执行 。方法原型中的 throws Throwable 表示它可以抛出任何类型的异常 。
任何 Java 类都可以重写 Object 类的 finalizeO方法,在该方法中清理该对象占用的资源 。 如果程序终止之前始终没有进行垃圾回收,则不会调用失去引用对象的 finalize()方法来清理资源 。
垃圾回收机制何时调用对象的 finalizeO方法是完全透明的,只有当程序认为需要更多的额外内存时,垃圾回收机制才会进行垃坡回收 。 因此,完全有可能出现这样一种情形:某个失去引用的对象只占用了少量内存,而且系统没有产生严重的内存需求,因此垃圾回收机制并没有试图回收该对象所占用的资源,所以该对象的自finalize()方法也不会得到调用。
finalize()方法具有如下 4 个特点 :
-
永远不要主动调用某个对象的 finalize()方法,该方法应交给垃圾回收机制调用 。
-
finalize()方法何时被调用,是否被调用具有不确定性 ,不要把自finalize()方法当成一定会被执行的方法 。
-
当 JVM 执行可恢复对象的 finalize()方法时,可能使该对象或系统中其他对象重新变成可达状态。
-
当执行 finalize()方法时出现异常时,垃圾回收机制不会报告异常,程序继续执行 。
下面程序演示了如何在 finalize()方法里复活自身,并可通过该程序看出垃圾回收的不确定性。
FinalizeTest.java
public class FinalizeTest {
private static FinalizeTest ft = null;
public void info() {
System.out.println( "测试资源清理的 finalize 方法 " );
}
public static void main(String[] args) throws Exception{
// 创建 FinalizeTest 对象立即进入可恢复状态
new FinalizeTest() ;
// 通知系统进行资源回收
System.gc(); //①
// 强制垃圾回收机制调用可恢复对象的 finalize ()方法
// Runtime.getRuntime() . runFinalization() ; //②
System.runFinalization(); //③
ft.info();
}
public void finalize() {
// 让 ft 引用到试图回收的可恢复对象,即可恢复对象重新变成可达
ft = this;
}
}
上面程序中定义了 一个 FinalizeTest 类,重写了该类的 finalizeO方法,在该方法中把需要清理的可恢复对象重新赋给 a 引用变量,从而让该可恢复对象重新变成可达状态。
上面程序中的 main()方法创建了 一个 FinalizeTest 类的匿名对象,因为创建后没有把这个对象赋给任何引用变量,所以该对象立即进入可恢复状态 。 进入可恢复状态后:
-
系统调用①号字代码通知系统进行垃圾回收
-
②号代码强制系统立即调用可恢复对象的 finalize()方法,再次调用位对象的 info()方法 。编译、运行上面程序 , 看到 info()方法被正常执行 。
-
如果删除①行代码,取消强制垃圾回收 。 再次编译、运行上面程序,将会看到如图四所示的结果 。
图四:调用info()方法时引发空指针异常
从图四所示的运行结果可以看 出,如果取消①号代码,程序并没有通知系统开始执行垃圾回收(而且程序内存也没有紧张 ) ,因此系统通常不会立即进行垃圾回收,也就不会调用 FinalizeTest对象的 fmalize()方法,这样 FinalizeTest 的ft类变量将依然保持为 null,这样就导致了空指针异常 。
上面程序中②号代码和③号代码都用于强制垃圾回收机制调用可恢复对象的 finalize()方法,如果程序仅执行 System.gc(); 代码,而不执行②号或③号代码一一由于 JVM垃圾回收机制的不确定性,JVM往往并不立即调用可恢复对象的 finalize()方法,这样 FinalizeTest 的ft类变量可能依然为 null ,可能依然会导致空指针异常。
============================================================================
对大部分对象而言 ,程序里会有一个引用变量引用该对象,这是最常见的引用方式 。 除此之外,还有软引用、弱引用、虚引用。
强引用就是指在程序代码之中普遍存在的,类似 "Object obj = new Object()” 这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。
软引用是用来描述一些还有用但并非必需的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。在JDK 1.2之后,提供了 SoftReference类来实现软引用。
弱引用也是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用 关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当 前内存是否足够,都会回收掉只被弱引用关联的对象。在JDK 1.2之后,提供WeakReference类来实现弱引用。
虚引用也称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象是否有虚 引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实 例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一 个系统通知。在JDK 1.2之后,提供了 PhantomRcfcrcncc类来实现虚引用。
上面三个引用类都包含了 一个 get()方法,用于获取被它们所引用的对象 。
引用队列由 java. lang.ref.ReferenceQueue 类表示,它用于保存被回收后对象的引用 。 当联合使用软引用、弱引用和引用队列时,系统在回收被引用的对象之后,将把被回收对象对应的引用添加到关联的引用队列中。与软引用和弱引用不同的是,虚引用在对象被释放之前,将把它对应的虚引用添加到它关联的引用队列中,这使得可以在对象被回收之前采取行动 。
软引用和弱引用可以单独使用,但虚引用不能单独使用,单独使用虚引用没有太大的意义。虚引用的主要作用就是跟踪对象被垃圾回收的状态,程序可以通过检查与虚引用关联的引用队列中是否已经包含了该虚引用,从而了解虚引用所引用的对象是否即将被回收 。
下面程序示范了弱引用所引用的对象被系统垃圾回收过程 :
ReferenceTest.java
public class ReferenceTest {
public static void main(String[] args) throws Exception {
//创建一个字符串对象
String str = new String( "疯狂Java讲义 " ) ;
//创建一个弱引用,让此弱引用引用到 " 疯狂 Java 讲义 " 字符串
WeakReference wr = new WeakReference(str) ; //①
技术学习总结
学习技术一定要制定一个明确的学习路线,这样才能高效的学习,不必要做无效功,既浪费时间又得不到什么效率,大家不妨按照我这份路线来学习。
最后面试分享
大家不妨直接在牛客和力扣上多刷题,同时,我也拿了一些面试题跟大家分享,也是从一些大佬那里获得的,大家不妨多刷刷题,为金九银十冲一波!
相关阅读docs.qq.com/doc/DSmxTbFJ1cmN1R2dB
字符串对象
String str = new String( "疯狂Java讲义 " ) ;
//创建一个弱引用,让此弱引用引用到 " 疯狂 Java 讲义 " 字符串
WeakReference wr = new WeakReference(str) ; //①
技术学习总结
学习技术一定要制定一个明确的学习路线,这样才能高效的学习,不必要做无效功,既浪费时间又得不到什么效率,大家不妨按照我这份路线来学习。
[外链图片转存中…(img-WSK8tioj-1724652981605)]
[外链图片转存中…(img-KUojHB8R-1724652981606)]
[外链图片转存中…(img-qEqha2RF-1724652981607)]
最后面试分享
大家不妨直接在牛客和力扣上多刷题,同时,我也拿了一些面试题跟大家分享,也是从一些大佬那里获得的,大家不妨多刷刷题,为金九银十冲一波!
[外链图片转存中…(img-bi0ECnVe-1724652981607)]
[外链图片转存中…(img-cfpiyzeU-1724652981608)]
相关阅读docs.qq.com/doc/DSmxTbFJ1cmN1R2dB