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.举个例子-看你能 ...
随机推荐
- vlan 技术
Ref: VLAN及Trunk,重要!看瑞哥如何讲的明明白白! 图文并茂VLAN以及Trunk详解,超级详细
- 8.4linux定时任务-环境变量-数据库
配合SUID本地环境变量提权 思路原理:利用sh环境变量替换,使得/tmp/ps得到root权限:ps=sh 过程:手写调用文件-编译-复制文件-增加环境变量-执行 gcc demon1.c -o s ...
- 5.5文件上传-WAF绕过
一.WAF绕过(明确有文件上传) 1.上传参数中,可修改参数 Content-Dispositin:一般可改 name:表单参数,不可更改 filename:文件名,可更改 Content-Type: ...
- ansible rpm包下载
Ansible2.9.18版本下载链接:https://pan.baidu.com/s/1dKlwtLWSOKoMkanW900n9Q 提取码:ansi 将软件上传至系统并解压安装: # tar -z ...
- Nuxt3 使用animate.css来实现页面切换过渡效果
首先,如果两个page分别在不同的layout下,是无法使用pageTransition来实现切换效果的,这时候需要使用layoutTransition来实现切换效果 这里采用npm package的 ...
- Asp.net 获取客户端的信息
Response.Write("客户端计算机名:" + Request.UserHostName + "<BR />"); Response.Wri ...
- OData – Query to Expression
前言 EF Core 可以把 expression 转换成 string, 但没办法转回来. 想把 string 转成 expression, 目前最合适的工具是 OData. 虽然 Dynamic ...
- pytorch中LSTM各参数理解
nn.LSTM(input_dim,hidden_dim,nums_layer,batch_first) 各参数理解: input_dim:输入的张量维度,表示自变量特征数 hidden_dim:输出 ...
- 暑假集训CSP提高模拟 16
\[暑假集训CSP提高模拟 \lim_{x\rightarrow\infty}\frac{8f_{x}}{f_{x+1}}\times(\sqrt{5}+1),\ \forall f_{x}=f_{x ...
- USB眼图常识
最近一段时间,项目需求,需要对USB眼图进行摸底测试.测试过程很简单,然而debug之路却很不简单.不简单往往也就意味着带来了不少收获. 示波器跨接在接收滤波器的输出端,调整示波器扫描周期,使示波器水 ...