返回顶部
首页 > 资讯 > 精选 >Java中的substring真的会引起内存泄露么
  • 284
分享到

Java中的substring真的会引起内存泄露么

2023-06-17 07:06:37 284人浏览 薄情痞子
摘要

这篇文章给大家介绍Java中的substring真的会引起内存泄露么,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。在Java中开发,String是我们开发程序可以说必须要使用的类型,String有一个substring

这篇文章给大家介绍Java中的substring真的会引起内存泄露么,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。

在Java中开发,String是我们开发程序可以说必须要使用的类型,String有一个substring方法用来截取字符串,我们想必也常常使用。但是你知道么,关于Java 6中的substring是否会引起内存泄露,在国外的论坛和社区有着一些讨论,以至于Java官方已经将其标记成bug,并且为此Java 7 还重新进行了实现。读到这里可能你的问题就来了,substring怎么会引起内存泄露呢?那么我们就带着问题,走进小黑屋,看看substring有没有内存泄露,又是怎么导致所谓的内存泄露。

基本介绍

substring方法提供两种重载,***种为只接受开始截取位置一个参数的方法。

public String substring(int beginIndex)

比如我们使用上面的方法,"unhappy".substring(2) 返回结果 "happy"

另一种重载就是接受一个开始截取位置和一个结束截取位置的参数的方法。

public String substring(int beginIndex, int endIndex)

使用这个方法,"smiles".substring(1, 5) 返回结果 "mile"

通过这个介绍我们基本了解了substring的作用,这样便于我们理解下面的内容。

准备工作

因为这个问题出现的情况在Java 6,如果你的Java版本号不是Java 6 需要调整一下。

终端调整(适用于Mac系统)

查看java版本号

13:03 $ java -version java version "1.8.0_25" Java(TM) SE Runtime Environment (build 1.8.0_25-b17) Java HotSpot(TM) 64-Bit Server VM (build 25.25-b02, mixed mode)

切换到1.6

export JAVA_HOME=$(/usr/libexec/java_home -v 1.6)

ubuntu使用alternatives --config java,Fedora上面使用alternatives --config java。

如果你使用Eclipse,可以选择工程,右击,选择Properties(属性)— Java Compiler(Java编译器)进行特殊指定。

问题重现

这里贴一下java官方bug里用到的重现问题的代码。

public class TestGC {     private String largeString = new String(new byte[100000]);       String getString() {         return this.largeString.substring(0,2);     }       public static void main(String[] args) {         java.util.ArrayList list = new java.util.ArrayList();         for (int i = 0; i < 1000000; i++) {             TestGC gc = new TestGC();             list.add(gc.getString());         }     } }

然而上面的代码,只要使用Java 6 (Java 7和8 都不会抛出异常)运行一下就会报java.lang.OutOfMemoryError: Java heap space的异常,这说明没有足够的堆内存供我们创建对象,JVM选择了抛出异常操作。

于是有人会说,是因为你每个循环中创建了一个TestGC对象,虽然我们加入ArrayList只是两个字符的字符串,但是这个对象中又存储largeString这么大的对象,这样必然会造成OOM的。

然而,其实你说的不对。比如我们看一下这样的代码,我们只修改getString方法。

public class TestGC {     private String largeString = new String(new byte[100000]);       String getString() {         //return this.largeString.substring(0,2);       return new String("ab");     }       public static void main(String[] args) {         java.util.ArrayList list = new java.util.ArrayList();         for (int i = 0; i < 1000000; i++) {             TestGC gc = new TestGC();             list.add(gc.getString());         }       } }

执行上面的方法,并不会导致OOM异常,因为我们持有的时1000000个ab字符串对象,而TestGC对象(包括其中的largeString)会在java的垃圾回收中释放掉。所以这里不会存在内存溢出。

那么究竟是什么导致的内存泄露呢?要研究这个问题,我们需要看一下方法的实现,即可。

深入Java 6实现

在String类中存在这样三个属性

  • value 字符数组,存储字符串实际的内容

  • offset 该字符串在字符数组value中的起始位置

  • count 字符串包含的字符的长度

Java 6中substring的实现

public String substring(int beginIndex, int endIndex) {   if (beginIndex < 0) {       throw new StringIndexOutOfBoundsException(beginIndex);   }   if (endIndex > count) {       throw new StringIndexOutOfBoundsException(endIndex);   }   if (beginIndex > endIndex) {       throw new StringIndexOutOfBoundsException(endIndex - beginIndex);   }   return ((beginIndex == 0) && (endIndex == count)) ? this :       new String(offset + beginIndex, endIndex - beginIndex, value); }

上述方法调用的构造方法

//Package private constructor which shares value array for speed. String(int offset, int count, char value[]) {   this.value = value;   this.offset = offset;   this.count = count; }

当我们读完上述的代码,我们应该会豁然开朗,原来是这个样子啊!

当我们调用字符串a的substring得到字符串b,其实这个操作,无非就是调整了一下b的offset和count,用到的内容还是a之前的value字符数组,并没有重新创建新的专属于b的内容字符数组。

举个和上面重现代码相关的例子,比如我们有一个1G的字符串a,我们使用substring(0,2)得到了一个只有两个字符的字符串b,如果b的生命周期要长于a或者手动设置a为null,当垃圾回收进行后,a被回收掉,b没有回收掉,那么这1G的内存占用依旧存在,因为b持有这1G大小的字符数组的引用。

看到这里,大家应该可以明白上面的代码为什么出现内存溢出了。

共享内容字符数组

其实substring中生成的字符串与原字符串共享内容数组是一个很棒的设计,这样避免了每次进行substring重新进行字符数组复制。正如其文档说明的,共享内容字符数组为了就是速度。但是对于本例中的问题,共享内容字符数组显得有点蹩脚。

如何解决

对于之前比较不常见的1G字符串只截取2个字符的情况可以使用下面的代码,这样的话,就不会持有1G字符串的内容数组引用了。

String littleString = new String(largeString.substring(0,2));

下面的这个构造方法,在源字符串内容数组长度大于字符串长度时,进行数组复制,新的字符串会创建一个只包含源字符串内容的字符数组。

public String(String original) {   int size = original.count;   char[] originalValue = original.value;   char[] v;   if (originalValue.length > size) {       // The array representing the String is bigger than the new       // String itself.  Perhaps this constructor is being called       // in order to trim the baggage, so make a copy of the array.       int off = original.offset;       v = Arrays.copyOfRange(originalValue, off, off+size);   } else {       // The array representing the String is the same       // size as the String, so no point in making a copy.       v = originalValue;   }   this.offset = 0;   this.count = size;   this.value = v; }

Java 7 实现

在Java 7 中substring的实现抛弃了之前的内容字符数组共享的机制,对于子字符串(自身除外)采用了数组复制实现单个字符串持有自己的应该拥有的内容。

public String substring(int beginIndex, int endIndex) {     if (beginIndex < 0) {       throw new StringIndexOutOfBoundsException(beginIndex);     }     if (endIndex > value.length) {       throw new StringIndexOutOfBoundsException(endIndex);     }     int subLen = endIndex - beginIndex;     if (subLen < 0) {       throw new StringIndexOutOfBoundsException(subLen);     }     return ((beginIndex == 0) && (endIndex == value.length)) ? this                 : new String(value, beginIndex, subLen); }

substring方法中调用的构造方法,进行内容字符数组复制。

public String(char value[], int offset, int count) {     if (offset < 0) {           throw new StringIndexOutOfBoundsException(offset);     }     if (count < 0) {       throw new StringIndexOutOfBoundsException(count);     }     // Note: offset or count might be near -1>>>1.     if (offset > value.length - count) {       throw new StringIndexOutOfBoundsException(offset + count);     }     this.value = Arrays.copyOfRange(value, offset, offset+count); }

真的是内存泄露么

我们知道了substring某些情况下可能引起内存问题,但是这个叫做内存泄露么?

其实个人认为这个不应该算为内存泄露,使用substring生成的字符串b固然会持有原有字符串a的内容数组引用,但是当a和b都被回收之后,该字符数组的内容也是可以被垃圾回收掉的。

哪个版本实现的好

关于Java 7 对substring做的修改,收到了褒贬不一的反馈。

个人更加倾向于Java 6的实现,当进行substring时,使用共享内容字符数组,速度会更快,不用重新申请内存。虽然有可能出现本文中的内存性能问题,但也是有方法可以解决的。

Java 7的实现不需要程序员特殊操作避免了本文中问题,但是进行每次substring的操作性能总会比java 6 的实现要差一些。这种实现显得有点“糟糕”。

问题的价值

虽然这个问题出现在Java 6并且Java 7中已经修复,但并不代表我们就不需要了解,况且Java 7的重新实现被喷的很厉害。

其实这个问题的价值,还是比较宝贵的,尤其是内容字符数组共享这个优化的实现。希望可以为大家以后的设计实现提供帮助和一些想法。

受影响的方法

trim和subSequence都存在调用substring的操作。Java 6和Java 7 substring实现的更改也间接影响到了这些方法。

参考资源

以下三篇文章写得都比较不错,但是都稍微有一些问题,我都已经标明出来,大家阅读时,需要注意。

  • The substring() Method in jdk 6 and JDK 7 本文中解决java6中问题提到的字符串拼接不推荐,具体原因可以参考Java细节:字符串的拼接

  • How SubString method works in Java &ndash; Memory Leak Fixed in JDK 1.7 本文中提到的有一个概念错误,新的字符串不会阻止旧的字符串被回收,而是阻止旧字符串中的内容字符数组。阅读时需要注意。

  • JDK-4513622 : (str) keeping a substring of a field prevents GC for object 本文中提到的有一个测试,使用非new的形式有一点问题,其忽视了字符串常量池的存在,具体查看下面的注意。

注意

上面的重现问题的代码中

String getString() {   //return this.largeString.substring(0,2);       return new String("ab"); }

这里***不要写成下面这样,因为在JVM中存在字符串常量池,”ab”不会重新创建新字符串,所有的变量都会引用一个对象,而使用new String()则每次重新创建对象。

String getString() {       return "ab"; }

关于Java中的substring真的会引起内存泄露么就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。

--结束END--

本文标题: Java中的substring真的会引起内存泄露么

本文链接: https://lsjlt.com/news/287688.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • Java中的substring真的会引起内存泄露么
    这篇文章给大家介绍Java中的substring真的会引起内存泄露么,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。在Java中开发,String是我们开发程序可以说必须要使用的类型,String有一个substring...
    99+
    2023-06-17
  • Android 中Handler引起的内存泄露
    在Android常用编程中,Handler在进行异步操作并处理返回结果时经常被使用。通常我们的代码会这样实现。 public class SampleActivity ext...
    99+
    2022-06-06
    handler Android
  • 避免 Android中Context引起的内存泄露
    Context是我们在编写Android程序经常使用到的对象,意思为上下文对象。 常用的有Activity的Context还是有Application的Context。Acti...
    99+
    2022-06-06
    context Android
  • 面试官:java ThreadLocal真的会造成内存泄露吗
    目录1、ThreadLocal知识体系2、为什么会被设计为弱引用呢?3、大量Entry造成的内存溢出问题探讨总结1、ThreadLocal知识体系 本文还是不能免俗,在回答这个问题之...
    99+
    2024-04-02
  • ThreadLocal在Tomcat中引起内存泄露怎么解决
    这篇文章主要介绍“ThreadLocal在Tomcat中引起内存泄露怎么解决”,在日常操作中,相信很多人在ThreadLocal在Tomcat中引起内存泄露怎么解决问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答...
    99+
    2023-06-05
  • java内存泄露的表现有哪些
    Java内存泄露的表现主要有以下几个方面:1. 内存占用持续增加:当出现内存泄露时,系统中的内存占用会持续增加,而且不会被垃圾回收机...
    99+
    2023-08-24
    java
  • Java语言中的内存泄露代码详解
    Java的一个重要特性就是通过垃圾收集器(GC)自动管理内存的回收,而不需要程序员自己来释放内存。理论上Java中所有不会再被利用的对象所占用的内存,都可以被GC回收,但是Java也存在内存泄露,但它的表现与C++不同。JAVA中的内存管理...
    99+
    2023-05-30
    java 内存泄露 实例
  • Java中内存泄露与溢出的区别是什么
    这期内容当中小编将会给大家带来有关Java中内存泄露与溢出的区别是什么,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。Java内存泄露与溢出的区别内存溢出就是你要求分配的内存超出了系统能给你的,系统不能满足...
    99+
    2023-06-17
  • Java内存溢出和内存泄露的示例分析
    这篇文章给大家分享的是有关Java内存溢出和内存泄露的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。一、为什么要了解内存泄露和内存溢出?内存泄露一般是代码设计存在缺陷导致的,通过了解内存泄露的场景,可以避...
    99+
    2023-05-30
    java
  • 内存泄露的原因是什么
    这篇文章主要介绍“内存泄露的原因是什么”,在日常操作中,相信很多人在内存泄露的原因是什么问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”内存泄露的原因是什么”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!Th...
    99+
    2023-06-16
  • Java中有哪些潜在的内存泄露风险
    本篇文章给大家分享的是有关Java中有哪些潜在的内存泄露风险,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。1. 内存泄露的定义如果GC无法回收内存中不再使用的对象,则定义为内存...
    99+
    2023-06-15
  • Java中的内存泄露问题和解决办法
    目录为什么会产生内存泄漏?内存泄漏对程序的影响?如何检查和分析内存泄漏?常见的内存泄漏及解决方法1、单例造成的内存泄漏2、非静态内部类创建静态实例造成的内存泄漏【已无】3、Handl...
    99+
    2024-04-02
  • 一文搞懂JavaScript中的内存泄露
    目录什么是内存泄漏怎么检测内存泄漏PerformanceMemory内存泄漏的场景垃圾回收算法引用计数循环引用标记清除闭包是内存泄漏吗总结以前我们说的内存泄漏,通常发生在后端,但是不...
    99+
    2024-04-02
  • Java中怎么引入内存泄漏
    Java中怎么引入内存泄漏,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。1. 什么是内存泄漏内存泄漏的定义:应用程序不再使用对象,但是垃圾收集器不能删除它们,因为它们正在被引用...
    99+
    2023-06-16
  • JVM内存泄露的原因是什么
    本篇内容介绍了“JVM内存泄露的原因是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!1 OOM的现象之一今天介绍第一种Java heap...
    99+
    2023-06-02
  • java常见内存泄露的情况有哪些
    Java常见的内存泄漏情况包括: 对象未被正确释放:当一个对象不再被使用时,如果没有正确释放它所占用的内存,那么该对象就会造成内...
    99+
    2024-02-29
    java
  • java内存管理关系及内存泄露的原理分析
    目录java内存管理关系及内存泄露原理java对象和内存的关系创建对象null的作用内存泄露检测内存泄露的原理java内存管理关系及内存泄露原理 这可能是最近写的博客中最接近底层的了...
    99+
    2024-04-02
  • 没有resolve及reject的Promise是否会造成内存泄露
    目录正文DevTools测试执行queryObjects(Promise)测试事件回调可疑的泄露对象正文 DevTools测试 可以用 DevTools 的 queryObjects...
    99+
    2022-11-13
    Promise内存泄露 Promise内存
  • 怎么理解MySQL中多源复制引起的内存泄漏
    怎么理解MySQL中多源复制引起的内存泄漏,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。场景 : MySQL-5.7, 所有的小版本(&l...
    99+
    2024-04-02
  • Java内存泄露的理解与解决是怎样的
    这篇文章给大家介绍Java内存泄露的理解与解决是怎样的,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。Java内存管理机制在C++ 语言中,如果需要动态分配一块内存,程序员需要负责这块内存的整个生命周期。从申请分配、到使...
    99+
    2023-06-17
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作