`
wayfarer
  • 浏览: 294499 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论
阅读更多

Java GC有隐患:
例:在游戏当中经常有不同场景的切换,如从游戏逻辑退到主菜单逻辑,对游戏逻辑对象的态度很多人会选择忘记,而等待GC来收尸。乍看之下似乎并无不妥,GC会来善后。
实际上GC并非实时的,当从游戏逻辑切换到主菜单逻辑时,两个对象同时存在很可能造成outofmemory。所以j2me上所有垃圾必须手工释放:显式的将引用置空(例imgs = null);
java.lang.Runtime.freeMemory():返回当前的剩余内存数,将它适当的安放在代码中可以有效的监测内存使用状况。

Image img = null;
img = Image.createImage("/1.png");
img = Image.createImage("/2.png");

很多人会认为新的对象会把旧的对象冲掉并且释放内存。
但这其中有隐患:img = Image.createImage("/2.png");是先创建对象然后再进行赋值操作的,也就是说在这期间有两个对象同时存在,这就很可能会产生溢出;这样做也会妨碍GC及时工作。

较好的写法如下:
Image img = null;
img = Image.createImage("/1.png");
img = null;
img = Image.createImage("/2.png");

protected void paint(Graphics g) {
    for (int i = 0; i < 100; i++) {
        g.drawString("士兵报数: " + i, 50, 50, Graphics.LEFT|Graphics.TOP);
    }
}
"士兵报数: " + i 在paint()方法当中刷100遍显示在屏幕上以模拟士兵报数。但根据String机制(见csdn博客),该语句会生成100个String对象,paint()后又必须由GC释放这100个String
对象,用了双倍时间,容易发生内存溢出。依此类推在重复执行的方法里应尽量避免重复定义对象。

把所有对象的初始化放在构造函数里想必是再正当不过了,大多数人通常的做法也是把当前逻辑所要用到的资源通通初始化完毕。但是,很大一部份的内存溢出都是发生在构造函数中。
因为所有对象都在构造函数中初始化,势必造成了初始化时内存使用处于高峰期。解决方法:第一次使用时再进行初始化,格式如下:
if(img==null) {
    //初始化
}

对于地图/声音数组等资源应放在文件中,需要时再load进来。而不应该将这些资源放在代码中,否则这些资源会随着代码的运行而一起load,极容易outofmemory。

另外不要忘记close()方法:rms、网络连接、流、正确地停止线程。


根据J2ME虚拟机对程序编写的优化方式
1、关于虚拟机
目前无论是Java还是.net基本上采用的都是分代式GC和分支预测GC。因此目前这两个虚拟机的性能相差不是很大,因此对于程序的优化,基本上真对这两种技术来进行。
(1)分代式GC技术(目前客户端虚拟机GC技术的主流):增加对寿命短对象的收集,而减少对长寿命对象的收集。这里使用术语第一代和第二代的术语来描述。第一代是指程序新分配的内存,从统计数据来看,对象越年轻,被回收的可能性就越大,因此,对第一代的垃圾回收进行的频繁一些;而一旦进行了第一代的垃圾回收,未被回收的对象将会成为第二代对象,对于第二代对象的回收,由于它的生命期更长,因此在到达第二代对象空间阈值的时候并不会收集,这样就减少了回收的次数。防止垃圾回收带来的系统停顿。
(2)分支预测(JIT, Just In Time, 及时编译技术: JIT编译器在程序开始执行前把所有字节码翻译成本地机器码,然后再将翻译后的机器码放在CPU上运行。因为机器码比字节码运行的速度快很多,因为一个程序编译也许只有一次,但运行有很多次,这样牺牲了编译的时间来提高运行的速度):JIT技术在虚拟机中已经被证明能极大地加快程序速度,而目前基本上采用的是带有分支预测技术的JIT编译器,因此越背频繁执行的语句,就越有可能被JIT编译器编译,从而加快程序的速度。
2、关于J2ME的虚拟机
J2ME虚拟机最严重影响程序性能的就是进GC操作,一旦进行GC操作,程序的主线程一般会暂时挂起,以方便进行垃圾回收操作。在程序上的表现就是程序出现暂时的停顿,这将极大地影响用户的体验。因此必须尽量减少垃圾回收进行的次数,近最大努力控制垃圾回收的执行,尽量减少垃圾回收执行的时间。
J2ME虚拟机中JIT技术的应用,目前我没有发现有显著的文章来进行描述,由于JIT技术需要实时编译并且耗费内存较多,因此,我认为MIDP1.0中并没有采用JIT技术。而MIDP2.0中有可能采用JIT技术,但是J2ME的JIT技术与J2SE的性能应该不是一个数量级上的,由于CPU性能有限,注定无法做复杂的JIT编译,而且由于JIT耗费内存,也注定无法对代码进行大规模的JIT编译。
3、J2ME(MIDP)对于这这两种技术的优化
(1)分代式GC的核心思想:减少GC次数,增加GC回收量。
①减少GC次数:分代式GC中第一代的GC进行比较频繁,因此GC优化首先针对第一代GC,就是说应该避免在Method中产生新的对象。例如:
在滚屏游戏的设计中,需要进行矩形的判断,这里采用的方式都是在类中定义矩形,而不是在Method中定义矩形,在碰撞判断中调用的矩形都是在类的Constructor中生成的,并不在Method中生成。
这是因为需要频繁调用碰撞检测,因此如果在Method中定义矩形将会增加GC的次数。因此经典程序中有很多看起来不应当定义为类成员的变量,都被定义为类成员,这极大地减少了GC的次数。
而对于二代的垃圾回收,就是在程序中不要随时把无用的资源置为null,这样可能会激发二代回收。
②增加GC回收量:对于类对象引用,不要无故置为null。例如:在过关游戏中,在某一关内不要无故置为null,而应当在过关时把上关卡中不再使用的资源全部置为null,然后显示调用垃圾回收,再载入新的资源,这个时候新的资源能够顺利栽入,旧的资源也能够顺利回收。而由于关卡切换,进行稍微的等待也是可行的,不会影响用户体验。
(2)JIT编译优化的核心思想:用户模拟JIT编译器来进行程序编译的优化,尤其对于频繁执行的地方要尽量优化。例如:对于程序中的分支判断,要把经常调用的分支写到判断前面。

GC不是根据周期来调用的,而是根据内存消耗情况来调用。如果内存消耗到一定值(一般是在内存快要用完的时候)系统就会自动调用GC,但这并不代表会马上释放内存。 
 
在你释放了资源(把引用置为null)之后最好调用一下System.gc(),但System.gc()只是一种请求,并不一定会执行。同时也要注意GC本身也是消耗内存的(除了释放资源,GC还要进行类似"磁盘碎片整理"的内存整理,代价还是比较大的),所以System.gc()不能太频繁。
 
优化代码
①最大限度的重用对象, 
②及时置空不再需要的对象(如果不置空,在生成第二个对象之时,很可能会占用双份的空间) 
③在频繁生成销毁对象的过程中适当调用System.gc()以便及时释放资源(需进行测试找到最佳调用点) 
④在使用循环时,最好不要采用方法或是经过一系列计算后所得的结果为循环条件,否则,每经过一次循环,都将会调用一次该方法或是经过一系列计算。
⑤循环变量如果递增或递减的次数很频繁,最好能采用前置操作符 
⑥如果可以,尽量用数组替代对象, 


我的工作是一个手机帐款支付平台,里面有n多界面,我把他们分别封装到类中,但是我是新手我不太清楚当我生成了n多类,当某些界面类不用之后是否还会占用内存空间,还是会被垃圾回收,还是必定要在不用的某些界面类后需要用a=null;来释放空间,大家多提意见
呵呵,kvm的gc是最没有用的
手机程序,我唯一的忠告就是只用一个类好,高级界面,低级界面都只用一个好
千万不要搞很多的类,否则恐怕很难看的...千万别指望垃圾回收,自己省着点...

No guarentee the memory will be released even you put a = null and call System.gc();
You'd better use as little memory (less graphics, less classes, less instance, less vectors, etc. ) as you can!

请使用以下语句打印出你的 内存用量
System.gc();
System.out.println(Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory());
不要分很多的类,常识告诉我们每一个类,每一个实例都是一个开销

对于可以从网络下载的资源,尽量通过网络下载数据,这样也可以减少jar的大小。
对于大量运算需要保持很多数据的结果,最后通过RMS作为缓冲来实现,比如你保存一些计算结果到rms中,特别是一些字符串的操作,有些数据可能有几十k,你保存到rms中,就相当于节省了几十k的数据了。
对于用字符串来保存状态的,可以用byte来保存那就更好了。
避免采用大对象。比如加载超过几十k,甚至是上百k的字符串,或者是图片
for (int i=0; i<1000000;i++) {
    s+="abc";
}
这样很容易出现问题。
因为s是一块连续的内存空间,搞不好就内存溢出了。


J2ME中最常使用的资源无非是图片和声音。为了提高游戏的运行速度,我们通常把它们声明为全局变量,又由于手机的内存的关系,我们不能把这些资源同时加载进内存,所以我们会在使用的时候导入资源(创建对象),而把暂时不需要的资源(图片或声音)对象赋值为null(当然这是我的做法,不知道是否和大家一样)。在这期间如果处理不好,就会有些资源对象的在内存中没有清除干净,就会造成内存泄漏,结果就是可用内存越来越小。下面我就举例说明。

  1.图片资源

  要点:要为每一个图片资源声明一个对象变量

  假设在一个程序声明了2个图片对象:

        Image pic1,pic2;
  如果在某一时刻要使用pic1和pic2则:
        if(pic1==null)pic1=Image.createImage("/1.png");
        if(pic2==null)pic2=Image.createImage("/2.png");
  当这些图片用完后,就应当:
        pic1=null;
        pic2=null;
  如果需要使用另外两个图片3.png和4.png,则最好另声明两个变量对象pic3,pic4来导入它们,不要使用pic1,pic2变量来导入这两个图片,如:
        if(pic1==null)pic1=Image.createImage("/3.png");//不要这样做
        if(pic2==null)pic2=Image.createImage("/4.png");//不要这样做
  因为这样做会影响垃圾收集器对pic1,pic2对象的回收工作,从而造成内存回收不干净。

  2.声音资源(不同的平台对声音的处理方式不同)
 
  要点:和图片资源一样,另外在回收声音资源一定要停止声音的播放;还有在每次开始播放声音前,也要先判断一下声音的状态(简单点,直接调用stop,先让声音停下来,然后再播放),如果不先停止声音的播放,再调用其运行播放,可能会生成无法回收的对象(这是本人猜测的)造成内存的泄漏。

  如:
  假设sound为声音对象
  释放sound资源:
         if(sound!=null)
         {
              sound.stop();
              sound=null;
         }
  这样sound的资源就可以安全的被垃圾回收器收回了
  播放声音资源:
         if(sound!=null)
         {
              sound.stop();
              sound.start();
         }


内存的主要使用看来是在每次重画的时候产生的,这样就要减少每次重画的内容,我采取的方式是把所有固定要重画的东西用双缓冲的方式画到一张图片上,这样只有初始化的时候才去遍历数组,经过一次的遍历把所有内容画到一张图片上,然后每一次的重画都是在重画一张图片。经过这样的处理性能上有了很大的提升。高兴还为时尚早,nokia的低端机器没有问题了,结果在moto的机器上出了问题,根本就不能初始化,也就是说创建那张图片的时候就应用程序错误了。继续查找原因,结果发现是因为moto的机器不支持创建一张那么大图片。也就是说你创建一张大图的时候,在moto的机器上根本就不能申请到内存。找到原因后,把大图分割为两个比较小的图,ok没有问题了。当然至于moto支持创建多大的图片可能每种机器不同吧,只能在需要的时候自己测试了。对于浏览器,一张分割为两张需要做些代码的处理。实际上为了性能的更加优化可以把一张大图分割为多张小图,每张小图的大小可以根据屏幕的几倍大小确定也可以根据机型固定。初始化的时候也可以先初始化一部分图。因人而异。
在细节的代码书写上多注意些也有利于程序性能的提升。关于细节上觉得以下几个方面对程序性能的提升很有帮助。
首先系统垃圾回收的利用:关于堆内存(heap)与栈内存(stack)我们知道,heap存放的是对象实例与变量;而stack存放的是静态方法。堆内存在JVM启动的时候被创建,堆内存中所存储的对象可以被JVM自动回收。在这里,要手动把不用对象置为null,特别是较大的对象,如果不用一定要记得置为空。比如说较大的数组,vector或者是image对象。(切忌)在这里,浏览器中页面图片的读取我是采用的是后台读取,即先显示文字部分,而后后台读取页面中的图片,读取完成后再一起重新显示。重新显示的时候要重新构建那个双缓冲图片,而我当时就忘记了把原来创建的那个双缓冲图片置为null了,走了很多弯路才解决问题。所以要切忌至少把大的对象置空,不要指望垃圾回收。
其次是static的使用:静态变量在程序运行期间内存空间对所有该类的对象实例而言是共享的,即只在内存中保存一份拷贝,这样节约了不比要的内存开销。但是static生命周期较长,而且不容易被垃圾回收机制所回收,所以要合理运用,不要适得其反。建议在全部具备下列条件的情况下尽量使用静态变量:
1),变量所包含的对象体积较大,占用内存较多。
2),变量所包含的对象生命周期较长。
3),变量所包含的对象数据稳定。
4),该类的对象实例有对该变量所包含的对象的共享需求。
在我的程序中对静态变量的优化后,使程序占用内存量至少提升了5k-10k。所以也不容忽视。
还有就是String类相关的东西:1。字符串累加的时候一定要用StringBuffer的append方法,不要使用+操作符连接两个字符串。差别很大。而且在循环或某些重复执行的动作中不要去创建String对象,因为String对象是要用StringBuffer对象来处理的,一个String对象应该是产生了3个对象(大概是这样:))。
2,字符串length()方法来取得字符串长度的时候不要把length放到循环中,可以在循环外面对其取值。(包括vector的size方法)。特别是循环次数多的时候,尽量把length放到循环外面。
int size = xmlVector.size();
for (int i = 2; i < size; i++) {
    。。。
}

http://whitesock.iteye.com/blog/162704

分享到:
评论
1 楼 woshild415 2010-07-02  
好东西,先藏了~很有参考价值~

相关推荐

Global site tag (gtag.js) - Google Analytics