[Effective Java]第二章 创建和销毁对象
第一章 前言
略...
第二章 创建和销毁对象
1、 考虑用静态工厂方法代替构造器
条)调用私有构造器,如果要抵御这种攻击,可以修改构造器,让他在要求创建第二个实例的时候抛出异常。
第二种方法中公有的成员是个静态工厂方法:
public class Elvis {
private static final Elvis INSTANCE = new Elvis();
private Elvis() { }
public static Elvis getInstance() { return INSTANCE; }
}
,简单的说就是防止私有域导出到序列化流中而受到攻击)声明所有实例域都是瞬时(transient)的,并提供一个readResolve方法,否则,每次序列化时,都会创建一个新的实例,正确的作法:
且已达到最大允许容量则会删除最老的项
if (num.intValue() < 3 && size() > MAX_ENTRIES) {
System.out.println("超容 - " + this);
return true;
}
return false;
}
public static void main(String[] args) {
CacheLinkedHashMap lh = new CacheLinkedHashMap();
for (int i = 1; i <= 5; i++) {
lh.put("K_" + Integer.valueOf(i), Integer.valueOf(i));
}
System.out.println(lh);
// 放入时会删除最早放入的 k_1 项
lh.put("K_" + Integer.valueOf(11), Integer.valueOf(0));
System.out.println(lh);
}
}
输出:
{K_1=1, K_2=2, K_3=3, K_4=4, K_5=5}
超容 - {K_1=1, K_2=2, K_3=3, K_4=4, K_5=5, K_11=0}
{K_2=2, K_3=3, K_4=4, K_5=5, K_11=0}
说到这里,我们再来看看LinkedHashMap的另一特性 —— 可以按照我们访问的顺序来重新排序(即访问的项放到链表最后),平时我们构造的LinkedHashMap 是按照存放的顺序来排的,如果要按照我们访问(调用get或者是修改时,即put已存在的键时相当于修改,如果放入的不是存在的项则还是放在链的最后)的顺序来重排集合,则需使用LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder)来构造,并将accessOrder设置为true:
public class OrderLinkedHashMap {
public static void main(String[] args) {
//按存入顺序连接
LinkedHashMap lh = new LinkedHashMap();
init(lh);
print(lh);//{0=0, 1=1, 2=2, 3=3, 4=4}
lh.get(0);//不将访问过的项放到链表最后
print(lh);//{0=0, 1=1, 2=2, 3=3, 4=4}
lh = new LinkedHashMap(10, 0.75f, true);//按访问顺序
init(lh);
print(lh);//{0=0, 1=1, 2=2, 3=3, 4=4}
lh.get(0);//会将访问过的项放到链表最后
print(lh);//{1=1, 2=2, 3=3, 4=4, 0=0}
lh.put(1, 11);//会将访问过的项放到链表最后
print(lh);//2=2, 3=3, 4=4, 0=0, 1=11,
}
static void init(Map map) {
for (int i = 0; i < 5; i++) {
map.put(i, i);
}
}
static void print(Map map) {
Iterator it = map.entrySet().iterator();
while (it.hasNext()) {
Entry entry = (Entry) it.next();
System.out.print(entry.getKey() + "=" + entry.getValue() + ", ");
}
System.out.println();
}
}
内存泄漏的第三个常见来源是监听器和其他回调。如果你实现了一个API,客户端在这个API中注册回调(即将回调实例存储到某个容器中),却没有显示地取消注册,那么除非你采取某些动作,否则它们就会积聚。确保立即被垃圾回收的最佳方法是只保存它的弱引用,例如,只将它们保存成WeakHashMap中的键。
内存泄漏剖析工具:Heap Profiler
7、 避免使用终结方法
终结方法(finalize)通常是不可预测的,也是限危险的,一般情况下是不必要的,它不是C++中的析构函数。为什么呢?在C++中所有的对象运用delete()一定会被销毁,而JAVA里的对象并非总会被垃圾回收器回收,所以调用的时机是不确定的。C++的析构器也可以被用来回收其他非内存资源,而在Java中,一般用try-finally块来完成类似的工作。
终结方法的缺点是不能保证会被及时地执行。
Java语言规范不仅不保证终结方法会被及时地执行,而且根本就不保证它们会被执行。当一个程序终止的时候,某些已经无法访问的对象上的终结方法却根本没有被执行,这是完全有可能的。
不要被System.gc和System.runFinalization这两个方法所诱惑,它们确实增加了终结方法被执行的机会,但是它们并不保证终结方法一定会被执行。唯一声称保证终结方法被执行的方法是System.runFinalizersOnExit,以及它臭名昭著的孪生兄弟Runtime.runFinalizersOnExit,这两个方法都有致命的缺陷(它可能对正在使用的对象调用终结方法,而其他线程正在操作这些对象,从而导致不正确的行为或死锁),已经被废弃了。注,runFinalizersOnExit(true)只是在JVM退出时才开始调用那些还没有调用的对象上的finalize方法(默认情况下JVM退出时不会调用这些方法),而不像前面的gc与runFinalization在调用稍后执行finalize方法(也可能不执行,因为垃圾收集器并未开始工作)。
如果未被捕获的异常在终结过程中被抛出来,那么该异常将被忽略,并且该对象的终结过程也会终止,并且不会打印异常栈轨迹信息,但该对象仍可以被垃圾收集器收集。
还有一点,使用终结方法有一个非常严重的性能损失。换句话说,用终结方法创建和销毁对象慢了很多。
如果类对象中封闭的资源确实需要终止,我们首先需提供一个显示的终止方法(如InputStream、OutputStream、Connection、Timer上的close方法),并通常与try-finally结构结合起来使用,以确保及时终止(另外要注意的是,该实例应该记录下自己是否已经被关闭了,如果用户已显示地关闭了,则在终结方法中不得再关闭),而不是将它们的释放工作放在finalize终结方法中执行。
当然终结方法不是一无事处的,它有两种合法用途。第一种用途是,当对象的所有者忘记调用前面段落中建议的显示终止方法时,终结方法可以充当“安全网”,我们可以在finalize方法中再进行一次释放资源的工作。这样做并不能保证终结方法会被及时调用或甚至不会被调用,但是在客户端无法通过调用显示的终止方法(或者是根本未调用或忘记调用)来正常结束操作的情况下,这样迟一点释放关键资源总比永远不释放要好。但是如果终结方法发现资源还未被终止,则应该在日志中记录一条警告(最好再调用一次释放资源的方法),因为这表示客户端代码中的一个Bug,应该得到修复,如果你正考虑编写这样的安全网终结方法,就要认真考虑清楚,这种的保护是否值得你付出这份额外的代价。
显示终止方法模式的类(如InputStream、OutputStream、Connection、Timer)都具有终结方法,当它们的终止方法未能被调用的情况下,可以再次在终结方法中显示调用它们的关闭方法,这样终结方法充当了安全网;第二种就是可以回收那些并不重要的本地资源(即本地方法所分配的资源)。
“终结方法链(父类的终结方法)”并不会被自动被执行。如果类(不是Object)有终结方法,并且子类覆盖了终结方法,子类的终结方法就必须手工调用超类的终结方法。你应该在一个try块中终结子类,并在相应的finally块中调用超类的终结方法。这样做可以保证:即使子类的终结过程抛出异常,超类的终结方法也会得到执行,如下面示例:
protected void finalize() throws Throwable{
try{
…// 子类回收动作
}finally{
super.finalize();// 调用父类的终结方法
}
}
如果子类实现者覆盖了超类的终结方法,但是忘了手式调用超类的终结方法,那么超类的终结方法永远也不会被调用到。要防范这样的粗心大意,我们可以为每个将被终结的对象创建一个附加的对象。不是把终结方法放在要求终结处理的类中,而是把终结方法放在一个匿名的类中,该匿名类的唯一作用就是终结它的外围实例。该匿名类的单个实例被称为终结方法守卫者,外围类的每个实例都会创建这样一个守卫者。外围实例在它的私有实例哉中保存着一个对其终结方法守卫者的唯一引用,因些终结方法守卫都与外围实例可以同时启动终结过程。当守卫都被终结的时候,它执行外围实例所期望的终结行为,就好像它的终结方法是外围对象上的一个方法一样:
class Foo{
//终结守卫者
private final Object finalizerGuardian = new Object(){
protected void finalize() throws Throwable{
System.out.println("Foo gc");
}
};
}
class Sub extends Foo{
// 即使没有在子类的终结方法中调用父类的终结方法,父类也会终结
protected void finalize() throws Throwable {
System.out.println("Sub gc");
}
}
注意,公有类Foo并没有终结方法(除了它从Object中继承了一个无关紧要的的之外),所以子类的终结方法是否调用super.finalize并不重要。
总之,除非是作为安全网,或者是为了终止非关键的本地资源,否则请不要使用终结方法。当然如果使用了终结方法,就要记得调用super.finalize。如果用终结方法作为安全网,要记得记录终结方法的非法调用。最后,如果需要把终结方法与公有的非final类关联起来,请考虑使用终结方法守卫者,以确保即使子类的终结方法未能调用super.finalize,该终结方法也会被执行。
补充:
虽然终结一个对象时,它不会去自动调用父类的终结方法,除非手工调用super.finalize,但是父类里的成员域一定会被调用终结方法,这就是为什么终结守卫者安全的原因。另外,回收的顺序是不确定的,不会像C++中的那样,先调用子类析构函数,再调用父类析构函数。还有一点要注意的是,即使对象的finalize 已经运行了,不能保证该对象被销毁。因为对象可以重生。
对于任何给定对象,Java 虚拟机最多只调用一次 finalize 方法。
垃圾收集器的工作过程大致是这样的:一旦垃圾收集器准备好释放无用对象占用的存储空间,它首先调用那些对象的finalize()方法,然后才真正回收对象的内存。
与 Java 不同,C++ 支持局部对象(存储在栈中)和全局对象(存储在堆中),C++ 能对栈中的对象自动析构,但对于堆中的对象就要手动分配内存与释放。在 Java 中,所有对象都驻留在堆内存,而内存的回收则由垃圾收集器统一回收。
finalize可以用来保护非内存资源被释放,即使我们定义了其它的方法来释放非内存资源,但是其它人未必会调用该方法来释放。在finalize里面可以检查一下,如果没有释放就释放好了,晚释放总比不释放好,这样好比“双保险”。通常我们可以在finalize方法中释放容易被忽略的资源,并且这些都是非常重要的资源。
[Effective Java]第二章 创建和销毁对象的更多相关文章
- [Effective Java 读书笔记] 第二章 创建和销毁对象 第一条
第二章 创建和销毁对象 第一条 使用静态工厂方法替代构造器,原因: 静态工厂方法可以有不同的名字,也就是说,构造器只能通过参数的不同来区分不同的目的,静态工厂在名字上就能表达不同的目的 静态工厂方法 ...
- Effective Java笔记一 创建和销毁对象
Effective Java笔记一 创建和销毁对象 第1条 考虑用静态工厂方法代替构造器 第2条 遇到多个构造器参数时要考虑用构建器 第3条 用私有构造器或者枚举类型强化Singleton属性 第4条 ...
- Effective Java学习笔记--创建和销毁对象
创建和销毁对象 一.静态工厂方法代替构造器 静态工厂方法的优缺点 优点: 1.可以自定义名称(可以将功能表述的更加清晰) 2.不必每次调用都创建新的对象(同一对象重复使用) 3.返回的类型可以是原返回 ...
- [Effective Java读书笔记] 第二章 创建和销毁对象(1~7)
我的技术博客经常被流氓网站恶意爬取转载.请移步原文:http://www.cnblogs.com/hamhog/p/3537576.html,享受整齐的排版.有效的链接.正确的代码缩进.更好的阅读体验 ...
- [Effective Java 读书笔记] 第二章 创建和销毁对象 第二条
第二条 遇到多个构造器参数时,可以考虑用构建器 当遇到有多个构造器参数时,常见的是用重叠构造器,即: public class TestClass{ public TestClass(int para ...
- [Effective Java 读书笔记] 第二章 创建和销毁对象 第六-七条
第六条 消除过期引用 JAVA中依然会有 memory leak的,比如一个栈先增长再收缩,那么从栈中弹出的对象是不会被当做垃圾回收的,即时使用栈的程序不再引用这些对象.这是因为栈的内部维护着对这些对 ...
- [Effective Java 读书笔记] 第二章 创建和销毁对象 第五条
第五条 避免创建不必要的对象 书中一开始举例: String s = new String("stringette"); // don't do this //应该使用下面,只会创 ...
- [Effective Java 读书笔记] 第二章 创建和销毁对象 第三 四条
第三条 用私有构造器或者枚举类型强化singleton属性 singleton指只能被实例化一次的类,即将构造器设置为私有,使用公有静态成员来实例化,且只实例化一次对象 第四条 通过私有构造器强化不可 ...
- 【Effective Java】第二章-创建和销毁对象——1.考虑用静态工厂方法代替构造器
静态工厂方法的优点: 可以赋予一个具有明确含义的名称 可以复用唯一实例,不必每次新建 可以返回原实例类型的子类对象 可以在返回泛型实例时更加简洁 缺点: 类如果不含有共有的或者受保护的构造器,就不能被 ...
随机推荐
- TVideoGrabber如何将网络摄像头影像实时发布到网络
在TVideoGrabber中如何将网络摄像头影像实时发布到网络?如何设置正在运行TVideoGrabber的一台电脑,同时通过另一台电脑在网络中实时的观看在线视频呢? 在这里称发送视频流的电脑为“m ...
- python时间处理函数
所有日期.时间的api都在datetime模块内. 1. 日期输出格式化 datetime => string import datetime now = datetime.datetime.n ...
- Java程序编译和运行的过程【转】
转自:http://www.360doc.com/content/14/0218/23/9440338_353675002.shtml Java整个编译以及运行的过程相当繁琐,本文通过一个简单的程序来 ...
- new和malloc的区别
1.malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符 2.new出来的指针是直接带类型信息的,而malloc返回的都是void*指针. 3.new 建立的是一个 ...
- Volley Get Post 方法
Get String url = CommonInterfaceUrl.COMM_GetWorksDetailUrl + "/" + worksID; RequestQueue m ...
- cocospod 安装和使用
一 ruby 安装 要安装coocspod 首先需要安装ruby,可以先安装xcode,在安装macport 下载地址,最后执行命令 port install ruby 二.安装CocoaPods 1 ...
- PHP 页面编码声明与用header或meta实现PHP页面编码的区别
php的header来定义一个php页面为utf编码或GBK编码 php页面为utf编码 header("Content-type: text/html; charset=utf-8&quo ...
- 再谈自主开发与企业IT管理
前两天写<自主开发与带兵打仗>分析了一下自主开发的利与弊,得到了园内不少朋友的反馈,但我觉得还有很多东西没有交待清楚,可能有很多朋友也跟我一样在公司的IT部门,有自己的研发团队也有很多外购 ...
- Ecshop后台订单列表增加”商品名”检索字段
近期ecshop网站做活动,统计商品订单量的时候没有按商品名搜索的选项,只能手动查询.这样效率很低下,而且容易出错. 现在为列表增加一个简单的“按商品名搜索”表单项.效果如下图 涉及到2个文件,分别是 ...
- ecshop简单三部实现导航分类二级菜单
1.在page_header.lbi对应的位置(你想显示导航的位置)插入 (注意下面的"themes/模板名称/util.php"中的"模板名称"改成你模板文件 ...