2007年以来,从Geotools、PostGIS到JTS Topology Suite再到java优化编程感受,看似过程相当的复杂,而且相当的凌乱。呵呵,都是Geotools惹的祸呀,没有办法,为了能深度使用Geotools我只能研究JTS Topology Suite,在学习JTS过程中又有一些感想,呵呵,其实从目的而言跨度没有那么大,只是一个磨刀不误砍柴功的过程。
读了一些JTS的源码,呵呵,不对不是一些,而只是com.vividsolutions.jts.geom中的一部分那,还不到整个JTS Topology Suite源码的1/10的代码,对其实现思路可能还不是很清晰,但是看着大拿们写的代码还是让我觉得很有收获。这些收获有我以前没有感受的,也有以前知道原理但是就是不去重视的内容。写在这些文字当作是对自己编程风格的一篇械文。也希望在学习JTS的过程中能从大拿们的代码中学习到更多的内容,最终使得自己不仅仅学习这些编程大拿成果的使用,更使自己能提高自己的代码质量。
作为自己对Java优化编程感受的开篇,还是从最基础的GC开始吧。呵呵,毕竟GC的存在是Java的特色之一,但是如果完全依托于JVM中的GC就想获取良好的程序效率太困难了,这一点相信很多人都有一个清楚的认识。如何合理的使用Java回收机制,如何让GC更快的实施回收,我想也许很多程序员和我一样都不是很重视,所以先来抛个砖,让朋友们一起来批判一下。
(一下文字中有一些引用了曾经看过的《JVM初探》、《Java的GC机制》等牛人文章,由于时间太长,很多定义我无法记住来源,所以就不一一标明了。望原作者谅解!ps:标题貌似是这个)
Java程序中的内存管理机制是通过GC完成的,“一个对象创建后被放置在JVM的堆内存中,当永远不在应用这个对象的时候将会被JVM在堆内存中回收。被创建的对象不能再生,同时也没有办法通过程序语句释放”(这个是《Java的GC机制》中提到的定义,呵呵,还依稀记得)这就是GC对垃圾对象的定义。个人感觉这么解释或许会比较快理解:在运行环境中JVM会对两种内存进行管理,一种是堆内存(对象实例或者变量),一种是栈内存(静态或非静态方法),而JVM所管理的内存区域实际上就是堆内存+栈内存(MS:对象实例+实例化变量+静态方法+非静态方法),当JVM在其所管理的内存区域的中无法通过根集合到达对象的时候就会将此对象作为垃圾对象实施回收。
Java所有的对象都有一个生命周期:创建、使用、不可视、不可到达、回收释放。先就从这个过程中结合昨天(2月9日)10点到今天(2月10日)凌晨2点看JTS代码的一些感受,对自己来个自我批评吧,同时也对自己的一些经验做一次总结(呵呵,在老爸家,无法上网,都不知道什么时候可以帖到Blog去)
先来看看我从JTS代码中感受到我需要立刻改正的错误,虽说理论上我明白道理,但是我就是没有去做过,也许这就是大师和大师兄的区别吧。。。汗自己一个。。。
我的代码中曾经出现过这样的代码:
List alist=uSvr.getUserinfoList();
for(int i=0;i<alist.size();i++){
//首先这里就有问题,这个是Crespo小兄弟给我提出的。最好使用for(int i=0 p=alist.size();i<p;i++),避免alist由于在循环体中发生变化时所带来的问题,而且即便alist没有发生变化,这么做也避免程序不断去执行size()方法所带来的资源损耗。赞一个先,因为Crespo所提到的思路在JTS代码中大师们都这么干,而且也绝对应该这么干
Object obj=new Object();
//这里问题大了。创建对象第一忌:不要在循环体中创建对象。这种做法会在内存中保存N份这个对象的引用//会浪费大量的内存空间(虽说内存便宜,可以进行硬件升级),同时JVM的GC机制会因为这些无谓的对象做大量//的回收工作,系统不慢都不行呀。。。好在这个问题很早以前就被我注意了,现在我的做法时在循环体外首先声明一个空对象,然后在循环体内new一个出来。
。。。 。。。
}
现在我写的代码中出现这种情况
public class Test{
Object obj=new Object();
public Test(){
obj=new Object();
//我的代码将Object对象初始化了两次。这个给内存带去的消耗绝对不比在循环体中创建对象来得小。创建对象第二忌:尽可能不要多次初始化对象。我这个问题也是恰好这两天在给一个公司做一个SP数据转发的开发中写过的。看JTS的大师们的代码在想流程的时候,偶然发现这个问题,但是那个汗呀。。真的。。汗流成河。。。
}
}
除了上面两大忌讳外,对象在创建过程中还需要注意到以下问题。不采用过深的集成关系;访问本地变量由于访问类变量。这两点很多书都有提到。
其实对象在使用阶段的优化,JTS的源码给我了一种提示,那就是java.lang.ref的有效利用。这个包实际上我今天是第一次看,从来都没有注意过这个包的使用问题,虽说它属于java.lang核心开发API中的范畴。知道看到JTS源码中出现这个代码我才“带着这玩意干什么的?”的疑问翻了一下API。
结合JTS源码中的写法以及所查阅的API的内容,说说我的感受吧。首先在java.lang.ref包中最值得我们多关注的是如何合理的使用SoftReference以及WeakReference。也就是我么那如何将我们的对象置为软引用和弱引用。在API上有软引用以及弱引用的详细解析,俺就不JY了,哪些人更不得了,都是火星派来介绍地球什么叫Java的外星人。很是先看看JTS代码中大师们的程序风格吧:
Object obj
=
new
Object();
使用obj对象过程
SoftReference softReference
=
new
SoftReference(obj);
//
将obj设置为软引用类型
obj
=
null
;
//
强制释放引用
//
obj对象再次使用时应该做的处理
if
(softReference
!=
null
)
{
obj
=
softReference.get();
}
else
{
obj
=
new
Object();
}
老实话,第一眼看到这样的代码我很惊讶,这tmd不是无聊吗?用了obj对象就直接仍到SoftReference(哪时候还以为是JTS自己的一种缓存机制)不说还要强制释放,第二次引用要么用get方法取,要么重新初始化,这不是没事找事情吗?唉,真是差距呀。。。OutOfMemory错误我一直都不在意,认为那是硬件问题,我不管。可是看看大师们,为了更加有效的节约资源所作的工作吧,这些东西增加了少量的程序工作量,可是对于系统资源使用效率以及程序执行效率的提升确实是很大的首先将对象设置为软引用,然后强制释放,将资源流出来给其他对象,当程序需要在此使用这对象得时候要么从softReference(其实也可以看作是简单缓存)中将原有对象还原,要么由于被GC回收了软引用,再重新初始化一次。。。。(所以说研究出Java的人都是火星上来的,居然考虑得如此周密)
弱引用方法河软引用方法一样。他们之间得区别在于弱应用能更快得被GC给回收,毕竟软应用只是再内存使用到达警戒水平的时候才会进行。结合JTS目标是处理空间数据以及空间拓扑,它处理的对象很多都是一种Map结构的对象,这种对象会占用大量的内存空间,所以我们很好理解JTS为什么做了这么多我们平时再代码中从来不注意的事情。但是这不代表因为我们的系统目标和JTS不一样,所以我们就可以不去做这些工作。
什么样的对象可以将其认定为不可视阶段呢?举个例子吧,在try{...}catch(Exception){...}代码中,如果在try的代码块中声明了一个obj,那么当整个 try{...}catch(Exception){...}代码段执行完毕以后这个obj实际上就已经属于不可视阶段了。在JTS源码中我看到很多这样的例子,大师们会在try代码块的最后多一句话:obj=null;实际上这种方式我也使用过,但是99%的时候都用所谓的项目时间紧张为借口忽略了。其实多这么一句话将obj对象置为空值可以快速的帮助JVM发现这个对象,并进行回收以释放资源。这里也有一部分代码是使用的SoftReference来处理,呵呵,大师的风格也不尽相同呀。
当然,即便没有这句话最终这个对象会被GC给回收,但是快速的释放资源就可以有效的提高现有资源的利用效率,这个难道真的不应该被我们这些使用高级编程语言进行开发的程序员所提倡吗?资源永远都是有限的,并非只是汇编或是做嵌入式开发的程序员才应该关注程序使用资源的问题的!改编周星星名言-“程序不是这样写的!资源不是这么用的!”
一个对象混到这份上就也该知足了,也该自觉一点轻轻走了算了,就算不走GC作为警察(户籍警察)也会对此对象实施强制消户了。。。一般情况下我还是不相信这个世界上有鬼存在的,那些怎么打也打不死的青铜圣斗士除外。。。。
其他方面据不完全统计的个人经验而言,在开发过程中做到1、不提前创建对象,什么时候用什么时候创建;2、数组创建时尽可能避免显示申请数据的内存空间;3、当对象占用资源大&对象数据稳定&对象生命周期长可能的使用静态变量;4、使用IDE编译代码的时候在程序的发布版本编译时去掉IDE默认的debug编译模式(Eclipse:窗口-首选项-java-编译器)、在需要同类大量对象的时候,使用对象池,数据库连接池即在次范围。。。 。。。同时没有事情的时候多多学习大师的代码,java开源社区有很多值得学习代码风格的源代码等待着我们呢:)
附上代码来解释一下堆内存和栈内存的区别
(阅读许可证:本实例代码未满18岁或家有女王者勿入,由本代码引起的一切纠纷、人身伤害以及法律问题需阅读者自行解决,原创作者
sinoly
不承担任何责任。一旦阅读本代码,就表示你已接受本许可所提出的所有观点)
public
class
我的老婆们
{
static
Vector 老婆s
=
new
Vector();
//
创建一个老婆的Vector序列
static
void
娶个老婆()
{
PLMM plmm
=
new
PLMM();
//
请首先new一个PLMM对象
老婆 wife
=
(老婆)plmm ;
//
不作此做出方法解释,涉及到太多问题
老婆s.AddElement(老婆);
//
本方法多次运行可实现了一夫多妻的宏伟目标,也是本人长期努力的方向
}
public
static
void
main(String[] args)
{
娶个老婆();
//
执行此方法可能会带来内存溢出,请慎重!
}
}
栈内存中存放的内容:main,娶个老婆()
堆内存中存放的内容:我的老婆们,我的老婆们.老婆s,Vector,plmm,老婆
NOTE:堆内存是在JVM启动的时候创建,堆内存分为新对象与老对象。对于新对象好像会分三个区域。当优先级最高的区域的堆栈满了以后JVM将会进行测试,测试内容是那些对象不可到达,不可到达的对象将会放入到老对象区域。同时JVM会将所有对象拷贝到另外两个区域中,然后经过一段时间依然没有引用的对象会进入老对象区域。对于老对象区域而言基本上就是等待被GC回收的对象了。(这些还是在大学时学.net时候的知识,直接换成java定义我想其中概念应该一样。唉,大学毕业到现在快5年了--真的老了,唉!看着周围83年甚至85年的同事,真的很伤自尊呀!)
ps:拜托各位看官千万不要把前一段代码和这个NOTE结合起来联想,会死人的!