Topic: 一位“资深”同事为了对象重用,而写下的代码....

  Print this page

1.一位“资深”同事为了对象重用,而写下的代码.... Copy to clipboard
Posted by: cxp108
Posted on: 2009-01-22 17:12

Document [] old ......//这是数据源
EntityDocument[] newArray = new EntityDocument[old.length];//自定义的类,为了把Document里数据保留下来避免Document被关联对象关闭而导致无法取出数据。
EntityDocument d = new EntityDocument();
for(int i=0;i<old.length;i++){
d.content = old[i].getContent();
d.key = old[i].getKey();
d......................
newArray[i] = d;//如此对象重用.....
}

2.Re:一位“资深”同事为了对象重用,而写下的代码.... [Re: cxp108] Copy to clipboard
Posted by: 九佰
Posted on: 2009-01-23 17:10

这么做是有道理的。
请看下面的讲解:

JVM中对象的生命周期
在JVM运行空间中,对象的整个生命周期大致可以分为7个阶段:创建阶段(Creation)、应用阶段(Using)、不可视阶段(Invisible)、不可到达阶段(Unreachable)、可收集阶段(Collected)、终结阶段(Finalized)与释放阶段(Free)。上面的这7个阶段,构成了JVM中对象的完整的生命周期。下面分别介绍对象在处于这7个阶段时的不同情形。

2.2.1 创建阶段
在对象创建阶段,系统要通过下面的步骤,完成对象的创建过程:

(1)为对象分配存储空间。

(2)开始构造对象。

(3)递归调用其超类的构造方法。

(4)进行对象实例初始化与变量初始化。

(5)执行构造方法体。

上面的5个步骤中的第3步就是指递归地调用该类所扩展的所有父类的构造方法,一个Java类(除Object类外)至少有一个父类(Object),这个规则既是强制的,也是隐式的。你可能已经注意到在创建一个Java类的时候,并没有显式地声明扩展(extends)一个Object父类。实际上,在Java程序设计中,任何一个Java类都直接或间接的是Object类的子类。例如下面的代码:

public class A {



}

这个声明等同于下面的声明:

public class A extends java.lang.Object {



}

上面讲解了对象处于创建阶段时,系统所做的一些处理工作,其中有些过程与应用的性能密切相关,因此在创建对象时,我们应该遵循一些基本的规则,以提高应用的性能。

下面是在创建对象时的几个关键应用规则:

(1)避免在循环体中创建对象,即使该对象占用内存空间不大。

(2)尽量及时使对象符合垃圾回收标准。

(3)不要采用过深的继承层次。

(4)访问本地变量优于访问类中的变量。

关于规则(1)避免在循环体中创建对象,即使该对象占用内存空间不大,需要提示一下,这种情况在我们的实际应用中经常遇到,而且我们很容易犯类似的错误,例如下面的代码:

… …

for (int i = 0; i < 10000; ++i) {

Object obj = new Object();

System.out.println("obj= "+ obj);

}

… …

上面代码的书写方式相信对你来说不会陌生,也许在以前的应用开发中你也这样做过,尤其是在枚举一个Vector对象中的对象元素的操作中经常会这样书写,但这却违反了上述规则(1),因为这样会浪费较大的内存空间,正确的方法如下所示:

… …

Object obj = null;

for (int i = 0; i < 10000; ++i) {

obj = new Object();

System.out.println("obj= "+ obj);

}

… …

采用上面的第二种编写方式,仅在内存中保存一份对该对象的引用,而不像上面的第一种编写方式中代码会在内存中产生大量的对象应用,浪费大量的内存空间,而且增大了系统做垃圾回收的负荷。因此在循环体中声明创建对象的编写方式应该尽量避免。

另外,不要对一个对象进行多次初始化,这同样会带来较大的内存开销,降低系统性能,如:

public class A {

private Hashtable table = new Hashtable ();

public A() {

// 将Hashtable对象table初始化了两次

table = new Hashtable();

}

}

正确的方式为:

public class B {

private Hashtable table = new Hashtable ();

public B() {

}

}

不要小看这个差别,它却使应用软件的性能相差甚远,如下图所示。

3.Re:一位“资深”同事为了对象重用,而写下的代码.... [Re: cxp108] Copy to clipboard
Posted by: cxp108
Posted on: 2009-01-25 01:10

版主可能没有注意看程序....

EntityDocument[] newArray = new EntityDocument[old.length];
声明时,数组每个元素都是指向null的reference
而当其在循环中对一个EntityDocument d进行赋值
EntityDocument d = new EntityDocument();
for(int i=0;i<old.length;i++){
d.content = old[i].getContent();
d.key = old[i].getKey();
尽管这段代码仅仅生成了一个d对象,很好的重用了d对象,但是
后面
newArray[i] = d ;
这段代码却大错特错,因为其将newArray的所有reference指向
唯一的一个d对象,这也就导致整个newArray数组都由一个对象组成。
与程序意图完全不符。

4.Re:一位“资深”同事为了对象重用,而写下的代码.... [Re: cxp108] Copy to clipboard
Posted by: JiafanZhou
Posted on: 2009-01-28 17:05

This is a severe bug.

5.Re:一位“资深”同事为了对象重用,而写下的代码.... [Re: JiafanZhou] Copy to clipboard
Posted by: cxp108
Posted on: 2009-02-01 10:31

JiafanZhou wrote:
This is a severe bug.

是挺严重的,因为比较隐蔽。

6.Re:一位“资深”同事为了对象重用,而写下的代码.... [Re: cxp108] Copy to clipboard
Posted by: Biubiu
Posted on: 2009-02-03 15:11

只是个bug而已。谁都会犯错误。

7.Re:一位“资深”同事为了对象重用,而写下的代码.... [Re: cxp108] Copy to clipboard
Posted by: menzy
Posted on: 2009-02-04 15:35

这个错误说明,此人对对象生命周期并不了解
BTW: 如果这个人以前用过C++,发生这种问题的几率会降低

8.Re:一位“资深”同事为了对象重用,而写下的代码.... [Re: Biubiu] Copy to clipboard
Posted by: cxp108
Posted on: 2009-02-04 18:23

Biubiu wrote:
只是个bug而已。谁都会犯错误。

我觉得这是一个很基础的错误,一个资深工程师
不应该犯这种错,而且还和我争了半天说他没错
,最后是他自己写代码验证才不说话。

9.Re:一位“资深”同事为了对象重用,而写下的代码.... [Re: cxp108] Copy to clipboard
Posted by: cxp108
Posted on: 2009-02-04 18:30

怎么进来的都是版主??

10.Re:一位“资深”同事为了对象重用,而写下的代码.... [Re: cxp108] Copy to clipboard
Posted by: JiafanZhou
Posted on: 2009-02-04 20:24

cxp108 wrote:
我觉得这是一个很基础的错误,一个资深工程师
不应该犯这种错,而且还和我争了半天说他没错
,最后是他自己写代码验证才不说话。

确实每个人都会犯这样的错误,但是因该能看出来的,单元测试也是一个好办法。

Consider the following example:


// still not the best way, but much more comprehensible than the original one...
Document[] old = ...
ArrayList<EntityDocument> newArray = new ArrayList<EntityDocument>();

for (int i = 0; i < old.length; i++)
{
EntityDocument document = new EntityDocument();
document.content = old[i].getContent();
document.key = old[i].getKey();
newArray.add(document);
}
// deal with the newArray cache...

使用Java数组有很多毛病,而且不具有继承特性.

Jiafan

11.Re:一位“资深”同事为了对象重用,而写下的代码.... [Re: cxp108] Copy to clipboard
Posted by: breezehou
Posted on: 2009-02-06 09:09

cxp108 wrote:
版主可能没有注意看程序....

EntityDocument[] newArray = new EntityDocument[old.length];
声明时,数组每个元素都是指向null的reference
而当其在循环中对一个EntityDocument d进行赋值
EntityDocument d = new EntityDocument();
for(int i=0;i<old.length;i++){
d.content = old[i].getContent();
d.key = old[i].getKey();
尽管这段代码仅仅生成了一个d对象,很好的重用了d对象,但是
后面
newArray[i] = d ;
这段代码却大错特错,因为其将newArray的所有reference指向
唯一的一个d对象,这也就导致整个newArray数组都由一个对象组成。
与程序意图完全不符。

确实如此,newArray里的元素都被初始化为old数组中最后一个元素。

12.Re:一位“资深”同事为了对象重用,而写下的代码.... [Re: breezehou] Copy to clipboard
Posted by: ditty
Posted on: 2009-02-11 15:08

其实,说白了就是在循环体内,忘记了初始化对象d而已。
既然是资深,还是需要得到尊敬,lz有些借题发挥的味道。

13.Re:一位“资深”同事为了对象重用,而写下的代码.... [Re: ditty] Copy to clipboard
Posted by: cxp108
Posted on: 2009-02-13 15:58

ditty wrote:
其实,说白了就是在循环体内,忘记了初始化对象d而已。
既然是资深,还是需要得到尊敬,lz有些借题发挥的味道。

问题并不在于初始化d,而在于对一个reference赋值模式
的不了解。
我之所以强调资深并不是讽刺他,而是为了强调一个这么
基础的问题,不应该出现在经验丰富的人身上。希望ditty
不要误会我的用意...
当我们写下每行代码的时候,都必须考虑一下这行代码计
算机会做些什么,而不能理所当然地想计算机会按照我们
的意愿去做。你说是吗?

14.Re:一位“资深”同事为了对象重用,而写下的代码.... [Re: cxp108] Copy to clipboard
Posted by: ditty
Posted on: 2009-02-13 16:42

嗬嗬,用代码说话吧:

EntityDocument d = new EntityDocument();
for(...){
....
newArray[i] = d;
}

=>

EntityDocument d;
for(...){
d = new EntityDocument();
....
newArray[i] = d;
}

首先要清楚人家为什么要把变量定义放在循环体外面,然后再去指责。

15.Re:一位“资深”同事为了对象重用,而写下的代码.... [Re: ditty] Copy to clipboard
Posted by: cxp108
Posted on: 2009-02-13 17:26

ditty wrote:
嗬嗬,用代码说话吧:

EntityDocument d = new EntityDocument();
for(...){
....
newArray[i] = d;
}

=>

EntityDocument d;
for(...){
d = new EntityDocument();
....
newArray[i] = d;
}

首先要清楚人家为什么要把变量定义放在循环体外面,然后再去指责。

你这么写就不叫对象重用了,因为循环内每次都要重新构建一个EntityDocument
对象。尽管它符合程序的意图。

16.Re:一位“资深”同事为了对象重用,而写下的代码.... [Re: cxp108] Copy to clipboard
Posted by: lxh_ming
Posted on: 2009-02-14 03:06

其实这种问题谁都会犯,我以前有个同事也犯了类似的错误,不过他是读上传的文件流,然后写入文件,buffer里面的数据也是象楼主那样生成,所以最好上传的文件怎么也没法子解压,因为都是二进制文件的最后一行。自己看代码又很不容易看出来问题,这个问题也是一个写代码的习惯问题。
我的看法是出现这种问题不可怕,总会有人能发现并解决这个问题,问题是犯错误的人态度怎么样。

17.Re:一位“资深”同事为了对象重用,而写下的代码.... [Re: cxp108] Copy to clipboard
Posted by: ditty
Posted on: 2009-02-17 13:18

cxp108 wrote:
你这么写就不叫对象重用了,因为循环内每次都要重新构建一个EntityDocument
对象。尽管它符合程序的意图。

应用的规则参见2楼九佰贴的那部分,每个人都有自己的代码风格,前提是要服从项目中的具体规定。这里就不和你较真的,其实我要说的就是,你认为有问题的代码其实仅仅是一个bug,至于是否符合你的所说的对象重用,那就不好说了。赫赫。

18.Re:一位“资深”同事为了对象重用,而写下的代码.... [Re: ditty] Copy to clipboard
Posted by: cxp108
Posted on: 2009-02-18 15:56

ditty wrote:
应用的规则参见2楼九佰贴的那部分,每个人都有自己的代码风格,前提是要服从项目中的具体规定。这里就不和你较真的,其实我要说的就是,你认为有问题的代码其实仅仅是一个bug,至于是否符合你的所说的对象重用,那就不好说了。赫赫。

我只是要说明这个bug是在试图进行对象重用时,对一些基本知识的欠缺而犯下的
低级错误。我不明白这和代码风格有什么关系?
我觉得任何时候,代码有bug那就是程序员的错,大家都会犯错,出现bug很正常
但不能轻描淡写地以“仅仅是一个bug”带过,除非你会容忍自己的代码到处是bug。

19.Re:一位“资深”同事为了对象重用,而写下的代码.... [Re: cxp108] Copy to clipboard
Posted by: JiafanZhou
Posted on: 2009-02-18 18:18

I see people have quite different opinions on this thread with regards to the severity of this bug. To be honest, I believe that it is an open question to everybody. My impression is that if this buggy piece of code is used in a critical section of a project, then it will cause this bug to be a Blocker at Priority P1 - P3. Because this bug is not only a performance hit, but also a functionality fault. However, on the other hand, if it is used in a not critical section, then it is fair to say that this bug can be only treated as "A normal BUG". But, as I mentioned before, because it is a functionality bug, it will still be considered as a P2-P4 (normal to major) one. (IMO, it is a severe one).

Also as people mentioned the coding style, I agree with cxp108 that it is a different issue with reference to the coding style. I am not going too further in this part, but I do believe that having a unique coding style dispersed across the company will make the bug easier to be disposed by others. And thus cost less time and effort. In other words, "Coding Style != Excuse to generate bugs"

Now, let's focus on the solution to this bug. Take everything into consideration, following list contains my opinions with regards to this bug.
- I believe that we should only create one instance of the EntityDocument outside the for loop.
- I have overlooked the performance hit in my previous code snippet. We should avoid that.
- We should avoid using Java arrays, as they have several disadvantages compared to dynamic arrays.
- Mark the EntityDocument implement Cloneable interface and override the clone() method.
- Use the clone() copy to avoid "referencing" issues.

The first example uses the fixed array. And it is not recommended.

Document [] old ......//这是数据源
EntityDocument[] newArray = new EntityDocument[old.length];//自定义的类,为了把Document里数据保留下来避免Document被关联对象关闭而导致无法取出数据。
EntityDocument d = new EntityDocument();

for(int i=0;i<old.length;i++)
{
d.content = old[i].getContent();
d.key = old[i].getKey();
d......................
newArray[i] = d.clone();//如此对象重用..... // N.B. If we do this, then we create a new reference and assign the new reference to the array element.
}


The second example does the same thing and it uses dynamic array.

// still not the best way, but much more comprehensible than the original one...
Document[] old = ...
ArrayList<EntityDocument> newArray = new ArrayList<EntityDocument>();
EntityDocument document = new EntityDocument();

for (Document oldDoc : old)
{
// EntityDocument document = new EntityDocument(); // remove this!!
document.content = oldDoc.getContent();
document.key = oldDoc.getKey();
newArray.add(document.clone());
}
// deal with the newArray cache...


And what's left is that I suggest you (or your co-worker) to write some JUnit tests to test the performance on various of implementation. For example, how much time to execute 1,000 Document[], 10,000 etc?? If you have these testing figures, your viewpoint will be much more convincing to everyone here, cxp108 right??

Jiafan

20.Re:一位“资深”同事为了对象重用,而写下的代码.... [Re: cxp108] Copy to clipboard
Posted by: ditty
Posted on: 2009-02-19 13:22

做人最重要的是换位思考,把对别人的尖刻转化为对自己的严格才会得到更大的进步。不想就前面的话题争论下去了,回到代码本身。

jiafan上面的改进还是有必要的,避免循环体内声明变量,尽量重用变量的声明。只是在具体使用时,还要考虑一下克隆对象的内部结构,注意克隆深度的问题。

只是没有验证过,new 和 clone从performance角度到底有多大的差别,有经验的朋友可以来说说。

21.Re:一位“资深”同事为了对象重用,而写下的代码.... [Re: JiafanZhou] Copy to clipboard
Posted by: cxp108
Posted on: 2009-02-19 15:24

To JiafanZhou
是的,我同意你的看法。这段代码特别是EntityDocument[]是项目很多基础
类的数据来源,实际的情况比这个要复杂,因此是一个很严重的functionality fault。

可能很多人都误以为我要讨论性能议题,但实际上这个帖子与性能一点关系
都没有。

new 和clone都能解决这个bug,但我们现在考虑另外两条路:对象池 和 类似javaNIO里
的buffer机制。

22.Re:一位“资深”同事为了对象重用,而写下的代码.... [Re: cxp108] Copy to clipboard
Posted by: JiafanZhou
Posted on: 2009-02-19 23:56

Today, I wrote a very simple unit test class to test the performance of clone() and new instance. Believe it or not, I am surprised to find out that the clone() method is much slower than the new instance. I will also attach the source code "CloneTest.java" for anyone who is interested to see the test result. After a little bit googling, I have a conclusion that actually in the HotSpot JVM Object.clone() is not currently heavily optimized, while new instance is. From now on, I will not use the clone() methods any more I believe.

Following the the test result of both clone(), create a new instance, and the System.copyarray() methods.

--------------------------------------------------------------------
Time to excute 1000000 clone() takes 262 milliseconds
Time to excute 1000000 new Object() takes 29 milliseconds
Time to excute 1000000 System.arrayCopy takes 17 milliseconds
--------------------------------------------------------------------

Also I found out a very interesting thread about the comparison between clone() and creating a new instance, see the following link:
http://forums.java.net/jive/thread.jspa?threadID=743

Now, I would suggest another solution which is shown below:


System.arraycopy(sourceArray, 0, destinationArray, 0, sourceArray.length);


cxp108 wrote:
new 和clone都能解决这个bug,但我们现在考虑另外两条路:对象池 和 类似javaNIO里
的buffer机制。

Do you really have a performance hit here? If so, then using object pool or java NIO is worthy looking at. Otherwise, it could be just some features overkilled. IMO.

Jiafan

CloneTest.java (2.07k)

23.Re:一位“资深”同事为了对象重用,而写下的代码.... [Re: cxp108] Copy to clipboard
Posted by: tangming
Posted on: 2009-02-24 17:40

为什么要用clone, pool,是因为new一个对象需要比较大的代价,比如new一个Connection,而对于上面的CloneTest对象来说,new一个根本不需要做什么操作,new比clone快也很正常。

24.Re:一位“资深”同事为了对象重用,而写下的代码.... [Re: tangming] Copy to clipboard
Posted by: cxp108
Posted on: 2009-02-25 14:58

EntityDocument的构造不仅耗时,而且它是一个复杂对象(还是叫复合对象?),
构造函数是我们应该考虑的一方面,但是如果一个对象包含很多其他对象,那么对
其的垃圾回收的耗时,也是我们要考虑的一方面,大家说是吗?
复杂对象的重用,在创建和回收时都有一定的收益。当然,前提是重用的算法
不要太复杂。

25.Re:一位“资深”同事为了对象重用,而写下的代码.... [Re: 九佰] Copy to clipboard
Posted by: informixca
Posted on: 2009-03-17 23:36

九佰 wrote:
for (int i = 0; i < 10000; ++i) {

Object obj = new Object();

System.out.println("obj= "+ obj);

}

… …

上面代码的书写方式相信对你来说不会陌生,也许在以前的应用开发中你也这样做过,尤其是在枚举一个Vector对象中的对象元素的操作中经常会这样书写,但这却违反了上述规则(1),因为这样会浪费较大的内存空间,正确的方法如下所示:

… …

Object obj = null;

for (int i = 0; i < 10000; ++i) {

obj = new Object();

System.out.println("obj= "+ obj);

}

… …

采用上面的第二种编写方式,仅在内存中保存一份对该对象的引用,而不像上面的第一种编写方式中代码会在内存中产生大量的对象应用,浪费大量的内存空间,而且增大了系统做垃圾回收的负荷。因此在循环体中声明创建对象的编写方式应该尽量避免。


Wrong! The first code and the second code all "仅在内存中保存一份对该对象的引用" Both code has same "系统做垃圾回收的负荷". Please remember the "obj" reference is a local reference not an instance reference. On the java memory model,local reference's on the stack not heap.

26.Re:一位“资深”同事为了对象重用,而写下的代码.... [Re: cxp108] Copy to clipboard
Posted by: wuanfo
Posted on: 2009-04-08 08:44

学习了。


   Powered by Jute Powerful Forum® Version Jute 1.5.6 Ent
Copyright © 2002-2021 Cjsdn Team. All Righits Reserved. 闽ICP备05005120号-1
客服电话 18559299278    客服信箱 714923@qq.com    客服QQ 714923