什么是内存泄漏?所谓内存泄漏,就是由于疏忽或错误造成程序未能释放已经不再使用的内存的情况,他并不是说物理内存消失了,而是指由于不再使用的对象占据了内存不被释放,而导致可用内存不断减小,最终有可能导致内存溢出。

由于垃圾回收器的出现,与传统的C/C++相比,Java已经把内存泄漏的概率大大降低了,所以不再使用的对象会由系统自动收集,但这并不意味着已经没有内存泄漏的可能。内存泄漏实际上更是一个应用问题,这里以String.substring()方法为例,说明这种内存泄漏的问题。

在JDK 1.6中,java.lang.String主要由3部分组成:代表字符数据的value、偏移量offset和长度count。

这个结构为内存泄漏埋下了伏笔,字符串的实际内容由value、offset和count三者共同决定,而非value一项。试想,如果字符串value数组包含了100个字符,而count长度只有1个字节,那么这个string实际上只有1个字符,却占据了至少100个字节,那剩余的99个就属于泄漏的部分,他们不会被使用,不会被释放,却长期占用内存,直到字符串本身被回收。

不幸的是,这种情况在JDK 1.6中非常容易出现。下面简单解读一下JDK 1.6中String.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);
}

可以看到,在substring()的视线中,最终是使用了String的构造函数,生成了一个新的String。该构造函数的实现如下:

String(int offset, int count, char value[]) {
this.value = value;
this.offset = offset;
this.count = count;
}

该构造函数并非公有构造函数。这点应该万幸,因为正是这个构造函数引起了内存泄漏问题。新生成的String并没有从value中获取自己需要的那部分,而是简单的使用了相同的value引用,只是修改了offset和count,以此来确定新的String对象的值。当原始字符串没有被回收时,这种情况是没有问题的,并且通过公用value,还可以节省一部分内存,但是一旦原始字符串被回收,value中多余的部分就造成了空间浪费。

综上所述,如果使用了String.substring()将一个大字符串切割为小字符串,当大字符串被回收时,小字符串的存在就会引起内存泄漏。

所幸,这个问题已经引起了官方的重视,在JDK 1.7中,对String的实现有了大幅度的调整。在新版本的String中,去掉了offset和count两项,而String的实质性内容仅仅由value决定,而value数组本身也就代表了这个String实际的取值。下面简单的对比String.length()方法来说明这个问题,代码如下:

//JDK 1.7 实现
public int length() {
return value.length;
} //JDK 1.6 实现
public int length() {
return count;
}

可以看到,在JDK 1.6中,String长度和value无关。基于这种改进的实现,substring()方法的内存泄漏问题也得以解决,如下代码所示,展示了JDK 1.7 中的String.substring()实现。

public String substring(int beginIndex, int endIndex) {
//省略部分无关内容
int subLen = endIndex - beginIndex;
//省略部分无关内容
return ((beginIndex == 0) && (endIndex == value.length)) ? this : new String(value, beginIndex, subLen);
} public String(char value[], int offset, int 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中,不再复用原String的value,而是将实际需要的部分做了复制,该问题也得到了完全的修复。

深入JVM-有关String的内存泄漏的更多相关文章

  1. jvm高级特性(1)(内存泄漏实例)

    jvm内存结构回顾: .8同1.7比,最大的差别就是:元数据区取代了永久代.元空间的本质和永久代类似,都是对JVM规范中方法区的实现. 不过元空间与永久代之间最大的区别在于:元数据空间并不在虚拟机中, ...

  2. 在 JNI 编程中避免内存泄漏

    JAVA 中的内存泄漏 JAVA 编程中的内存泄漏,从泄漏的内存位置角度可以分为两种:JVM 中 Java Heap 的内存泄漏:JVM 内存中 native memory 的内存泄漏. Java H ...

  3. 在 JNI 编程中避免内存泄漏与崩溃

    JNI 编程简介 JNI,Java Native Interface,是 native code 的编程接口.JNI 使 Java 代码程序可以与 native code 交互——在 Java 程序中 ...

  4. 解析Java的JNI编程中的对象引用与内存泄漏问题

    JNI,Java Native Interface,是 native code 的编程接口.JNI 使 Java 代码程序可以与 native code 交互——在 Java 程序中调用 native ...

  5. 运维-JVM监控之内存泄漏

    转载:https://blog.csdn.net/zdx_csdn/article/details/71214219 jmap -heap pid查看进程堆内存使用情况,包括使用的GC算法.堆配置参数 ...

  6. JVM的内存管理、对象的生命周期、内存泄漏

    1 JVM内存 分为“堆”.“栈”和“方法区”三个区域,分别用于存储不同的数据 1.1 堆 JVM在其内存空间开辟一个称为”堆”的存储空间,这部分空间用于存储使用new关键字所创建的对象. 1.2 栈 ...

  7. String中substring方法内存泄漏问题

    众所周知,JDK中以前String类中的substring方法存在内存泄漏问题,之所以说是以前,是因为JDK1.7及以后的版本已经修复了,我看都说JDK1.6的版本也存在这个问题,但是我本机上安装的1 ...

  8. JVM系列之六:内存溢出、内存泄漏 和 栈溢出

    1. OOM && SOF OutOfMemoryError异常: 除了程序计数器外,虚拟机内存的其他几个运行时区域都有发生OutOfMemoryError(OOM)异常的可能, 内存 ...

  9. JVM的堆内存泄漏排查-性能测试

    JVM异常说明 https://testerhome.com/articles/24259 一文中已介绍了,JVM每个运行时区域--程序计数器 .Java虚拟机栈.本地方法栈.Java堆.方法区.直接 ...

随机推荐

  1. Java Little Knowledge

    1.Constructor running order of Base class and Derived class This is Alibaba's audition problem. clas ...

  2. exce生成随机数

    有时候数据库没有数据,造数据专用. 来源于:http://jingyan.baidu.com/article/93f9803feba1f5e0e46f55f2.html 首先介绍一下如何用RAND() ...

  3. .Net Core 1.0.0 RC2安装及示例教程

    前几天微软发布了.Net Core1.0.0 RC2 Preview版本,一直都想尝试下跨平台的.Net Core,一直拖到今天,也参考了下园友们的经验,闲时整理了一下安装的步骤,供大家参考. 我们要 ...

  4. poj1144 求不同割点的个数

    Network Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 11914   Accepted: 5519 Descript ...

  5. zoj1492 最大团

    Maximum Clique Time Limit: 10 Seconds      Memory Limit: 32768 KB Given a graph G(V, E), a clique is ...

  6. 关于ArrayList 容量问题

    ArrayList 是实现List 接口的动态数组,即它的容量大小是可变的.允许包括null 在内的所欲元素. 每个ArrayList 实例都有一个容量,该容量是指用来存储列表元素的数组的大小.默认初 ...

  7. Java-URLConnection类详解

    抽象类 URLConnection 是所有类的超类,它代表应用程序和 URL 之间的通信链接.此类的实例可用于读取和写入此 URL 引用的资源.通常,创建一个到 URL 的连接需要几个步骤: open ...

  8. C#-WinForm-进程、线程

    进程:一个程序就是一个进程,也有可能一个程序需要多个进程来支持的情况,比如QQ 点击按钮打开记事本,静态方法 public partial class Form3 : Form { public Fo ...

  9. Unable to find element on closed window (WARNING: The server did not provide any stacktrace information)

    当你的selenium WebDriver 启动IE11报这个错时:Unable to find element on closed window (WARNING: The server did n ...

  10. Java多线程学习(吐血超详细总结)

    本文主要讲了java中多线程的使用方法.线程同步.线程数据传递.线程状态及相应的一些线程函数用法.概述等. 首先讲一下进程和线程的区别: 进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的 ...