A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

© 程辰 黑马帝   /  2011-9-16 19:25  /  2677 人查看  /  12 人回复  /   0 人收藏 转载请遵从CC协议 禁止商业使用本文

大家都知道的,JVM默认能够控制的内存在63M左右,所以在运行稍大一点的项目时,在不改变JVM控制内存大小的前提下,很容易发生内存溢出,大家都有什么节约内存的方法,或者某个类在哪个方面相比较其他的类能够提高运行速度。

我先说我知道的一个,String和StringBuffer在增添字符串方面最好用StringBuffer的append()方法,而不用String的 +=运算来做,相同的字符串,StringBuffer相对于Sting内存大概可以节约一半左右,也就是说,向一个StringBuffer变量添加的实际字节数达到8M所占用的JVM内存只相当于向String变量里面添加8M所占的JVM内存的一半。

确实很想知道这方面的知识,知道的同学请积极补充。

为了方便大家阅读,增加控制java内存的功力,我会将同学们补充的知识点汇总到1楼,谢谢大家的支持。

感谢宋红旺同学的发言:

原帖由 宋红旺 于 2011-09-17 09:58 发表 昨天晚上总结的,正准备发表回复的时候突然宿舍停电了,oh 天啊,没保存。所以今天赶紧重新整理了一下,现在分享一下:
1、尽早释放无用对象的引用。好的办法是使用临时变量的时候,让引用变量在退出活动域后,自动设置为null,暗示垃圾收集器来收集该对象,防止发生内存泄露。对于仍然有指针指向的实例,jvm就不会回收该资源,因为垃圾回收会将值为null的对象作为垃圾,提高GC回收机制效率;
2、我们的程序里不可避免大量使用字符串处理,避免使用String,应大量使用StringBuffer,每一个String对象都得独立占用内存一块区域;正如楼主说的那样
3、尽量少用静态变量 ,因为静态变量 是全局的,GC不会回收的;
4、避免集中创建对象尤其是大对象,JVM会突然需要大量内存,这时必然会触发GC优化系统内存环境;显示的声明数组空间,而且申请数量还极大。
5、尽量运用对象池技术以提高系统性能;生命周期长的对象拥有生命周期短的对象时容易引发内存泄漏,例如大集合对象拥有大数据量的业务对象的时候,可以考虑分块进行处理,然后解决一块释放一块的策略。
6、不要在经常调用的方法中创建对象,尤其是忌讳在循环中创建对象。可以适当的使用hashtable,vector 创建一组对象容器,然后从容器中去取那些对象,而不用每次new之后又丢弃
7、一般都是发生在开启大型文件或跟数据库一次拿了太多的数据,造成 Out Of Memory Error 的状况,这时就大概要计算一下数据量的最大值是多少,并且设定所需最小及最大的内存空间值。
现在整理的也只有这么多了,等以后增加了再分享给大家哈!



我再次补充:


原帖由 程辰 于 2011-09-17 10:26 发表 学习到了很多,十分感谢。另外再补充一点:


经过测试,一维数组和二维数组开辟差不多大小的空间时,二维数组占用的内存空间要远远大于它开辟的数组元素数。


感谢ila123同学补充:


原帖由 ila123 于 2011-09-18 14:19 发表 其实有一些是习惯问题,人们不太注意而于
1、int &byte
比如在写一个标志位时,一般都写private int a=0;//0:表示草稿1:表示已经发布
但像这种标志位,我确定知道是在byte范围内,那么可以改为private byte b=0;//0:草稿1:已发布;这样的话省下不少,但人们懒得去这样弄。
2、String&StringBuffer
package heima.javase;
public class StringStringBuffer {

        /**
         * @param args
         */
        public static void main(String[] args) {
                int a = 5000;
                String tempStr = "abcdefghijklmnopqrstuvwxyz";
                long start1 = System.currentTimeMillis();
                String str="";
                for (int i = 0; i < a; i++) {
                        str += tempStr;
                }
                long end1 = System.currentTimeMillis();
                System.out.println("String执行时间:" + (end1 - start1));
                StringBuffer sb = new StringBuffer();
                long start2 = System.currentTimeMillis();
                for (int i = 0; i < a; i++) {
                        sb = sb.append(tempStr);
                }
                long end2 = System.currentTimeMillis();
                System.out.println("StringBuffer执行时间:" + (end2 - start2) );
        }

}
效果很明显,第一个的执行时间是第二个的700多倍。
[ 本帖最后由 程辰 于 2011-09-18  15:14 编辑 ]

评分

参与人数 1技术分 +1 收起 理由
wangfayin + 1

查看全部评分

12 个回复

倒序浏览
黑马网友  发表于 2011-9-16 22:07:07
沙发
StringBuffer相对于Sting内存大概可以节约一半左右,为什么呀?
回复 使用道具 举报
黑马网友  发表于 2011-9-16 23:26:19
藤椅

回复 沙发 的帖子

不为什么阿,测试出来就是这样子的。
回复 使用道具 举报
黑马网友  发表于 2011-9-17 01:50:46
板凳
怎么测试的?内存一般不会溢出,但想弄出事还是挺简单的,new个很大的字节数组,或者用dom4j解析一个相当大的xml文件
回复 使用道具 举报
黑马网友  发表于 2011-9-17 08:08:17
报纸

回复 板凳 的帖子

测试很简单,使用Runtime.getRuntime().maxmemory()方法可以得到当前虚拟机最大可用内存。
使用Runtime.getRuntime().totalMemory()可以得到虚拟机已占用的内存。

你把String和StringBuffer分别写个循环(考虑极端的内存溢出情况),然后使用上面的第二个可以得到虚拟机已占用的内存,再计算出此时String和StringBuffer实际的字节数,就可以得到结论了。
回复 使用道具 举报
黑马网友  发表于 2011-9-17 08:28:57
地板

回复 楼主 的帖子

while循环比for循环效率高些,但他有些局限性,Stringbuffer可以自动扩大存储量,而String 直接固定它的容量,所以StringBuffer效率高些,希望给你有些帮助
回复 使用道具 举报
程辰 黑马帝 2011-9-17 08:36:06
7#
我给你写一个吧:

先写String的:

package PersonalAbitraryText;

public class Test3 {
       public static void main(String[] args)  {
              String s = "abcdefghigklmnopqrst";
              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 e){
                      System.out.println("循环次数:" + count);
            
              
              System.out.print("String实际字节数:");
              System.out.println(s.length()/1024/1024 + "M");
              System.out.println("测试后,虚拟机已占用内存:");
              System.out.println(Runtime.getRuntime().totalMemory()/1024/1024 + "M");
              e.printStackTrace();
              break;
                      }
          }
        }
}
这是我的测试结果:
当前虚拟机最大可用内存为:63M
测试前,虚拟机已占用内存:1M
循环次数:19
测试后,虚拟机已占用内存:
63.5625M
String实际字节数:8M
java.lang.OutOfMemoryError: Java heap space
        at java.util.Arrays.copyOfRange(Arrays.java:3209)
        at java.lang.String.<init>(String.java:215)
        at java.lang.StringBuilder.toString(StringBuilder.java:430)
        at PersonalAbitraryText.Test3.main(Test3.java:14)


然后是StringBuffer的:
package PersonalAbitraryText;

public class Test3 {
       public static void main(String[] args)  {
              StringBuffer s = new StringBuffer("abcdefghigklmnopqrst");
              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 e){
                      System.out.println("循环次数:" + count);
            
              
              System.out.print("StringBuffer实际字节数:");
              System.out.println(s.length()/1024/1024 + "M");
              System.out.println("测试后,虚拟机已占用内存:");
              System.out.println(Runtime.getRuntime().totalMemory()/1024/1024 + "M");
              e.printStackTrace();
              break;
                      }
          }
        }
}
控制台打印结果:

当前虚拟机最大可用内存为:63M
测试前,虚拟机已占用内存:1M
循环次数:20
StringBuffer实际字节数:16M
测试后,虚拟机已占用内存:
63.5625M
java.lang.OutOfMemoryError: Java heap space
        at java.util.Arrays.copyOf(Arrays.java:2882)
        at java.lang.AbstractStringBuilder.expandCapacity(AbstractStringBuilder.java:100)
        at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:403)
        at java.lang.StringBuffer.append(StringBuffer.java:253)
        at PersonalAbitraryText.Test3.main(Test3.java:14)


废了很多力气,通过上面的结果应该有看出什么吧。

评分

参与人数 1技术分 +2 收起 理由
admin + 2

查看全部评分

回复 使用道具 举报
黑马网友  发表于 2011-9-17 09:58:19
8#
昨天晚上总结的,正准备发表回复的时候突然宿舍停电了,oh 天啊,没保存。所以今天赶紧重新整理了一下,现在分享一下:
1、尽早释放无用对象的引用。好的办法是使用临时变量的时候,让引用变量在退出活动域后,自动设置为null,暗示垃圾收集器来收集该对象,防止发生内存泄露。对于仍然有指针指向的实例,jvm就不会回收该资源,因为垃圾回收会将值为null的对象作为垃圾,提高GC回收机制效率;
2、我们的程序里不可避免大量使用字符串处理,避免使用String,应大量使用StringBuffer,每一个String对象都得独立占用内存一块区域;正如楼主说的那样
3、尽量少用静态变量 ,因为静态变量 是全局的,GC不会回收的;
4、避免集中创建对象尤其是大对象,JVM会突然需要大量内存,这时必然会触发GC优化系统内存环境;显示的声明数组空间,而且申请数量还极大。
5、尽量运用对象池技术以提高系统性能;生命周期长的对象拥有生命周期短的对象时容易引发内存泄漏,例如大集合对象拥有大数据量的业务对象的时候,可以考虑分块进行处理,然后解决一块释放一块的策略。
6、不要在经常调用的方法中创建对象,尤其是忌讳在循环中创建对象。可以适当的使用hashtable,vector 创建一组对象容器,然后从容器中去取那些对象,而不用每次new之后又丢弃
7、一般都是发生在开启大型文件或跟数据库一次拿了太多的数据,造成 Out Of Memory Error 的状况,这时就大概要计算一下数据量的最大值是多少,并且设定所需最小及最大的内存空间值。
现在整理的也只有这么多了,等以后增加了再分享给大家哈!

评分

参与人数 1技术分 +2 收起 理由
admin + 2

查看全部评分

回复 使用道具 举报
黑马网友  发表于 2011-9-17 10:26:09
9#

回复 8 # 的帖子

学习到了很多,十分感谢。另外再补充一点:

经过测试,一维数组和二维数组开辟差不多大小的空间时,二维数组占用的内存空间要远远大于它开辟的数组元素数。
回复 使用道具 举报
黑马网友  发表于 2011-9-18 14:19:50
10#
其实有一些是习惯问题,人们不太注意而于
1、int &byte
比如在写一个标志位时,一般都写private int a=0;//0:表示草稿1:表示已经发布
但像这种标志位,我确定知道是在byte范围内,那么可以改为private byte b=0;//0:草稿1:已发布;这样的话省下不少,但人们懒得去这样弄。
2、String&StringBuffer
package heima.javase;
public class StringStringBuffer {

        /**
         * @param args
         */
        public static void main(String[] args) {
                int a = 5000;
                String tempStr = "abcdefghijklmnopqrstuvwxyz";
                long start1 = System.currentTimeMillis();
                String str="";
                for (int i = 0; i < a; i++) {
                        str += tempStr;
                }
                long end1 = System.currentTimeMillis();
                System.out.println("String执行时间:" + (end1 - start1));
                StringBuffer sb = new StringBuffer();
                long start2 = System.currentTimeMillis();
                for (int i = 0; i < a; i++) {
                        sb = sb.append(tempStr);
                }
                long end2 = System.currentTimeMillis();
                System.out.println("StringBuffer执行时间:" + (end2 - start2) );
        }

}
效果很明显,第一个的执行时间是第二个的700多倍。

评分

参与人数 1技术分 +1 收起 理由
wangfayin + 1

查看全部评分

回复 使用道具 举报
黑马网友  发表于 2011-9-18 16:50:17
11#

回复 8 # 的帖子

[quote]3、尽量少用静态变量 ,因为静态变量 是全局的,GC不会回收的;
[/quote]

这样是不是说等程序结束了静态变量所占的内存才消失呢?
回复 使用道具 举报
黑马网友  发表于 2011-9-18 22:26:17
12#

回复 11 # 的帖子

是这样的,静态变量在创建之后就会一直占用内存,知道程序结束,释放内存时才会消失。
回复 使用道具 举报
黑马网友  发表于 2011-9-21 17:21:49
13#
我是来学习的。
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马