摘要:如果在程序中创建了比较大的对象,并且我们基于这个大对象生成了一些其他的信息,此时,一定要释放和这个大对象的引用关系,否则,就会埋下内存溢出的隐患。

本文分享自华为云社区《【高并发】你敢信?String类竟然是导致生产环境频繁内存溢出的罪魁祸首!!》,作者: 冰 河 。

最近,一名小伙伴跟我说:他写的程序在测试环境一点问题没有,但是发到生产环境却会频繁出现内存溢出的情况,这个问题都困扰他一周多了。于是乎,周末我便开始帮他排查各种问题。

小伙伴的疑问

问题确定

在排查问题的过程中,我发现这位小伙伴使用的JDK还是1.6版本。开始,我也没想那么多,继续排查他写的代码,也没找出什么问题。但是一旦启动生产环境的程序,没过多久,JVM就抛出了内存溢出的异常。

这就奇怪了,怎么回事呢?

启动程序时加上合理的JVM参数,问题依然存在。。。

没办法,继续看他的代码吧!无意间,我发现他写的代码中,大量使用了String类的substring()方法来截取字符串。于是,我便跟到JDK中的代码查看传递进来的参数。

这无意间点进来的一次查看,竟然找到了问题所在!!

JDK1.6中String类的坑

经过分析,竟然发现了JDK1.6中String类的一个大坑!为啥说它是个坑呢?就是因为它的substring()方法会把人坑惨!不多说了,我们先来看下JDK1.6中的String类的substring()方法。

public String substring(int bedinIndex, 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);
}

接下来,我们来看看JDK1.6中的String类的一个构造方法,如下所示。

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

看到,这里,相信细心的小伙伴已经发现了问题,导致问题的罪魁祸首就是下面的一行代码。

this.value = value;

在JDK1.6中,使用 String 类的构造函数创建子字符串的时候,并不只是简单的拷贝所需要的对象,而是每次都会把整个value引用进来。如果原来的字符串比较大,即使这个字符串不再被应用,这个字符串所分配的内存也不会被释放。 这也是我经过长时间的分析代码得出的结论,确实是太坑了!!

既然问题找到了,那我们就要解决这个问题。

升级JDK

既然JDK1.6中的String类存在如此巨大的坑,那最直接有效的方式就是升级JDK。于是,我便跟小伙伴说明了情况,让他将JDK升级到JDK1.8。

同样的,我们也来看下JDK1.8中的String类的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);
}

在JDK1.8中的String类的substring()方法中,也调用了String类的构造方法来生成子字符串,我们来看看这个构造方法,如下所示。

public String(char value[], int offset, int count) {
if (offset < 0) {
throw new StringIndexOutOfBoundsException(offset);
}
if (count <= 0) {
if (count < 0) {
throw new StringIndexOutOfBoundsException(count);
}
if (offset <= value.length) {
this.value = "".value;
return;
}
}
// 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);
}

在JDK1.8中,当我们需要一个子字符串的时候,substring 生成了一个新的字符串,这个字符串通过构造函数的 Arrays.copyOfRange 函数进行构造。这个是没啥问题。

优化JVM启动参数

这里,为了更好的提升系统的性能,我也帮这位小伙伴优化了JVM启动参数。

经小伙伴授权, 我简单列下他们的业务规模和服务器配置:整套系统采用分布式架构,架构中的各业务服务采用集群部署,日均访问量上亿,日均交易订单50W~100W,订单系统的各服务器节点配置为4核8G。目前已将JDK升级到1.8版本。

根据上述条件,我给出了JVM调优后的参数配置。

-Xms3072M -Xmx3072M -Xmn2048M -Xss1M -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M

至于,为啥会给出上述JVM参数配置,后续我会单独写文章来具体分析如何根据实际业务场景来进行JVM参数调优。

经过分析和解决问题,小伙伴的程序在生产环境下运行的很平稳,至少目前还未出现内存溢出的情况!!

结论

如果在程序中创建了比较大的对象,并且我们基于这个大对象生成了一些其他的信息,此时,一定要释放和这个大对象的引用关系,否则,就会埋下内存溢出的隐患。

JVM优化的目标就是:尽可能让对象都在新生代里分配和回收,尽量别让太多对象频繁进入老年代,避免频繁对老年代进行垃圾回收,同时给系统充足的内存大小,避免新生代频繁的进行垃圾回收。

点击关注,第一时间了解华为云新鲜技术~

生产环境频繁内存溢出,原来就是因为这个“String类”的更多相关文章

  1. 生产环境-jvm内存溢出-jprofile问题排查

    首先线上开启了dump的参数 dump的内容有2G,先进行压缩打包,传输至本地(scp) tar -czvf dump.tar java_pid4824.hprof  使用Jprofile打开dump ...

  2. 解决Vue编译和打包时频繁内存溢出情况CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory

    解决Vue编译和打包时频繁内存溢出情况CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory 如上图所示:频繁出现此 ...

  3. Vue项目运行或打包时,频繁内存溢出情况CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory

    前端使用基于vue的Nuxt框架,但是随着项目功能增多,项目变大,频繁出现此种情况,原因是项目太大,导致内存溢出,排除代码问题外,可参照以下方式解决 解决方案 1.全局安装increase-memor ...

  4. NC 解决启动环境报内存溢出问题

    java heap space 内存溢出 解决方法如下: 在eclipse中,window-->preferences-->Java-->Installed JREs选中JRE 点击 ...

  5. 记一次线上环境的内存溢出(java.lang.OutOfMemoryError)

    事故背景 今天客户说风控项目有个别用户查询不到数据不是报错就是一直卡在那里,我就去那个接口看了下. 一看项目日志今天的都几个g了,平常也就几百兆吧,很明显出了问题. 请求接口后使用命令tail -f ...

  6. C++_类和动态内存分配2-改进后的String类

    添加前面介绍过的复制构造函数和赋值运算符,使类能够正确管理类对象使用的内存. 知道对象何时被创建和释放. =================================== 修订后的默认构造函数 ...

  7. JVM(五) 生产环境内存溢出调优

    1.gc配置参数 1.1 控制台打印gc日志 -verbose:gc -XX:+PrintGCDetails -XX:+PrintHeapAtGC(详细的gc信息) 1.2 输出gc日志到指定文件 - ...

  8. Tomcat常见问题[内存溢出,虚拟目录配置等](一)

    一.Tomcat内存溢出的解决方法 内存溢出一般有如下三种常见的原因: OutOfMemoryError: Java heap space OutOfMemoryError: PermGen spac ...

  9. jmap MAT内存溢出实践

    jmap MAT内存溢出实践 一.创建Spring Boot工程 进入https://start.spring.io/网站,配置如下图 点击创建工程,然后用Idea或者Eclipse打开 二.创建模拟 ...

随机推荐

  1. Redis 中 String 类型的内存开销比较大

    使用 String 类型内存开销大 1.简单动态字符串 2.RedisObject 3.全局哈希表 使用 Hash 来存储 总结 参考 使用 String 类型内存开销大 如果我们有大量的数据需要来保 ...

  2. mysql is not null 执行效率低

    a表字段类型为int b表字段类型为varchar a left join b时如果查询条件是is not null,效率很快,is  null 效率很低 后来两者改为同类型 int,效率很高

  3. WPF 自定义文本框输入法 IME 跟随光标

    本文告诉大家在 WPF 写一个自定义的文本框,如何实现让输入法跟随光标 本文非小白向,本文适合想开发自定义的文本框,从底层开始开发的文本库的伙伴.在开始之前,期望了解了文本库开发的基础知识 本文实现的 ...

  4. 攻防世界MISC进阶之签到题

    攻防世界MISC进阶之签到题    第一步:分析    第二步:实操    第三步:答案第一步:分析难度系数:1星题目来源: SSCTF-2017题目描述:SSCTF线上选举美男大赛开始了,泰迪拿着他 ...

  5. BI平台能做什么,有哪些功能呢?

    ​相信接触过数据分析工作的小伙伴们,对BI平台并不陌生.BI(Business Intelligence),也就是商业智能,它是一个完整的解决方案,可以有效地整合企业中的现有数据.BI通常被理解为将企 ...

  6. 替代Tableau,思迈特软件Smartbi让Excel成为企业级自助分析平台

        谈到企业级自助分析平台,大家自然会想到Tableau,在Garnter最新的BI平台魔力象限中,是这么描述Tableau的. "Tableau is a Leader in this ...

  7. Hive常用函数大全-字符串函数

    1.字符串长度函数:length(X)(返回字符串X的长度) select length('qwerty') from table --6 2.字符串反转函数:reverse(X)(返回字符串X反转的 ...

  8. Linux 查看网卡的MAC地址

    转至:https://www.cnblogs.com/kerrycode/p/13709537.html 如何使用命令查看网卡的MAC地址呢? 下面总结一下查看网卡MAC地址的方式. 1:ifconf ...

  9. 前端人员不要只知道KFC,你应该了解 BFC、IFC、GFC 和 FFC

    前言 说起KFC,大家都知道是肯德基,但面试官问你什么是BFC.IFC.GFC和FFC的时候,你是否能够像回答KFC是肯德基时的迅速,又或者说后面这些你根本就没听说过,作为一名前端开发工程师,以上这些 ...

  10. C# Event (1) —— 我想搞个事件

    本文地址:https://www.cnblogs.com/oberon-zjt0806/p/15975299.html 本文最初来自于博客园 本文遵循CC BY-NC-SA 4.0协议,转载请注明出处 ...