(本文摘自互联网)
我想做过J2ME的人,特别是像我这样做手机游戏的,肯定会对OutOfMemoryError这个异常深恶痛绝,尤其是在老40这样变态的机型上,甚至对这个异常都产生了恐惧。
首先了解一下分析内存占用的方法,一般有两种:模拟器自带工具和Runtime类方法。
模拟器自带工具:WTK貌似带了一个Memory Monitor,而且许多学者人士也夸夸其谈他的使用方法,但我不知道有多少人真正在用。就我对他的了解,首先运行他你的程序会慢的一塌糊涂,这对游戏开发者来说简直是无法忍受的。但我出于研究目的仍然让他跑了半个小时才发现原来他根本无法显示正确的内存占用量,我载入一张很大的图片后他的内存线好像只出现了微微的波动又停留在原位,呵,看来的确是拿出来秀的。我一般使用的是7210模拟器自带的内存监视器,模拟的很准,但唯一的缺点是内存太少,才200K。我也见某些人使用3220的模拟器监视内存,好像内存稍微大一点,我还没来得及尝试就再也不用为老40写程序了,庆幸。
Runtime类方法:我经常用这个语句System.out.println(Runtime.getRuntime().freeMemory());后来集成进了我的引擎,他能够显示当前剩余内存。不记得有多少次我用它在老40上来寻找内存占用峰值。
了解了分析内存的方法,来看看内存占用的罪魁祸首:程序和资源。
程序:类会被编译成class字节码文件随MIDlet的启动加载进内存,而且是一次性全部加入。也就是说MIDlet里类个数越多、单个类程序越长、类内字符串常量及数据越多,编译后的class文件就越大,载入后占用的内存也越多。我经常在MIDlet类的构造函数里用Runtime方法来查看MIDlet启动后整个程序占用内存量。
优化方法:
1.某些同志将MIDlet程序写成两个类来减少内存占用量,但是以牺牲Java的OOP特性为代价的。在程序比较大时这种弊端将尤为显见。而且CoCoMo曾经遇到过单个类过大,载入时间过长而违反百宝箱有关Logo 6秒时间限制的情形。因而我现在的程序加带引擎一般都是6-7个类。
2.尽量编写优雅的代码,减少函数数量,在程序发布时去掉try catch,最大限度的减少程序行数,这一般都是在老40上没有办法的办法,现在CoCoMo已经不靠这个来省内存了。
3.将数据及字符串写进文件,在用时方载入内存,不用时设为null。
4.I/O操作getClass().getResourceAsStream(file);、数据库操做RecordStore.openRecordStore(name, true);、声音创建Manager.createPlayer();、图像创建Image.createImage(file);会在短时间内占用大量内存且过后释放,如果MIDlet程序内存剩余量不足则会在这些函数频繁调用时发生内存溢出,产生所谓的内存峰值,尤其在老40上比较普遍。当你再次与讨厌的OutOfMemoryError碰面时,多用Runtime查找内存峰值发生位置并尽量将这些语句分开调用,并灵活运用System.gc()来及时回收。
资源:
图片:是占用内存的大户,尤其是手机游戏图片资源众多。对图片资源在内存中占用量的计算成为J2ME游戏开发者的经常性工作,CoCoMo来解释一下如何计算图片在内存中的占用量:内存占用量=宽*高*像素字节数,其中像素字节数因机型而异。例如一张64*64的图片在7210上的内存占用量=64*64*1.5=6144(字节)=6K、在S60上的内存占用量=64*64*2=8192(字节)=8K。像素字节数因机型而异,例如7210是4096色机型,也就是说用12位来表示一个像素,所以乘上1.5,而S60是65536色的机型,用16位来表示一个像素,所以乘上2。
优化方法:
有些人认为压缩图片可以节省内存,这种想法是错误的。根据上面的解释图片载入内存后只和宽高有关系,和图片数据量大小没有任何关系,压缩图片只能减少jar大小而不能减少内存占用量。
1.静态法:减小图片大小,宽高小了结果当然小了。根据这个思路出现了动画编辑器之类的工具,像gameloft的波斯王子,人物被分割后使人*体的部位可以重用,各部位紧凑放置都是为了较少图片大小,充分利用图片中的每一寸空间。
2.动态法:减少同一时刻载入内存的图片数。CoCoMo曾经在火影武士项目中遇到过这种情况,当时有6种怪物,如果同时载入内存在老40上肯定爆掉了,但是每关只出现两到三种怪物,所以每一关只需要载入该关出现的怪物图片即可。现在想起来当时做这个项目在老40上溢出频出,真把我搞死了。
声音:声音也是比较耗用内存的资源,声音中音轨所占的byte会转化成字节流被载入到内存中。因而减少音轨所占byte即可减少内存耗用量。目前gameloft的做法是用声音转化工具将mid转化为ott,然后变为ByteArrayInputStream字节流来创建Player。
MOTO 小C系列
常见机型:C650
手机屏幕:128*128
游戏屏幕:128*116
非全屏屏幕:128*100
JAVA联网:CMWAP需要代理
字体大小:17*17,一行约7个中文字,内置一种字体,任何字体选项均为默认字体
MIDP:2.0
CLDC:1.0
JAVA单个容量限制:标准100K(实际无限制,小于手机本身内存)
JAVA堆栈容量:800K
特别1:支持MOTO FUNLIGHT API
特别2:支持MOTO 3D API
特别3:开启摄像头、内部文件访问权限等需要授权
特别4:C650机型:setMediaTime该机型不支持,playerUpdate传过来的player是副本,所以应该用equal而不是==。
MOTO C550/C370/E380系列
手机屏幕:96*65
游戏屏幕:96*64
非全屏屏幕:96*64
JAVA联网:CMWAP需要代理
字体大小:不明,可参考MOTO C
MIDP:1.0
CLDC:1.0
JAVA单个容量限制:100K标准(实际不明)
JAVA堆栈容量:512K
特别1:支持MOTO GAME API,可实现相当于MIDP2.0的功能。
MOTO E398、V600系列
手机屏幕:176*220
游戏屏幕:176*204
非全屏屏幕:176*182
JAVA联网:CMWAP需要代理
字体大小:17*17 一行约10个中文字,内置一种字体,任何字体选项均为默认字体。
MIDP:2.0
CLDC:1.0
JAVA单个容量限制:无限制,小于手机本身内存
JAVA堆栈容量:800K
特别1:E398支持MOTO FUNLIGHT API
特别2:支持MOTO 3D API
特别3:开启摄像头、内部文件访问权限等需要授权
特别4:V300系列,键值正好与E398互为相反数,可以归为同一个版本,取键值的判断其绝对值。
MOTO C975/C980/V980系列
手机屏幕:176*220
游戏屏幕:176*204
非全屏屏幕:176*182
JAVA联网:CMWAP需要代理
字体大小:内置三种字体
MIDP:2.0
CLDC:1.1
JAVA单个容量限制:无限制,小于手机本身内存
JAVA堆栈容量:1.5M
特别1:支持标准 3D API
特别2:支持蓝牙
MOTO E680系列
手机屏幕:240*320
游戏屏幕:240*320
JAVA联网:CMWAP需要代理、直联
字体大小:内置三种字体
MIDP:2.0
CLDC:1.1
JAVA单个容量限制:无限制,小于手机本身内存
JAVA堆栈容量:1.5M(预想)
特别1:支持标准 3D API
特别2:支持蓝牙
特别3:E680上5个空格占一个字符宽度
特别4:E680加载代码是分段加载,尽量避免写超长的方法,否则可能会有延迟。
NOKIA S40 V1
手机屏幕:128*128
游戏屏幕:128*128(FullCanvas)
非全屏屏幕:128*???
JAVA联网:CMWAP直接联
字体大小:三种字体大小,最小字体12*12,一行约10个中文字
MIDP:1.0
CLDC:1.0
JAVA单个容量限制:64KB
JAVA堆栈容量:不明
特别1:按键会有延迟,中断后原线程还会在后台继续运行直到调用repaint,内存开销不当会死机
NOKIA S40 V2
手机屏幕:128*128
游戏屏幕:128*128(FullCanvas 或 setfullscreenmode(ture))
非全屏屏幕:128*???
JAVA联网:CMWAP直接联
字体大小:三种字体大小,最小字体12*12,一行约10个中文字
MIDP:2.0
CLDC:1.0
JAVA单个容量限制:110~128KB不等
JAVA堆栈容量:不明
NOKIA 6230i
手机屏幕:128*128
游戏屏幕:208*208(这里指分辨率)
JAVA联网:CMWAP直接联
字体大小:不明,可参考S40
MIDP:2.0
CLDC:1.0
JAVA单个容量限制:不明
JAVA堆栈容量:不明
NOKIA S60 MIDP1.0
手机屏幕:176*208
游戏屏幕:176*208(FullCanvas)
非全屏屏幕:176*144
JAVA联网:CMWAP直接联
字体大小:不明,一行可显示约12-13个中文字
MIDP:1.0 (但是可增加多媒体播放API)
CLDC:1.0
JAVA单个容量限制:不明
JAVA堆栈容量:不明
特别1:3650机型:setClip和drawRegion搭配不能正确设置裁减框。
特别2:3650机型:频繁I/O操作会死机,应尽量在游戏初始化时将数据一次读入。
特别3:N-Gage机型:在背景缓冲上setClip和drawRegion搭配完全不能设置裁减框声音播放有问题,建
特别4:N-Gage机型:声音播放有问题,建议在I/O操作等跟系统底层有关调用之后再播放声音
NOKIA S60 MIDP2.0
手机屏幕:176*208
游戏屏幕:176*208(FullCanvas或 setfullscreenmode(ture))
非全屏屏幕:176*144
JAVA联网:CMWAP直接联
字体大小:不明,一行可显示约12-13个中文字
MIDP:2.0
CLDC:1.0
JAVA单个容量限制:不明
JAVA堆栈容量:不明
特别1:6600机型:调用readFully不能按指定字节数读取,readByte代替。
特别2:6600机型:setClip和drawRegion搭配在欧版6600上不能正确设置裁减框,导致绘图错误
特别3:7610机型:drawRegion在这个机型上会拖慢速度,建议使用Nokia UI API上的drawImage。
特别4:7610机型:绘图函数调用不当会当机。
特别5:6681机型:频繁I/O操作会死机,应尽量在游戏初始化时将数据一次读入。
特别6:6681机型:使用2.0的drawRegion会造成内存泄露,尽量减少使用翻转,尤其是画地图时应尽量使用1.0的drawImage来实现
索爱K700C
手机屏幕:176*220
游戏屏幕:176*220(setfullscreenmode(true))
游戏屏幕:176*208(com.nokia.mid.ui.FullCanvas)
非全屏屏幕:176*176(setfullscreenmode(false))
JAVA联网:CMWAP需要代理
字体大小:不明,一行中文字数约10个,内置一种字体,任何字体选项均为默认字体。
MIDP:2.0
CLDC:1.1
JAVA单个容量限制:???
JAVA堆栈容量:512K(实际使用中感觉不止)
特别1:支持NOKIA UI API,但是drawpixels(),getpixels()这2个表现差劲不能使用
特别2:支持标准3D API
特别3:单个类文件不能超过70K(JAR包压缩后的大小),否则无法加载
波导S689
手机屏幕:128*160
游戏屏幕:128*144
非全屏屏幕:128*128(估计)
JAVA联网:CMWAP需要代理
字体大小:不明, 一行约8个中文字,内置一种字体
MIDP:2.0
CLDC:1.0
JAVA单个容量限制:200K
JAVA堆栈容量:512K
阿尔卡特 OT556/557
手机屏幕:128*160
游戏屏幕:128*160
非全屏屏幕:128*129
JAVA联网:CMWAP需要代理
字体大小:14*14,一行中文字个数约8个,内置一种字体font(0,0,0)
MIDP:2.0
CLDC:1.0
JAVA单个容量限制:256k
JAVA堆栈容量:512K
三星X108/X608
手机屏幕:128*128
游戏屏幕:128*128(全屏补丁实现)
非全屏屏幕:128*110
JAVA联网:CMWAP需要代理
字体大小:不明,一行中文字个数不明,内置一种字体font(0,0,0)
MIDP:1.0
CLDC:1.0
JAVA单个容量限制:???
JAVA堆栈容量:不明
自己总结的经验:
1、最占内存的是图片,所以要优先考虑优化图片,能不用就不用吧;J2ME的内存杀手无疑非图片莫属,一张3k的图片可以占用20 多k 的内存。所以防止内存溢出最直接的 办法就是从图片入手。图片压缩:在photoshop里图片制作完成后不要选择“存储为”,而是选择“存储为web所用格式”。可以根据里面的选项进行压缩,特别是颜色这一项越小越好,不过相应的图像会有所失真。实际上该图片还可以再次压缩,在网上有许多类似的工具。例如可以压缩png格式的软件ImageOptimizer。它可以进行70%的压缩率且图像不会失真。
2、很大一部份的j2me程序员之前都是从事pc软件开发工作,充裕的内存掩盖了许多写代码的不良习惯。如下所示:
/ /a 不为空
a=newLogic();
这样的代码要注意,尤其是图片很容易就内存泄露了,大家可以试试弄几张比较大的图 连续赋值几次就挂了。很多人可能对此有异议,他们会认为新的对象会把旧的对象 冲掉并且释放内存。这里面包含两个问题:(1).该段代码是先创建对象然后再进行赋值操作的,也就是说在这期间有两个对象同时存在,这就很可能会产生溢出。
(2).这样做也会妨碍垃圾回收器的工作。
较好的写法如下:
a=null;
a=newLogic();
虽然麻烦了点,但在j2me中还是必要的。
3、g,drawString(("游戏时间:"+time,50,50,Graphics.LEFT|Graphics.TOP);
(1) 在执行+的时候系统会重新创建一个新的str 对象,尽量用StringBuffer代替string,string很容易产生垃圾;
(2)字符串("游戏时间:"+time)在paint()方法当中每次都被刷一遍显示在屏幕上。但是,该语句会引起新的内存重新分配来存储该字符串,而显示完以后又必须由 垃圾回收器释放,用了双倍时间,并且容易发生内存溢出。依此类推在重复执行的方法里应尽量避免重复定义对象。与paint()方法类似在循环里也有类似的情况存在。
4、尽量用数组代替vector(),少用二维数组,二维数组访问时间是一维的2倍,如果能用一维的代替就把它换成一维的数组吧,少用数组即使是一维的数值也没有直接变 量访问速度快。大部分手机开发时都需要数组,试验证明,将这些资源放在文件中需要时在读取进来可以很好节省内存。这些资源文件如果放在代码中则会占用很大的代码段空间,而代码一般是程序一运行就装载到内存当中。
5、不要在循环里面创建对象 ,可以先创建对象再在循环里面初始化
6、异常处理很占资源,如果可以尽量把异常处理放在一块
7、paint()方法里面不要放太多复杂的计算,否则在性能比较低的机器很容易挂掉,而且也很影响屏幕的刷新速度
8、图片处理很费cpu 和资源,如果能够让美工做一些翻转、放大、缩小的图片代替,就不要用算法了
9、关于自定义字体,透明效果,多媒体处理...比较占资源的工作,如果不是针对高端手机或有针对性的项目,就不要实现了,低端手机根本不行
10、关闭没用的rms、网络连接、流。正确地停止线程。良好的程序架构减少代码偶合性也是一种很好的方法,无论在代码调式,内存释放都可以做到非常清晰。
11、混淆器是用来保护代码的,以加大反编译的难度。实际上用它来优化程序也是不错的选择,好处有两点:(1)、压缩程序大小。一个60k的程序经常可以压掉10k左右。10k的空间对于写低端手机的程序员简直是雪中送炭;(2)、节省内存空间。
12、把所有对象的初始化放在构造函数里想必是再正当不过了,大多数人通常的做法是把当前逻辑所要用到的资源通通初始化完毕。很大一部份的内存溢出都是发生在构 造函数中。内存使用的高峰期都是在构造函数中所以避开这个高峰能有效的防止溢出。建议最好的办法是第一次使用时初始化。如下所示
if (img==null){
//初始化
}
13、在paint中加上了System.gc();但不要频繁调用System.gc() ,保证它只用一次。
最后、还有就是也是最重要的 代码 算法的优化
long freeMemory=Runtime.getRuntime().freeMemory();//获得手机剩余堆栈内存
long totalMemory=Runtime.getRuntime().totalMemory();//获得手机总堆栈内存 智能机总堆栈内存是动态分配的.