一、finalize() 方法

1. 为什么要有 finalize() 方法?

假定你的对象(并非使用new)获得了一块“特殊”的内存区域,由于垃圾回收器只知道释放那些经由 new 分配的内存,所以他不知道该如何释放该对象的这块“特殊”内存,为了应对这种情况,java 允许在类中定义一个 finalize() 的方法。

    protected void finalize(){

    }

2. finalize()方法在何时调用?

一旦垃圾回收器准备好释放对象占用的存储空间,将首先调用其 finalize() 方法,并且在下一次垃圾回收动作发生时,才会真正回收对象占用的内存。

finalize() 方法是对象逃脱死亡命运的最后一次机会,如果对象要在 finalize() 方法中成功拯救自己 — 只要重新与引用链上的任何一个对象建立关联即可,譬如把自己(this 关键字)赋值给某个类变量或者对象的成员变量,那么它将被移除出”即将回收“的集合;如果对象这时候还没有逃脱,那基本上它就真的被回收了。

3. finalize()的局限性?

  finalize() 方法不是 C/C++ 的析构函数,而是 Java 刚诞生时为了使 C/C++ 程序员更容易接受它所做出的一个妥协。

一个对象的 finalize() 方法最多只会被系统自动调用一次。

finalize() 方法运行代价高昂,不确定性大,无法保证各个对象的调用顺序,虚拟机调用 finalize() 方法甚至不能保证 finalize() 的逻辑执行完毕。

finalize() 方法内做普通的清除工作是不合适的。 如果一定要进行回收动作,最好自己写一个回收方法 dispose() 方法。应当注意的是如果子类重写了父类的 dispose() 方法,当进行清除动作时,应该先清除子类的,再清除父类的,原因在于:可能子类存在对父类的方法调用。

建议大家可以完全忘掉 Java 语言中有这个方法的存在。

二、对象存活算法

如果 JVM 并未面临内存耗尽的情形,它是不会浪费时间在回收垃圾上的,而进行垃圾回收之前首先要进行判定的就是 —— 对象是否存活,下面介绍几种对象存活算法:

1. 引用计数算法(Reference Counting)

给每个对象都添加有一个引用计数器,当有引用连接至对象时,引用计数加 1;当引用离开作用域或被置为 null 时,引用计数器减1。垃圾回收器会在含有全部对象的列表上,当发现某个对象的引用计数为 0 时,就释放其占有的空间。

客观的说,引用计数算法实现简单,判断效率也很高,在大部分情况下都是一个不错的算法。但是这种算法有个缺陷,如果对象之间存在循环引用,可能会出现“对象应该被回收,但引用计数却不为0的情况”。

目前主流的 Java 虚拟机都没有先用引用计数算法来管理内存。

2. 可达性分析算法(Reachability Analysis)

这个算法的基本思路就是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径成为引用链(Reference Chain),当一个对象到 GC Roots 没有任何引用链相连时,则判断此对象是不可用的。在 Java 语言中,可作为 GC Roots 的对象包括以下几种:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象。
  • 本地方法栈中 JNI(即一般说的 Native 方法)引用的对象。
  • 方法区中类静态属性引用的对象。
  • 方法区中常量引用的对象。

主流的商用语言(Java、C#等)都是通过可达性分析算法来判断对象是否存活的。

三、Java 引用类型

在 JDK 1.2 以前,Java 中的引用定义很传统:如果 reference 类型的数据中存储的数值代表的是另外一块内存的起始地址,就称这块内存代表着一个引用。在这种定义下,一个对象只有引用和没有被引用两个状态。

在 JDK 1.2 之后,Java 将引用分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)四种,这四种引用强度依次逐渐变弱。在这种定义下,一个对象的引用状态被大大丰富,虚拟机也根据对象引用状态确定是否对对象进行回收。

强引用

类似 “Object obj = new Object()” 这类的引用,只要强引用还存在,垃圾收集器永远不会掉被引用的对象。

软引用

描述一些还有用但并非必需的对象。在系统将要发生内存溢出异常之前,将会把可回收的软引用对象进行二次回收,如果回收后还没有足够的内存,才会抛出内存溢出异常。

Java 中提供了 SoftReference 类来实现软引用。

软引用还可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收(即对象的强引用被回收,该对象变成了软可及对象),Java 虚拟机就会把这个软引用加入到与之关联的引用队列中。在软引用还没被垃圾回收之前,通过软引用的 get() 方法可以重新获得强引用,相反,如果软引用也被垃圾回收了,该软引用的 get() 方法就会返回 null。

tips:我们可以通过检查 ReferenceQueue 元素的 get() 方法是否返回 null,来判断该软引用是否已经被回收。

弱引用

也是用来描述非必需对象的,可回收的弱引用对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,可回收的弱引用对象都会被回收。

Java 中提供了 WeakReference 类来实现弱引用。

弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java 虚拟机就会把这个弱引用加入到与之关联的引用队列中。同样,Java 虚拟机就会把这个弱引用加入到与之关联的引用队列中。在弱引用还没被垃圾回收之前,通过弱引用的 get() 方法可以重新获得强引用,相反,如果弱引用也被垃圾回收了,该软引用的 get() 方法就会返回 null。

虚引用

最弱的一种引用关系,一个对象是否有虚引用,完全不会对其生存时间构成影响,也无法通过一个虚引用来取得一个对象实例,为一个对象设置虚引用关联的唯一目的就是能够在这个对象被收集器回收时收到一个系统通知。

Java 提供了 PhantomReference 类来实现虚引用。

虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之 关联的引用队列中,程序员以此来追踪一个对象的回收情况。

四、引用实践

1. 通过软可及对象重获方法实现Java对象的高速缓存

这里的高速缓存是指:当 Java 对象的强引用已经被垃圾回收或被设置成 null 值,但是该对象的软引用还没被回收,这时可以通过软引用的 get() 方法重新获取强引用,避免重新实例化对象。

1.1 Employee

public class Employee {

    private String id;// 雇员的标识号码
private String name;// 雇员姓名
private String department;// 该雇员所在部门
private String Phone;// 该雇员联系电话
private int salary;// 该雇员薪资
private String origin;// 该雇员信息的来源 // 构造方法
public Employee(String id) {
this.id = id;
getDataFromlnfoCenter();
} // 到数据库中取得雇员信息
private void getDataFromlnfoCenter() {
// 和数据库建立连接井查询该雇员的信息,将查询结果赋值
// 给name,department,plone,salary等变量
// 同时将origin赋值为"From DataBase"
this.name = "JMCui";
this.Phone = "15980292662";
this.salary = 5000;
this.origin = "From DataBase";
}
}

1.2 EmployeeCache

public class EmployeeCache {
// 一个 Cache 实例
private static volatile EmployeeCache cache;
// 用于 Cache 内容的存储
private Hashtable<String, EmployeeRef> employeeRefs;
// 垃圾 Reference 的队列(当软引用对象被回收的时候,会将 SoftReference 保存到该队列)
private ReferenceQueue<Employee> queue; // 构建一个缓存器实例
private EmployeeCache() {
employeeRefs = new Hashtable<>();
queue = new ReferenceQueue<>();
} // 取得缓存器实例
public static EmployeeCache getInstance() {
if (cache == null) {
synchronized (EmployeeCache.class) {
if (cache == null) {
cache = new EmployeeCache();
}
}
}
return cache;
} // 以软引用的方式对一个Employee对象的实例进行引用并保存该引用
private void cacheEmployee(Employee em) {
// 清除垃圾引用
cleanCache();
EmployeeRef ref = new EmployeeRef(em, queue);
employeeRefs.put(em.getId(), ref);
} // 依据所指定的ID号,重新获取相应Employee对象的实例
public Employee getEmployee(String id) {
Employee em = null;
// 缓存中是否有该Employee实例的软引用,如果有,从软引用中取得。
if (employeeRefs.containsKey(id)) {
EmployeeRef ref = employeeRefs.get(id);
em = ref.get();
}
// 如果没有软引用,或者从软引用中得到的实例是null,重新构建一个实例,
// 并保存对这个新建实例的软引用
if (em == null) {
em = new Employee(id);
System.out.println("Retrieve From EmployeeInfoCenter. ID=" + id);
cacheEmployee(em);
}
return em;
} // 清除那些所软引用的Employee对象已经被回收的EmployeeRef对象
private void cleanCache() {
EmployeeRef ref;
while ((ref = (EmployeeRef) queue.poll()) != null) {
employeeRefs.remove(ref._key);
}
} // 继承SoftReference,使得每一个实例都具有可识别的标识。
// 并且该标识与其在 HashMap 内的key相同。
// 如果 SoftReference 对象没有被回收,则通过 SoftReference.get() 可以返回强引用对象,否则返回 null。
private class EmployeeRef extends SoftReference<Employee> { private String _key; public EmployeeRef(Employee em, ReferenceQueue<Employee> q) {
super(em, q);
_key = em.getId();
}
} public static void main(String[] args) {
EmployeeCache employeeCache = getInstance();
Employee employee1 = employeeCache.getEmployee("11111");
System.out.println("employee1:" + employee1.getName());
employee1 = null;
Employee employee2 = employeeCache.getEmployee("11111");
System.out.println("employee2:" + employee2.getName());
employee2 = null;
System.gc();
Employee employee3 = employeeCache.getEmployee("11111");
System.out.println("employee3:" + employee3.getName());
}
}

2. WeakHashMap 实践

WeakHashMap 的 key 是一个弱引用的对象,当一个键对象被垃圾回收器回收时,那么相应的值对象的引用会从Map中删除。

public class WeakMapManager {

    public static class Element {
private String ident; public Element(String id) {
ident = id;
} @Override
public String toString() {
return ident;
} @Override
public int hashCode() {
return ident.hashCode();
} @Override
public boolean equals(Object obj) {
return obj instanceof Element && ident.equals(((Element) obj).ident);
}
} public static class Key extends Element {
public Key(String id) {
super(id);
}
} public static class Value extends Element {
public Value(String id) {
super(id);
}
}

public static void main(String[] args) {
int size = 20;
// 该数组的作用,仅仅只是维护一个强引用
Key[] keys = new Key[size];
Map<Key, Value> map = new WeakHashMap<>();
for (int i = 0; i < size; i++) {
Key k = new Key(Integer.toString(i));
Value v = new Value(Integer.toString(i));
if (i % 3 == 0) keys[i] = k;
map.put(k, v);
}
System.gc();
System.out.println(map);
}
}

从打印结果可以看出,当执行 System.gc() 方法后,垃圾回收器只会回收那些仅仅持有弱引用的 Key 对象,id 可以被 3 整除的 Key 对象持有强引用,因此不会被回收。

参考资料:

1. Java 对象的强、软、弱和虚引用

2. 《Thinking in Java(3th)》

3. 《深入理解 Java 虚拟机》

终结 finalize() 和对象引用的更多相关文章

  1. 终结 finalize()和垃圾回收(garbage collection)

    1.为什么要有finalize()方法? 假定你的对象(并非使用new)获得了一块"特殊"的内存区域,由于垃圾回收器只知道释放那些经由new分配的内存,所以他不知道该如何释放该对象 ...

  2. 垃圾回收GC:.Net自己主动内存管理 上(三)终结器

    垃圾回收GC:.Net自己主动内存管理 上(三)终结器 垃圾回收GC:.Net自己主动内存管理 上(一)内存分配 垃圾回收GC:.Net自己主动内存管理 上(二)内存算法 垃圾回收GC:.Net自己主 ...

  3. CLR回收非托管资源

    一.非托管资源 在<垃圾回收算法之引用计数算法>.<垃圾回收算法之引用跟踪算法>和<垃圾回收算法之引用跟踪算法>这3篇文章中,我们介绍了垃圾回收的一些基本概念和原理 ...

  4. java 面试大全

    一.CoreJava 部分: 基础及语法部分: 1.面向对象的特征有哪些方面? [基础] 答:面向对象的特征主要有以下几个方面: 1)抽象:抽象就是忽略一个主题中与当前目标无关的那些方面,以便更充分地 ...

  5. cir from c# 托管堆和垃圾回收

    1,托管堆基础 调用IL的newobj 为资源分配内存 初始化内存,设置其初始状态并使资源可用.类型的实列构造器负责设置初始化状态 访问类型的成员来使用资源 摧毁状态进行清理 释放内存//垃圾回收期负 ...

  6. .NET中的垃圾回收

    目录 l         导言 l         关于垃圾回收 l         垃圾回收算法 m        应用程序根(Application Roots) l         实现 m   ...

  7. Object类方法简介二

    在学了Object类前面的三个常用方法后,又遇到它的另外三个方法——clone().finalize().getClass(),这三个方法不经常使用,但因为在学习过程遇到了,就简单的对它们的使用做一个 ...

  8. Java垃圾回收与内存

    好久没看关于java的书了, 最近, 看了James Gosling的<<Java程序设计语言>>, 做了一些读书笔记. 这部分是关于垃圾回收的. 1.垃圾回收 对象是使用ne ...

  9. 【C# .Net GC】清除非托管类型(Finalize终结器、dispose模式以及safeHandler)

    总结 1.一般要获取一个内核对象的引用,最好用SafeHandle来引用它,这个类可以帮你管理引用计数,而且用它引用内核对象,代码更健壮 2.托管中生成并引用非托管,一但非托管和托管中的引用断开(托管 ...

随机推荐

  1. 如何在linux终端创建文件

    我们都知道可以用mkdir命令创建一个新的目录,但更多时候如果能直接创建一个文件(普通文件)会让人感觉更愉悦:这样就可以不用在去打开一个专门的创建文本文件的软件,然后还要设置文件名,保存路径那样的繁琐 ...

  2. Security+学习笔记

    第二章 风险分析 风险管理 评估:确定并评估系统中存在的风险 分析:分析风险对系统产生的潜在影响 响应:规划如何响应风险的策略 缓解: 缓解风险对未来安全造成的不良影响 风险分析流程 资产确定 漏洞确 ...

  3. December 07th, Week 49th Saturday, 2019

    Snowflakes are pretty patterns etched in water's dreams. 雪花,是水在梦中镌刻的美丽图案. From Anthony T.Hincks. Tod ...

  4. 10.jenkins 按角色分配

    在实际的生产中,需要项目比较多.不同的用户需要对应 不同的项目工程 .这个时候,我们需要按角色给与权限. 要实现这个功能,需要一个插件来完成 . Role-based Authorization St ...

  5. npm报错及解决

    nodejs安装之后 无法使用 npm的解决方法 首先我们要知道Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境 Node.js 使用了一个事件驱动.非阻塞式 I ...

  6. 目前为止最简洁的C#文件夹Copy代码,不接受反驳

    private static void CopyEntireDir(string sourcePath, string destPath) { foreach (string dirPath in D ...

  7. .Net Core MVC中过滤器简介

    在.Net Framework MVC 中有四种过滤器,授权过滤器(Authorize).Action 过滤器.结果过滤器(Result).异常过滤器(Exception)四种过滤器.在.Net Co ...

  8. XMind 是一个全功能的思维导图和头脑风暴软件,为激发灵感和创意而生

    XMind 是一个全功能的思维导图和头脑风暴软件,为激发灵感和创意而生 https://www.xmind.cn/

  9. jmeter控制器(三)

    While Controllert当控制器: 当满足条件的情况下,就会执行控制器里面的脚本,首先我们设置线程组循环次数为10,如下图: 其次在配置元件中添加一个计数器,并设置从0到最大的10,每次递增 ...

  10. linux 命令cp -a的用法

    cp -a 保留原文件属性的前提下复制文件 cp -r dirname(源文件) destdi(目标文件) 复制目录后其文件属性会发生变化想要使得复制之后的目录和原目录完全一样包括文件权限,可以使用c ...