`
yeminping
  • 浏览: 180570 次
  • 性别: Icon_minigender_1
  • 来自: 福州
社区版块
存档分类
最新评论

内存控制效率优化的启示

    博客分类:
  • JAVA
阅读更多

4 内存控制效率优化的启示

内存控制效率优化说起来简单做起来难,真正能做到优化,必须从点滴做起,并利用有效的手段加以应用。现在就看看对于控制内存方面都有哪些启示吧!

4.1 启示1:String和StringBuffer的不同之处

相信大家都知道String和StringBuffer之间是有区别的,但究竟它们之间的区别在哪里下面就在本小节中一探究竟,看看能给我们些什么启示。还是刚才那个程序,把它改一改,将本程序中的String进行无限次的累加,看看什么时候抛出内存超限的异常,程序如下所示。

public class MemoryTest{

public static void main(String args[]){

   String s="abcdefghijklmnop";

   System.out.print("当前虚拟机最大可用内存为:");

   System.out.println(Runtime.getRuntime().maxMemory()/1024/1024+"M");

   System.out.print("环前,虚拟机已占用内存:");

   System.out.println(Runtime.getRuntime().totalMemory()/1024/1024+"M");

   int count = 0;

   while(true){

     try{

    s+=s;

    count++;

     }

     catch(Error o){

    System.out.println("环次数:"+count);

    System.out.println("String实际字节数:"+s.length()/1024/1024+"M");

    System.out.print("环后,已占用内存:");

    System.out.println(Runtime.getRuntime().totalMemory()/1024/        1024+"M");

    System.out.println("Catch到的错误:"+o);

    break;

     }

   }

   }

}

程序运行后,果然不一会儿的功夫就报出了异常,如图 11-3所示。

图 11-3

可以注意到,在String的实际字节数只有8MB的情况下,环后已占内存数竟然已达到了63.56MB。这说明,String这个对象的实际占用内存数量与其自身的字节数不相符。于是,在环19次的时候就已报OutOfMemoryError的错误了。

因此,应该少用String这东西,特别是 String的“+=”操作。不仅来的String对象不能继续使用,而且又要产生多个新对象,会较高的占用内存,所以必须要改用StringBuffer来实现相应目的。下面是改用StringBuffer来做的测试。

public class MemoryTest{

 public static void main(String args[]){

   StringBuffer s=new StringBuffer("abcdefghijklmnop");

   System.out.print("当前虚拟机最大可用内存为:");

   System.out.println(Runtime.getRuntime().maxMemory()/1024/1024+"M");

   System.out.print("环前,虚拟机已占用内存:");

   System.out.println(Runtime.getRuntime().totalMemory()/1024/1024+"M");

   int count = 0;

   while(true){

   try{

    s.append(s);

    count++;

   }

   catch(Error o){

    System.out.println("环次数:"+count);

    System.out.println("String实际字节数:"+s.length()/1024/1024+"M");

    System.out.println("环后,已占用内存:");

    System.out.println(Runtime.getRuntime().totalMemory()/1024/1024+"M");

    System.out.println("Catch到的错误:"+o);

    break;

    }

    }

  }

}

将String改为StringBuffer以后,在运行时得到了如下结果,如图 11-4所示。

图 11-4

这次可以发现,当StringBuffer所占用的实际字节数为16M的时候才产生溢出,整整比上一个程序的String实际字节数8MB多了一倍。

4.2 启示2:用-Xmx参数来提高内存可控制量

前面介绍过-Xmx这个参数的用法,如果还是处理刚才的那个用StringBuffer的Java程序,则用-Xmx1024m来启动它,看看它的环次数有什么变化。

输入如下指令。

java –mx1024m MemoryTest

得到结果如图 11-5所示。

图 11-5

通过使用-Xmx参数将其可控内存量扩大至1024MB后,这个程序到了1GB的时候内存才超限,从而使内存的可控性提高了。但扩大内存使用量永远不是最终的解决方案,如果你的程序没有去更加地优化,早晚还是会超限的。

4.3 启示3:二维数组比一维数组占用更多内存空间

对于内存占用的问题,还有一个地方值得注意,就是二维数组的内存占用问题。

有时候我们一厢情愿地认为:

二维数组的占用内存空间多无非就是二维数组的实际数组元素数比一维数组多而已,那么二维数组的所占空间一定是实际申请的元素数而已。

事实上并不是这样的,对于一个二维数组而言,它所占用的内存空间要远远大于它开辟的数组元素数。下面来看一个一维数组程序的例子。

public class MemFor{

   public static void main (String[] args) {

   try{

      int len=1024*1024*2;   //设定环次数

      byte [] abc=new byte[len];

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

       abc[i]=(byte)i;

      }

      System.out.print("已占用内存:");

      System.out.println(

      Runtime.getRuntime().totalMemory()/1024/1024+"M");

   }

   catch(Error e){

   }

   }

}

这个程序是开辟了1024×1024×2即2MB的数组元素的一维数组,运行这个程序得到的结果如图 11-6所示,程序运行结果提示“已占用内存:3MB”。

图 11-6

下面再将这个程序进行修改,改为一个二维数组,这个二维数组的元素数量也尽量地和上一个一维数组的元素数量保持一致。

public class MemFor{

   public static void main (String[] args) {

   try{

      int len=1024*1024;  //设定环次数

      byte [][] abc=new byte[len][2];

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

       abc[i][0]=(byte)i;

       abc[i][1]=(byte)i;

      }

      System.out.print("已占用内存:");

      System.out.println(

      Runtime.getRuntime().totalMemory()/1024/1024+"M");

   }

   catch(Error e){

   }

   }

}

当申请的元素数量未变,只是将二维数组的行数定为1024×1024、列数定为2,和刚才的那个一维数组1024×1024×2的数量完全一致,但得到的运算结果如图 11-7所示,竟然占用了29MB的空间。

图 11-7

姑且不管造成这种情况的因,只要知道一点就够了,那就是“二维数组占内存”。所以在编写程序的时候要注意,能不用二维数组的地方尽量用一维数组,要求内存占用小的地方尽量用一维数组。

4.4 启示4:用HashMap提高内存查询速度

有一本《大学计算机应用基础》中是这样描述内存的。

DRAM:即内存条。常说的内存并不是内部存储器,而是DRAM。

CPU的运行速度很快,而外部存储器的读取速度相对来说就很慢,如果CPU需要用到的数据总是从外部存储器中读取的,由于外部设备很慢,CPU可能用到的数据预先读到DRAM中,CPU产生的临时数据也暂时存放在DRAM中,这样的结果是大大地提高了CPU的利用率和计算机运行速度。

这是一个典型计算机基础教材针对内存的描述,也许作为计算机专业的程序员,对这段描述并不陌生。但也因为这段描述而对内存的处理速度有神话的理解,认为内存中的处理速度是非常快的。以使持有这种观点的程序员遇到一个巨型的内存查询环的较长时间时,就束手无策了。

看一下如下程序。

public class MemFor{

   public static void main (String[] args) {

   long start=System.currentTimeMillis(); //取得当前时间

   int len=1024*1024*3;   //设定环次数

   int [][] abc=new int[len][2];

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

     abc[i][0]=i;

     abc[i][1]=(i+1);

   }

   long get=System.currentTimeMillis();  //取得当前时间

   //环将想要的数值取出来,本程序取数组的最后一个值

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

     if ((int)abc[i][0]==(1024*1024*3-1)){

       System.out.println("取值结果:"+abc[i][1]);

     }

   }

   long end=System.currentTimeMillis();   //取得当前时间

    //输出测试结果

   System.out.println("赋值环时间:"+(get-start)+"ms");

   System.out.println("获取环时间:"+(end-get)+"ms");

   System.out.print("Java可控内存:");

   System.out.println(Runtime.getRuntime().maxMemory()/1024/1024+"M");

   System.out.print("已占用内存:");

   System.out.println(Runtime.getRuntime().totalMemory()/1024/1024+"M");

}

}

运行这个程序。

java -Xmx1024m MemFor

程序的运行结果如下。

取值结果:3145728。

赋值环时间:2464ms。

获取环时间:70ms。

Java可控内存:1016MB。

已占用内存:128MB。

可以发现,这个程序环了3145728次获得想要的结果,环获取数值的时间用了70毫秒。

你觉得快吗

是啊,70毫秒虽然小于1秒钟,但是如果你不得不在这个环外面再套一个环,即使外层嵌套的环只有100次,那么想想看是多少毫秒呢

回答:70毫秒×100=7000毫秒=7秒

如果,环1000次呢

70秒!

70秒的运行时间对于这个程序来说就是灾难了。

面对这个程序的运行时间,很多程序员已束手无策了,其实Java给程序员们提供了一个较快的查询方法——哈希表查询。

下面将这个程序用HashMap来改造一下,再看看运行结果。

import java.util.*;

public class HashMapTest{

  public static void main (String[] args) {

   HashMap has=new HashMap();

   int len=1024*1024*3;

   long start=System.currentTimeMillis();

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

      has.put(""+i,""+i);

   }

   long end=System.currentTimeMillis();

   System.out.println("取值结果:"+has.get(""+(1024*1024*3-1)));

   long end2=System.currentTimeMillis();

   System.out.println("赋值环时间:"+(end-start)+"ms");

   System.out.println("获取环时间:"+(end2-end)+"ms");

   System.out.print("Java可控内存:");

   System.out.println(Runtime.getRuntime().maxMemory()/1024/1024+"M");

   System.out.print("已占用内存:");

   System.out.println(Runtime.getRuntime().totalMemory()/1024/1024+"M");

  }

}

运行这个程序。

java -Xmx1024m HashMapTest

程序的运行结果如下。

取值结果:3145727。

赋值环时间:16454ms。

获取环时间:0ms。

Java可控内存:1016MB。

已占用内存:566MB。

那么现在用HashMap来取值的时间竟然不到1ms,这时的程序的效率明显提高了。看来用哈希表进行内存中的数据搜索的速度确实很快。

在提高数据搜索速度的同时也要注意到,赋值时间的差异和内存占用的差异。

赋值环时间:

HashMap:16454ms。

普通数组:2464ms。

占用内存:

HashMap:566MB。

普通数组:128MB。

可以看出,HashMap在初始化以及内存占用方面都要高于普通数组。如果仅仅是为了数据存储,用普通数组是比较适合的,但是如果为了频繁查询的目的,HashMap是必然的选择。

4.5 启示5:用arrayCopy()提高数组截取速度

当需要处理一个大的数组应用时,往往需要对数组进行大面积的截取与复制操作,比如针对图形显示的应用时,单纯地通过对数组元素的处理操作有时捉襟见肘。

提高数组处理速度的一个很好的方法是使用System.arrayCopy(),它可以提高数组的截取速度。下面可以做一个对比试验。

例如用普通的数组赋值方法来处理,程序如下。

public class arraycopyTest1{

  public static void main( String[] args ){

    String temp="abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"

         +"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"

         +"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"

         +"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"

         +"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"

         +"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"

         +"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"

         +"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"

         +"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"

         +"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz";

    char[] oldArray=temp.toCharArray();

    char[] newArray=null;

    long start=0L;

    newArray=new char[length];

    //开始时间记录

    start=System.currentTimeMillis();

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

    for(int j=0;j<length;j++){

       newArray[j]=oldArray[begin+j];

    }

    }

    //打印总用时间

    System.out.println(System.currentTimeMillis()-start+”ms”);

  }

}

程序运行结果如图 11-8所示。

图 11-8

那么下面再用arrayCopy()的方法试验一下。

public final class arraycopyTest2{

  public static void main( String[] args ){

    String temp="abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"

       +"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"

       +"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"

       +"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"

       +"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"

       +"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"

       +"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"

       +"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"

       +"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"

       +"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz";

   char[] oldArray=temp.toCharArray();

   char[] newArray=null;

   long start=0L;

   newArray=new char[length];

   //记录开始时间

   start=System.currentTimeMillis();

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

       System.arraycopy(oldArray,100,newArray,0,120);

   }

   //打印总用时间

   System.out.println((System.currentTimeMillis()-start)+”ms”);

   }

}

程序运行结果如图 11-9所示。

图 11-9

两个程序的差距为3秒多,如果处理更大批量的数组,它们的差距还会更大。因此,可以在适当的情况下用这个方法来处理数组的问题。

有时候为了使用方便,可以在自己的tools包中重载一个arrayCopy方法,程序如下。

  public static Object[] arrayCopy(int pos,Object[] srcObj){

   return arrayCopy(pos,srcObj.length,srcObj);

  }

  public static Object[] arrayCopy(int pos,int dest,Object[] srcObject){

   Object[] rv=null;

   rv=new Object[dest-pos];

   System.arraycopy(srcObject,pos,rv,0,rv.length);

   return rv;

  }

分享到:
评论

相关推荐

    Oracle优化日记:一个金牌DBA的故事 白鳝.扫描版

    调整游标相关参数6月1日 ORA-4030和内存泄漏今日点评优化小技巧 如何分析ORA-40306月2日 优化方案今日点评优化小技巧 一个提供参考的 优化方案优化小技巧 游标的共享6月3日 拆分大型SQL优化小技巧 拆分复杂SQL6月4日...

    高质量C++/C 编程指南,提高编程能力,优化程序效率,编程高手必备

    综上所述,"高质量C++/C编程指南"是一本全面涵盖C++和C语言编程技巧、最佳实践和优化策略的书籍,无论你是初学者还是经验丰富的开发者,都能从中获得宝贵的知识和启示。通过深入学习和实践,你可以显著提升自己的...

    Windows编程启示录 中英文版合集

    内存管理是另一大重点,Chen解释了Windows的虚拟内存机制、内存分配策略和内存泄漏检测方法。这对于优化程序性能、避免资源浪费和提升稳定性有着重要作用。 文件系统和I/O操作也是Windows编程中不可忽视的部分。书...

    DOOM启示录

    6. **编程技巧与优化**:书中可能包含了关于C语言编程和游戏性能优化的技巧,如内存管理、代码效率提升等,对于程序员来说具有很高的学习价值。 7. **游戏行业影响**:DOOM的成功推动了游戏行业的快速发展,促进了...

    C++语言实现系统内存分配与回收.docx

    在C++编程中,内存管理是程序设计的关键部分。该文档描述的是如何使用C++来实现操作系统级别的内存分配和回收...然而,实际的内存回收和碎片整理策略通常更为复杂,可能需要更高级的数据结构和算法来优化内存使用效率。

    VB中的优化理念及小技巧

    代码优化不仅仅是对代码质量的一种追求,更是提高程序运行效率的关键手段。尤其是在资源有限或对实时性要求较高的应用场景下,通过优化能够显著减少程序运行时间,改善用户体验。以下是一些基本的优化理念: 1. **...

    oracle10G性能调整与优化

    1. **SQL 语句优化**:SQL语句是数据库操作的核心,优化SQL语句可以显著提升查询效率。这包括但不限于选择合适的索引、合理使用连接方式、避免使用子查询等。 2. **数据库配置参数调优**:通过调整Oracle数据库的...

    dma控制器的作用及其原理

    ### DMA控制器的作用及其原理 #### 一、DMA控制器概述 DMA(Direct Memory Access,直接内存...通过对DMA工作原理的深入理解,不仅可以帮助我们更好地利用这一技术,还能够为未来的系统优化和创新提供宝贵的启示。

    2 手淘性能优化实践1

    【手淘性能优化实践】 手淘,作为阿里巴巴淘系技术的核心产品,其性能优化对于提供良好的用户体验至关重要。...这不仅是对Android开发者的启示,也为整个移动互联网行业提供了宝贵的性能优化实践案例。

    CT图像重建的快速卷积反投影算法的优化

    通过合理地组织数据结构和优化内存访问模式,可以有效地减少内存读写次数,进一步提高算法的整体性能。 5. **多尺度分析**:对于不同尺度的数据采用不同的处理策略。对于低频成分采用较粗略的计算,而对于高频成分...

    网络游戏-用于无线传感器网络的控制方法.zip

    网络游戏,看似与无线传感器网络相距甚远,但其背后的技术原理和优化策略却能为WSNs的控制带来新的启示和创新。 网络游戏的发展,尤其是在多人在线游戏(MMOGs)中的实时性和互动性需求,催生了大量先进的网络控制...

    实用优化:简介Practical Optimization: A Gentle Introduction

    了解具体的案例研究、开发者分享的最佳实践和技术文章等,都能够为应用程序优化提供宝贵的参考和启示。 总之,《实用优化:简介Practical Optimization: A Gentle Introduction》作为一本介绍应用程序优化中最重要...

    梯级水库短期优化调度模型的精细化与GPU并行实现.pdf

    综上所述,该研究对于处理短期优化调度的“维数灾”问题具有重要启示,为未来的水资源管理和水电站优化运行提供了新的思路和技术支持。通过精细化模型和GPU并行计算,不仅可以提高调度的精确性,还能大幅缩短计算...

    Shader优化

    7. **内存访问优化**:优化内存访问模式,减少延迟和提高数据吞吐量。 #### 五、实际案例分析 为了更直观地理解Shader优化的重要性,我们可以参考一个实际案例。假设有一款游戏需要渲染大量复杂场景,其中一个关键...

    浅谈Java中正则表达式的优化方法

    这是因为捕获分组会将匹配结果存储在内存中,而使用非捕获分组则可以避免这部分开销。 **示例代码**: ```java String regex = "(?:(abc))"; // 非捕获分组 String input = "abcabcabc"; Pattern pattern = Pattern...

    基于单片机的智能教室控制系统的设计

    2. 系统功能:智能教室控制系统可以实现自动调节灯光、空调、窗帘等环境参数,支持远程监控与控制,还能记录并分析教室使用情况,为资源优化提供数据支持。 3. 通信技术:为了实现设备间的互联互通,控制系统可能...

    SQL Server 2008查询性能优化(英文版)

    - **查询优化**:通过对查询进行分析和重构,可以显著提升查询效率。例如,避免全表扫描、减少子查询的使用等。 - **索引优化**:合理创建和使用索引可以大幅加快查询速度。书中介绍了多种索引类型(如聚集索引、非...

Global site tag (gtag.js) - Google Analytics