解读String字符串导致的JVM内存泄漏问题

 更新时间:2023年07月31日 14:17:02   作者:大力海棠  
这篇文章主要介绍了解读String字符串导致的JVM内存泄漏问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教

String类存在于java.lang包中,在程序里使用挺广泛的,用来创建一个字符串对象变量,Java内部对String类做了一些特殊的处理,例如把String类声明成final类型,也就是说不能有子类,String类型对象一旦创建后就不可改变(你可能会想不是可以拼接字符串吗,怎么不可以改变String类对象了,别急,下面慢慢看),以及一些针对JVM的优化等,先来简单看看String类在Java中的一些特点。

被定义为final类

在Java语言中,String类型被定义成final修饰,导致String类不能拥有其他子类,最主要的是出于安全方面问题,JDK中的一些核心类,包括String类,内部实现都不是Java语言,而是调用了系统本地的API,这些API较为底层,需要和操作系统打交道,所以为了安全起见,String类定义为final修饰,不允许被继承,也就不会被重写。

不可改变

String对象的不可改变,也就是不变性,指的是String对象一旦创建成功后,就不能再对它进行修改,

看一个例子:

程序中进行了一次字符串拼接,都在同一个String对象str上操作,虽然如此,但是可以看到两次输出的hashCode是不同的,原因是String对象一旦在JVM常量池里面被创建,那么它的地址就不会被修改,即使我们对对象进行拼接等修改,也只是把新的字符串保存到一个新的地址中去。

所以说,所谓的String不可改变,不是指的该类型实例对象的指向不可改变,我们可以把上面程序中的str对象指向新的字符串地址,但是原来的字符串还是存在于JVM常量池中,没有改变,不可改变指的就是这个,字符串一旦创建后,一直存在,不可修改,对象可以修改指向,不指向它的地址,一旦该字符串没有任何变量指向它后,就等着GC把它回收掉。

常量池优化

String对象不可改变的好处是多线程环境下访问安全,性能高,因为对象不可被修改,所以多线程对它的访问只能是读操作,多个读操作即使不加同步处理也不会出现修改数据导致不一致的问题,所以减少了许多不必要的同步操作,提高了性能。

可以来看一个简单的例子:

证明在JVM内部,当两个String类型对象存放相同的字符串时,它们在常量池内部的引用是一样的:

程序中,String对象str_1和str_2它们的字符串内容是相同的,这两个对象在创建时都各自在堆中分配了自己的空间,所以输出str_1 == str_2的结果为false。

之后我们通过String.intern()方法输出两者在常量池中的引用,发现是一样的,也就是说,两个不同的String对象,因为值相同,所以在常量池中引用的是同一个副本,这是一种常量池优化,为了节省内存空间。

String造成的内存泄漏

内存泄漏上一篇日志讲过,指的是程序由于一些设计上的问题或者执行过程中出错,在申请内存,使用完毕后没有释放资源,内存堆积越来越多,最后堆空间被占用完,具体的例子就是一些已经不再被使用的对象没有被回收,一直常驻在内存中。

虽然GC会帮助我们自动回收那些已经不再被使用的对象,但是如果程序的一些逻辑设计不当,仍然会出现内存泄漏问题,最后导致内存溢出。

举个例子:

如果String类的substring()方法使用不恰当,也有可能导致内存泄漏,不过这个问题随着JDK的更新,早已被修复了,还是总结一下,当作一种设计上的前车之鉴,提醒自己日后留意类似的这种情况(例如创建定长数组时)。

String结构

String.substring()方法导致内存泄漏问题与String类的结构有关,String的结构分为三部分组成,count长度,value数组和offset偏移量,假设有这么一种情况,String对象的value数组可以存储500个字符,count长度标识有10字节,那么这个String对象实际占用的空间是10个字节,剩下的490个字节空间就放在那里了,它们一直没有被使用,直到这个String对象被释放前,它们都会常驻在内存中。

回到String.substring()方法,它的内部实现是这样的:

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

可以看到方法内部对于一些边界情况抛出异常信息,最后调用了String类的构造函数,从传入的参数看,offset偏移量和count变量都发生了改变,但是value数组没有改变,使用的还是原来旧的引用,这么做的问题是,如果旧的String字符串被回收后,这个value值没有得到更新,而是跟着创建新的String对象,那么使用旧的value创建出来的新的String对象中多出来的原来已经被回收了的部分内存,就堆积在那里了,跟着新的对象常驻在内存中,随着字符串拼接操作,substring()方法被多次调用后,便可能会造成内存泄漏。

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • Java内存模型之重排序的相关知识总结

    Java内存模型之重排序的相关知识总结

    重排序是指编译器和处理器为了优化性能而对指令序列进行重新排序的一种手段,文中详细介绍了Java重排序的相关知识,需要的朋友可以参考下
    2021-06-06
  • JVM内存分配及String常用方法解析

    JVM内存分配及String常用方法解析

    这篇文章主要介绍了JVM内存分配及String常用方法解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-09-09
  • Java超详细分析抽象类和接口的使用

    Java超详细分析抽象类和接口的使用

    在类中没有包含足够的信息来描绘一个具体的对象,这样的类称为抽象类,接口是Java中最重要的概念之一,它可以被理解为一种特殊的类,不同的是接口的成员没有执行体,是由全局常量和公共的抽象方法所组成,本文给大家介绍Java抽象类和接口,感兴趣的朋友一起看看吧
    2022-04-04
  • Java中多媒体文件上传及页面回显的操作代码

    Java中多媒体文件上传及页面回显的操作代码

    这篇文章主要介绍了Java中多媒体文件上传及页面回显的操作代码,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-09-09
  • Java绘图技术基础(实例讲解)

    Java绘图技术基础(实例讲解)

    下面小编就为大家带来一篇Java绘图技术基础(实例讲解)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-08-08
  • javaweb实现百度GPS定位接口(经纬度)

    javaweb实现百度GPS定位接口(经纬度)

    这篇文章主要介绍了javaweb实现百度GPS定位接口(经纬度),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-02-02
  • 由@NotNull注解引出的关于Java空指针的控制

    由@NotNull注解引出的关于Java空指针的控制

    这是一些很容易学会的简单技术,但是对于代码质量和健壮性来说确实很重要。以我的经验,仅是第一个小技巧就已经对改进代码质量具有很大的作用了
    2016-09-09
  • 用递归查找有序二维数组的方法详解

    用递归查找有序二维数组的方法详解

    本篇文章是对用递归查找有序二维数组的方法进行了详细的分析介绍,需要的朋友参考下
    2013-06-06
  • MyBatis中#{}和${}有哪些区别

    MyBatis中#{}和${}有哪些区别

    大家好,本篇文章主要讲的是MyBatis中#{}和${}区别,感兴趣的同学赶快来看一看,对你有帮助的话记得收藏一下,方向下次浏览
    2021-12-12
  • springboot对接第三方微信授权及获取用户的头像和昵称等等

    springboot对接第三方微信授权及获取用户的头像和昵称等等

    这篇文章主要介绍了springboot对接第三方微信授权及获取用户的头像和昵称等等,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-01-01

最新评论