Java解读内存,优化编程
1.别用new Boolean
在很多场景中Boolean类型是必须的,比如JDBC中boolean类型的set与get都是通过Boolean封装传递的,大部分ORM也是用Boolean来封装boolean类型的,比如:
ps.setBoolean("isClosed",new Boolean(true));
ps.setBoolean("isClosed",new Boolean(isClosed));
ps.setBoolean("isClosed",new Boolean(i==3));
通常这些系统中构造的Boolean实例的个数是相当多的,所以系统中充满了大量Boolean实例小对象,这是相当消耗内存的。Boolean类实际上只要两个实例就够了,一个true的实例,一个false的实例。
Boolean类提供两了个静态变量:
public static final Boolean TRUE = new Boolean(true);
public static final Boolean FALSE = new Boolean(false);
需要的时候只要取这两个变量就可以了,比如:
ps.setBoolean("isClosed",Boolean.TRUE);
那么象2、3句那样要根据一个boolean变量来创建一个Boolean怎么办呢?可以使用Boolean提供的静态方法: Boolean.valueOf() 比如:
ps.setBoolean("isClosed",Boolean.valueOf(isClosed));
ps.setBoolean("isClosed",Boolean.valueOf(i==3));
因为valueOf的内部实现是:return (b ? TRUE : FALSE);
所以可以节省大量内存。相信如果java规范直接把Boolean的构造函数规定成private,就再也不会出现这种情况了。
2.别用new Integer
和Boolean类似,java开发中使用Integer封装int的场合也非常多,并且通常用int表示的数值通常都非常小。sun sdk 中对Integer的实例化进行了优化,Integer类缓存了-128到127这256个状态的Integer,如果使用 Integer.valueOf(int i),传入的int范围正好在此内,就返回静态实例。这样如果我们使用Integer.valueOf代替new Integer的话也将大大降低内存的占用。如果您的系统要在不同的SDK(比如ibm SDK)中使用的话,那么可以自己做了工具类封装一下,比如IntegerUtils.valueOf(),这样就可以在任何SDK中都可以使用这种特性。
3.用StringBuffer代替字符串相加
这个我就不多讲了,因为已经被人讲过N次了。我只想将一个不是笑话的笑话,我在看国内某“著名”java开发的web系统的源码中,竟然发现其中大量的使用字符串相加,一个拼装SQL语句的方法中竟然最多构造了将近100个string实例。无语中!
4.过滥使用哈希表
有一定开发经验的开发人员经常会使用hash表(hash表在jdk中的一个实现就是HashMap)来缓存一些数据,从而提高系统的运行速度。比如使用HashMap缓存一些物料信息、人员信息等基础资料,这在提高系统速度的同时也加大了系统的内存占用,特别是当缓存的资料比较多的时候。其实我们可以使用
操作系统中的缓存的概念来解决这个问题,也就是给被缓存的分配一个一定大小的缓存容器,按照一定的算法淘汰不需要继续缓存的对象,这样一方面会因为进行了对象缓存而提高了系统的运行效率,同时由于缓存容器不是无限制扩大,从而也减少了系统的内存占用。现在有很多开源的缓存实现项目,比如 ehcache、oscache等,这些项目都实现了FIFO、MRU等常见的缓存算法。
5.避免过深的类层次结构和过深的方法调用
因为这两者都是非常占用内存的(特别是方法调用更是堆栈空间的消耗大户。
6.变量只有在用到它的时候才定义和实例化
7.尽量避免使用static变量,类内私有常量可以用final来代替
8..对频繁使用的对象采用对象池技术
内存会溢出肯定和代码逃不了关系,99.99%学java的人都知道垃圾回收器是 java的一大优点并据此来嘲笑C++。显然这个特性为代码编写者省了不少事,但这个特性却带来了不少隐患。举个例子在游戏当中经常有不同场景的切换,如从游戏逻辑退到主菜单逻辑,对游戏逻辑对象的态度很多人会选择忘记等待垃圾回收器来收尸。乍看之下似乎并无不妥垃圾回收器会来善后。实际上垃圾回收器并非实时的,它不像C++的Delete语句马上释放不用的内存。当从游戏逻辑切换到主菜单逻辑这时两个对象同时存在很可能这时内存就不够用了。读到这里很多人会发现实际上垃圾回收器在j2me上并不怎么好用,从一个角度上来讲在j2me上所有垃圾必须由手工释放,除简单类型以外所有对象都必须显式地置空例如 imgs=null; 实际上java提供了一个不错的工具用来查找内存溢出,java.lang.Runtime.freeMemory() 。它可以返回当前的剩余内存数,将它适当的安放在代码中可以有效的监测内存使用状况。很大一部份的j2me程序员之前都是从事pc软件开发工作,充裕的内存掩盖了许多写代码的不良习惯。如下所示:
//a 不为空
a=new Logic();
很多人可能对此有异议,他们会认为新的对象会把旧的对象冲掉并且释放内存。这里面包含两个问题:1. 该段代码是先创建对象然后再进行赋值操作的,也就是说在这期间有两个对象同时存在这就很可能会产生溢出。2. 这样做也会妨碍垃圾回收器的工作
较好的写法如下:
a=null;
a=new Logic();
虽然麻烦了点但在j2me中还是必要的。接着看下例。
drawString("游戏时间:" + time ,50,50,Graphics.LEFT|Graphics.TOP);
"游戏时间:" + time 很完美在paint()方法当中每次都被刷一遍显示在屏幕上。危机往往隐藏在美丽的外表,该语句会引起新的内存重新分配来存储 "游戏时间:" + time 而显示完以后又必须由垃圾回收器释放,用了双倍时间,并且容易发生内存溢出。依此类推在重复执行的方法里应尽量避免重复定义对象。与paint()方法类似在循环里也有类似的情况存在。
把所有对象的初始化放在构造函数里想必是再正当不过了,大多数人通常的做法是把当前逻辑所要用到的资源通通初始化完毕。
很大一部份的内存溢出都是发生在构造函数中。内存使用的高峰期都是在构造函数中所以避开这个高峰能有效的防止溢出。建议最好的办法是第一次使用时初始化。如下所示
if (img==null){
//初始化
}
现在做游戏很多时候都需要地图数组,声音数组,还有一些其它资源这些资源很多可以放在代码中也有的可以放在文件当中。
强烈建议将这些资源放在文件中需要时在load进来。这些资源文件如果放在代码中则会占用不小的代码段空间,而代码一般是程序一运行就装载到内存当中。
除上面列举的方法外还有一些大家所熟知的顺便一提, 比如关闭没用的rms ,关闭没用的网络连接,关闭没用的流。正确地停止线程。良好的程序架构减少代码偶合性也是一个不错的方法,无论在代码调式,内存释放都可以做到非常清析。
二.图片优化
j2me的内存杀手无疑非图片莫属,一张3k的图片可以占用20多k的内存不信大家把load前后的内存剩余打印出来对比看看。所以防止内存溢出最直接的办法就是从图片入手。
1.图片压缩: 多数人马上会想到这个办法。不错这个办法是最有效的。在photoshop里图片制作完成后不要选择 "存储为",而是选择 "存储为 web 所用格式" 可以根据里面的选项进行压缩,特别是颜色这一项越小越好不过相应的图像会有所失真。不要认为这样就完了。
实际上该图片还可以再次压缩,在网上有许多类似的工具。推荐一款可以压缩png格式的软件 xat.com Image Optimizer 效果不错。经常都有 70% 的压缩率且图像不会失真。
假如你有多张规格一样的图片,那么建议你把它做成一张长条图片。有两个原因:
1、 这样节省存储空间和内存空间。大家可做个试验将10张图片的内容放在一张当中对比看看文件大小有没有变化。
2 、10张图片需要10个image 对象需要进行10次io操作浪费时间不说还浪费内存。当笔者发现这个好处时兴奋地把所有图片都存成一张,吱地一声内存又溢出了...原因想必大家也知道!!图片太大了不要把不同界面的图片整合在一起否则经常会得不偿失。
作图时还有一些细节需要注意,颜色数量,分辩率,图像模式(最好是索引颜色),画布大小都会影响到图片大小。
三.工具优化
谁都知道混淆器是用来保护代码的以加大反编译的难度(个人认为这是在嘲笑程序员的智商)。实际上用它来优化程序也是不错的选择,至少有两点好处:
1、 压缩程序大小。一个60k的程序经常可以压掉10k左右。10k的空间对于写低端手机的程序员简直是雪中送碳,多少超过64k限制的游戏都受过它的恩惠;
2、节省内存空间。用脚去想也想得出来代码少了内存里的代码段自然就短了。