关于内存溢出问题,java.lang.OutOfMemoryError: Java heap space (此处是 运行时 String长度过长,总占用内存 过多)

报错信息

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3332)
at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124)
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:448)
at java.lang.StringBuilder.append(StringBuilder.java:136)
at java.lang.StringBuilder.append(StringBuilder.java:131)
at label.label01.main(label01.java:66)

具体代码

出错代码

    ArrayList<Hero> hash = new ArrayList<>();
    long start = System.currentTimeMillis();
    StringBuilder random = new StringBuilder(); // 创建一个随机数缓存
    Random random1 = new Random();

    for (int i = 0; i < 3000000; i++) {
         //生成4位随机数,此处循环若在上一层for里会产生内存溢出
        for (int i1 = 0; i1 < 4; i1++) {
            random.append(random1.nextInt(10)) ;
        }
        hash.add(new Hero("hero-"+random));
    }
    hash.forEach(hero -> {
        if (hero.name.equals("hero-5555"))
            hash.indexOf(hero);
        System.out.println(hero.name);
    });
    long end = System.currentTimeMillis();
    System.out.println(end-start);

修改后1:

   ArrayList<Hero> hash = new ArrayList<>();
    long start = System.currentTimeMillis();
    for (int i = 0; i < 3000000; i++) {
	//采用,新的生成4位随机数方法
       int j=(int)(Math.random() * 9000) + 1000;
        hash.add(new Hero("hero-"+j));
    }
    hash.forEach(hero -> {
        if (hero.name.equals("hero-5555"))
            hash.indexOf(hero);
        System.out.println(hero.name);
    });
    long end = System.currentTimeMillis();
    System.out.println(end-start);

修改后2:

    ArrayList<Hero> hash = new ArrayList<>();
    long start = System.currentTimeMillis();
    StringBuilder random = new StringBuilder(); // 创建一个随机数缓存
    Random random1 = new Random();

    for (int i = 0; i < 3000; i++) {
        //生成4位随机数,此处循环若在上一层for里会产生内存溢出----此处见解有点狭义,请看下一步
        for (int i1 = 0; i1 < 4; i1++) {
            random.append(random1.nextInt(10)) ;
        }
        //具体错因:random在append东西之后没有清空,一直加,导致不断边长,然后此处的Hero对象中的name属性(String)长度过长,导致jvm内存溢出。
        hash.add(new Hero("hero-"+random));
        random.setLength(0); //----------新增了一个清空-----------------------------
    }
    hash.forEach(hero -> {
        if (hero.name.equals("hero-5555"))
            hash.indexOf(hero);
        System.out.println(hero.name);
    });
    long end = System.currentTimeMillis();
    System.out.println(end-start);

具体出错原因:

我们先看一下String中的容纳大小
image
最大值是:2的31次方减一
明显此处的语句是在for循环中,所以是在运行中超过了int的限制,
就是字符串总大小超过了2^31 -1,也就是超过了4G

        hash.add(new Hero("hero-"+random));

问:字符串有长度限制吗?是多少?

答:首先字符串的内容是由一个字符数组 char[] 来存储的,由于数组的长度及索引是整数,且String类中返回字符串长度的方法length() 的返回值也是int ,所以通过查看java源码中的类Integer我们可以看到Integer的最大范围是2^31 -1,由于数组是从0开始的,所以数组的最大长度可以使【0~2^31】通过计算是大概4GB。

但是通过翻阅java虚拟机手册对class文件格式的定义以及常量池中对String类型的结构体定义我们可以知道对于索引定义了u2,就是无符号占2个字节,2个字节可以表示的最大范围是2^16 -1 = 65535。

其实是65535,但是由于JVM需要1个字节表示结束指令,所以这个范围就为65534了。超出这个范围在编译时期是会报错的,但是运行时拼接或者赋值的话范围是在整形的最大范围。

简单地说

字符串有长度限制,在编译期,要求字符串常量池中的常量不能超过65535,并且在javac执行过程中控制了最大值为65534。

在运行期,长度不能超过Int的范围,否则会抛异常。

posted on 2021-07-21 15:33  汤姆猫8  阅读(2006)  评论(0)    收藏  举报