Java的内存管理1:“并不只有C++程序员关心内存回收”——Java的内存管理2:"不中用的finalize( )方法"
通常Java的缓存管理会由垃圾回收器(Java Garbage Collection)定时处理,无须程序员操心。但Java Garbage Collection仅有权回收那些非“强引用”(Strong Reference)类型的类。也就是说,如果程序生成大量的强引用对象,JVM存放对象实例的“堆”(heap)容量不够时(默认64M,可设置),就会抛出java.lang.OutOfMemoryError:
Java heap space的错误,看下面的代码:

编译器报错:Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
JDK用自带的类java.lang.ref.SoftReference提供解决办法,而不是像C++那样自己动手写缓存管理队列。SoftReference将我们新实例化的类转变成“软引用”(Soft Reference)型,以便Garbage Collection能够在JVM的heap不足时,回收那些相对较旧的“软引用”实例,腾出heap空间存放新的对象实例。需要注意的是,尽管“软引用”实例被回收,但它们的Reference已经在JVM中完成登记,一旦它们被再次引用,JVM能够复原它们(仍旧是“软引用”型)。下面的代码显示如何使用SoftReference类:
上述代码可以用:BookShelf shelf = (BookShelf)shelves[i].get();得到BookShelf的实例。这时所有实例都会被创建:

JavaDoc1.6中写道,一个实例的引用类型,可用它在引用链中的可达性(reachable)表示,JVM共有5种可达性:强可到达(strongly reachable);软可到达(softly reachable);弱可到达(weakly reachable);虚可到达(phantomly reachable);不可到达(unreachable). 除了Strong Reference,其他四种对象都可以被回收。
可到达性
从最强到最弱,不同的可到达性级别反映了对象的生命周期。在操作上,可将它们定义如下:
- 如果某一线程可以不必遍历所有引用对象而直接到达一个对象,则该对象是强可到达 对象。新创建的对象对于创建它的线程而言是强可到达对象(注:这就是第一段代码中BookShelf不能被gc回收的原因:BookShelf对Liberary来说是强可到达对象)。
- 如果一个对象不是强可到达对象,但通过遍历某一软引用可以到达它,则该对象是软可到达 对象。
- 如果一个对象既不是强可到达对象,也不是软可到达对象,但通过遍历弱引用可以到达它,则该对象是弱可到达 对象。当清除对某一弱可到达对象的弱引用时,便可以终止此对象了。
- 如果一个对象既不是强可到达对象,也不是软可到达对象或弱可到达对象,它已经终止,并且某个虚引用在引用它,则该对象是虚可到达 对象。
- 最后,当不能以上述任何方法到达某一对象时,该对象是不可到达 对象,因此可以回收此对象。
java.lang.ref包中对应还有WeakReference、PhantomReference两个类,有机会再实践下。
import java.lang.ref.SoftReference;
class Book
{
char[] symbols = new char[300000];
}
class BookShelf
{
Book[] books = new Book[100];
public BookShelf()
{
for (int i = 0; i < books.length; i++)
{
books[i] = new Book();
}
// TODO Auto-generated constructor stub
}
}
public class Library
{
public static void main(String[] args)
{
// BookShelf[] shelves = new BookShelf[50];
SoftReference[] shelves = new SoftReference[50];
for (int i = 0; i < shelves.length; i++)
{
// shelves[i] = new BookShelf();
shelves[i] = new SoftReference(new BookShelf());
System.out.println("Creating bookshelf: " + (i + 1));
}
}
}
《Java的内存管理1》提醒我们:如果需要在同一对象中不停地new一些占用大量内存的实例,务必使用java.lang.ref.SoftReference类对实例进行“包装”,防止JVM因Java Garbage Collection不能回收新建实例,而造成的内存溢出错误。
我想到可以用finalize()方法,观察实例在什么时候会被GC回收(任何继承Object的类都可Override),finalize()会在一个实例即将被GC回收的时候被调用一次,因此可用它记录一个实例的“临终遗言”,我对代码做了如下修改:(顺道实现了java1.5的泛型)

运行结果却出乎意料:
Creating bookshelf: 1
Creating bookshelf: 2
Creating bookshelf: 3
Creating bookshelf: 4
Creating bookshelf: 5
Creating bookshelf: 6
Creating bookshelf: 7
Creating bookshelf: 8
Creating bookshelf: 9
Creating bookshelf: 10
Creating bookshelf: 11
BookShelf: 10 is dying!
BookShelf: 9 is dying!
BookShelf: 8 is dying!
BookShelf: 7 is dying!
BookShelf: 6 is dying!
BookShelf: 5 is dying!
BookShelf: 4 is dying!
BookShelf: 3 is dying!
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at edu.thinkingjava.chap4.Book.<init>(Library.java:5)
at edu.thinkingjava.chap4.BookShelf.<init>(Library.java:15)
at edu.thinkingjava.chap4.Library.main(Library.java:28)
BookShelf: 2 is dying!
BookShelf: 1 is dying!
BookShelf: 11 is dying!
即便使用了SoftReference做缓存保护,控制台再次抛出OutOfMemoryError的异常。输出的BookShelf销毁顺序,每次都有不同,颇有多线程的味道。由此看出GC的回收机制充满不确定性。JavaDoc1.6解释如下:
在启用某个对象的 finalize 方法后,将不会执行进一步操作,直到 Java 虚拟机再次确定尚未终止的任何线程无法再通过任何方法访问此对象,其中包括由准备终止的其他对象或类执行的可能操作,在执行该操作时,对象可能被丢弃。 对于任何给定对象,Java 虚拟机最多只调用一次 finalize 方法。
JavaDoc实际上在说:finalize()方法阻挡了GC对BookShelf实例的首次回收,JVM转而去调用finalize()方法,所幸Java 虚拟机最多只调用一次 finalize 方法,当下一次JVM发现内存吃紧,BookShelf实例才能最终被回收。
由此例推断:JVM释放内存的进度被finalize方法耽误了,因此出现内存溢出异常(大家可以试着把代码第19行注释掉,内存又恢复正常了)。
import java.lang.ref.SoftReference;
class Book
{
char[] symbols = new char[300000];
}
class BookShelf
{
private int ID;
Book[] books = new Book[100];
public BookShelf()
{
for (int i = 0; i < books.length; i++)
{
books[i] = new Book();
}
// TODO Auto-generated constructor stub
}
public BookShelf(int ID)
{
this.ID = ID;
for (int i = 0; i < books.length; i++)
{
books[i] = new Book();
}
// TODO Auto-generated constructor stub
}
@Override
protected void finalize() throws Throwable
{
// TODO Auto-generated method stub
super.finalize();
System.out.println("BookShelf: " + ID + " is dying!");
}
}
public class Library
{
public static void main(String[] args)
{
// BookShelf[] shelves = new BookShelf[50];
SoftReference[] shelves = new SoftReference[50];
for (int i = 0; i < shelves.length; i++)
{
// shelves[i] = new BookShelf();
shelves[i] = new SoftReference(new BookShelf(i + 1));
System.out.println("Creating bookshelf: " + (i + 1));
}
}
}
Java的内存管理1:“并不只有C++程序员关心内存回收”——Java的内存管理2:"不中用的finalize( )方法"的更多相关文章
- Java匹马行天下之C国程序员的秃头原因
Java帝国的崛起 前言: 分享技术之前先请允许我分享一下黄永玉老先生说过的话:“明确的爱,直接的厌恶,真诚的喜欢.站在太阳下的坦荡,大声无愧地称赞自己.” <编程常识知多少> <走 ...
- 程序员必须掌握的Java 框架,小白学会之后15k不是问题
Spring 的核心特性是什么?Spring 优点? Spring 的核心是控制反转(IoC)和面向切面(AOP) Spring 优点: 程序员必须掌握的Java 框架,学会之后50k不是问题 (1) ...
- 从程序员到CTO的Java技术路线图 作者:zz563143188
在技术方面无论我们怎么学习,总感觉需要提升自已不知道自己处于什么水平了.但如果有清晰的指示图供参考还是非常不错的,这样我们清楚的知道我们大概处于那个阶段和水平. Java程序员 高级特性 反射.泛型. ...
- 年度Java技术盘点,懂这些技术的程序员2019发展大好
与一年前一样,Java仍然是最流行的编程语言.据TIOBE的数据显示,几十年来,Java比其他语言更常名列榜首,Java因为它拥有可移植性.可扩展性和庞大的用户社区,所以许多知名互联网公司使用Java ...
- MySQL内存表(MEMORY)说明 | 一个PHP程序员的备忘录
MySQL内存表(MEMORY)说明 | 一个PHP程序员的备忘录 MySQL内存表(MEMORY)说明
- 做什么职业,也别做程序员,尤其是Java程序员
千万别做程序员,尤其别做Java这种门槛低,入门快的程序员(别跟我说Java搞精通了也很牛之类的,原因不解释,做5年以上就知道了),程序员本来就是我见过最坑爹的职业了...Java程序员更是,现在满地 ...
- 寻找下一个结点 牛客网 程序员面试金典 C++ java Python
寻找下一个结点 牛客网 程序员面试金典 C++ java Python 题目描述 请设计一个算法,寻找二叉树中指定结点的下一个结点(即中序遍历的后继). 给定树的根结点指针TreeNode* root ...
- 碰撞的蚂蚁 牛客网 程序员面试金典 C++ Java Python
碰撞的蚂蚁 牛客网 程序员面试金典 C++ Java Python 题目描述 在n个顶点的多边形上有n只蚂蚁,这些蚂蚁同时开始沿着多边形的边爬行,请求出这些蚂蚁相撞的概率.(这里的相撞是指存在任意两只 ...
- 检查是否是BST 牛客网 程序员面试金典 C++ java Python
检查是否是BST 牛客网 程序员面试金典 C++ java Python 题目描述 请实现一个函数,检查一棵二叉树是否为二叉查找树. 给定树的根结点指针TreeNode* root,请返回一个boo ...
- java程序员--小心你代码中的内存泄漏
当你从c&c++转到一门具有垃圾回收功能的语言时,程序员的工作就会变得更加容易,因为你用完对象,他们会被自动回收,但是,java程序员真的不需要考虑内存泄露吗? 其实不然 1.举个例子-看你能 ...
随机推荐
- 使用 Quickwit 的搜索流功能为 ClickHouse 添加全文搜索
本指南将帮助您使用 Quickwit 的搜索流功能为知名的 OLAP 数据库 ClickHouse 添加全文搜索.Quickwit 暴露了一个 REST 端点,可以极快地(每秒最多 5000 万条)流 ...
- 【Docker学习系列】Docker学习2-docker设置阿里云镜像加速器
在上一篇中,我们学会了在centos中安装docer.我们知道,镜像都是外网的,镜像一般都是比较大的,因为种种原因,我们知道,从外网下载比较慢的.所以,本文,凯哥就介绍怎么将docker的镜像拉取设置 ...
- (Ljava/lang/String;)Ljava/util/List;
背景:原正常代码,更改类名后,重新运行 报错:(Ljava/lang/String;)Ljava/util/List; 解决:mvn clean 后 compile,再运行,正常
- SpringMVC:文件上传和下载
文件下载 ResponseEntity用于控制器方法的返回值类型,该控制器方法的返回值就是响应到浏览器的响应报文 使用ResponseEntity实现下载文件的功能 @RequestMapping(& ...
- sentinel中如何使用@SentinelResource和openFeign来进行服务熔断和降级的操作
sentinel 前方参考 计算QPS-Sentinel限流算法 https://www.cnblogs.com/yizhiamumu/p/16819497.html Sentinel 介绍与下载使用 ...
- ansible rpm包下载
Ansible2.9.18版本下载链接:https://pan.baidu.com/s/1dKlwtLWSOKoMkanW900n9Q 提取码:ansi 将软件上传至系统并解压安装: # tar -z ...
- VS2019 查看源码,使用F12查看源码
前几天在微软社区看到VS的功能演示时,偶然看到此功能,对于开发人员来说太有用了,特此记录分享出来希望可以帮助到家. 具体设置步骤,打开vs2019,在工具>选项>文本编辑器>c#&g ...
- 使用Joi 完成JavaScript 数据校验
无论是在前端还是后端(Node.js),数据校验都是一件不可或缺的事情,使用JOI让我们轻松的完成数据校验 > npm install joi 这里持续记录使用心得 版本:17.4.2 官网:h ...
- Go runtime 调度器精讲(八):sysmon 线程和 goroutine 运行时间过长的抢占
原创文章,欢迎转载,转载请注明出处,谢谢. 0. 前言 在 Go runtime 调度器精讲(七):案例分析 一文我们介绍了一个抢占的案例.从案例分析抢占的实现,并未涉及到源码层面.本文将继续从源码入 ...
- C++ cout打印输出 (解决输出乱码)
cout打印输出 输出单份内容 // 输出单份内容 cout << "Hello World!" << endl; cout << 10 < ...