substring内存泄露原因及解决

本文分析了JDK1.6中使用substring方法引发内存溢出的原因及解决方案,通过对比JDK1.7的改进说明了如何避免此类问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题描述

在JDK1.6版本下,运行substring方法报内存泄露,已被Java官方标记,1.7版本已经修复,但是因为公司老项目不肯升JDK,还是要把这个错误拉出来遛一遛。

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.lang.OutOfMemoryError: Java heap space

源码分析

  • value 字符数组,存储字符串实际的内容
  • offset 该字符串在字符数组value中的起始位置
  • count 字符串包含的字符的长

JDK 1.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);
}

String对应的构造方法

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

当我们调用substring方法时,并没有创建一个新的长度为2的新字符串,而是在原有字符串的基础上修改了offset和count。
因为新字符串的生命周期长于代码执行期间,当GC将原字符串的内存回收掉之后,因为新字符串仍旧存在,1gb的内存占用仍然存在。最后导致内存溢出。

substring方法生成的新旧字符串共享内容数组避免了方法调用时字符数组复制,使得代码运行时有了更高的速度,但在特殊情况下就导致了 OOM异常

解决方法

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

新字符串littleString不再和原字符串共享内容,而调用substring的新旧字符串都会被GC回收掉,就不会产生上面的问题。

Java7的解决方式

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);
}

构造方法

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);
}

是不是内存泄漏

顺便讲一下Java内存泄漏到底是什么bug。
内存泄漏是指对象的生命周期已经结束了,应该被GC回收的时候无法被回收掉的bug。
那这个substring的bug产生原因显然不是因为生命周期结束了无法回收,而是因为对象还处在被引用的状态,GC无法回收。如果整个方法能够顺利执行完毕,新旧字符串的引用都会被回收掉。
所以严格意义上,这个应该不算内存泄漏bug。

附上版权声明:
原作者:Vi_error,博客地址:Vi_error.nextval
转载请保持署名和注明原地址

<think>嗯,用户问的是如何排查MySQL服务器的内存泄漏问题。这个问题看起来有点复杂,但让我慢慢理清楚思路。 首先,我需要回忆一下内存泄漏可能的表现。MySQL内存泄漏通常表现为内存使用量持续增长,即使在没有高负载的情况下,最终可能导致服务器崩溃或者被OOM Killer终止。那用户可能已经观察到内存使用不断上升,需要找出原因。 接下来,我应该考虑从哪里开始排查。通常,第一步是确认是否真的是内存泄漏,而不是正常的内存使用。比如,MySQL的缓冲池(InnoDB Buffer Pool)等组件会预先分配大量内存,这是正常现象。所以,需要区分正常使用和泄漏。 然后,检查操作系统级别的内存监控。使用top、htop或者free -m命令查看内存使用情况,特别是RES(常驻内存)和VIRT(虚拟内存)的值。如果RES持续增长,可能存在泄漏。另外,可以通过/proc/meminfo来查看更详细的内存信息,比如Slab内存是否异常增加,这可能和内核内存分配有关。 接下来,查看MySQL的内部内存使用情况。MySQL提供了一些性能模式(Performance Schema)和系统变量来监控内存分配。比如,启用Performance Schema中的内存统计功能,可以查看各个组件的内存使用情况。需要检查是否启用了performance_schema=ON,并且设置instruments和consumers来收集内存数据。 然后,用户可能需要执行一些SQL查询来获取内存分配的具体情况。例如,使用SELECT * FROM sys.memory_global_by_current_bytes WHERE current_alloc > 100*1024*1024; 这样的查询,找出占用内存较多的组件。这有助于定位是哪个部分的内存没有释放。 另外,检查MySQL的错误日志也是重要的步骤。可能有相关的警告或错误信息,提示内存分配失败或者泄漏的线索。需要关注是否有重复的错误消息,比如无法分配内存等。 接下来,考虑第三方工具,如Valgrind,但这对生产环境可能不适用,因为会影响性能。不过,如果是测试环境,可以用Valgrind启动MySQL,检测内存泄漏。 同时,分析代码或版本问题。如果MySQL的某个特定版本存在已知的内存泄漏问题,升级到修复版本可能解决问题。需要查看官方文档或社区报告,确认是否存在已知问题。 然后,检查插件和自定义代码。用户可能安装了第三方插件、存储引擎或UDF,这些可能存在内存管理问题。禁用这些组件,观察内存是否稳定,可以帮助排查问题。 还要检查连接和会话的内存使用。比如,每个连接可能会分配一些内存,如果连接数过多或存在连接泄漏,会导致内存增长。需要监控线程和连接数,使用SHOW PROCESSLIST查看是否有异常连接。 此外,临时表和排序操作也可能占用大量内存,尤其是在处理复杂查询时。检查是否有大量的磁盘临时表创建,或者需要调整sort_buffer_size、join_buffer_size等参数。 最后,如果上述步骤无法确定问题,可能需要联系官方支持或社区寻求帮助,提供相关的日志和诊断信息。 在回答时,需要将这些步骤结构化,逐步引导用户排查,从确认问题存在,到系统监控,再到MySQL内部诊断,最后到高级工具和社区支持。同时,提醒用户谨慎调整参数,并在生产环境中做好备份和测试。 还要注意用户的实际情况,可能对某些步骤不熟悉,需要给出具体的命令和查询示例,比如如何启用Performance Schema,如何执行特定的SQL语句。同时,强调区分正常内存使用和泄漏的重要性,避免用户误判。 总之,回答需要条理清晰,逐步推进,结合多种排查手段,帮助用户系统地定位和解决MySQL内存泄漏问题。</think>排查MySQL服务器内存泄漏需要结合系统监控、MySQL内部诊断及工具分析,以下是分步骤排查指南: --- ### **一、确认内存泄漏现象** 1. **监控系统内存使用** ```bash # 实时查看内存变化(观察RES列) top -c -p $(pgrep mysqld) # 或使用周期性采集 free -m -s 5 ``` - **关键指标**:若`RES`(物理内存占用)持续增长且不释放,可能存在泄漏。 2. **检查MySQL内存配置** ```sql SHOW VARIABLES LIKE '%buffer%'; SHOW VARIABLES LIKE '%cache%'; ``` - 确认`innodb_buffer_pool_size`等配置是否合理,避免误将正常缓存分配视为泄漏。 --- ### **二、定位MySQL内部内存分配** 1. **启用Performance Schema** ```ini # 在my.cnf中启用并重启MySQL [mysqld] performance_schema=ON ``` ```sql -- 开启内存监控 UPDATE performance_schema.setup_instruments SET ENABLED = 'YES' WHERE NAME LIKE 'memory/%'; UPDATE performance_schema.setup_consumers SET ENABLED = 'YES' WHERE NAME LIKE 'memory%'; ``` 2. **查询内存分配详情** ```sql -- 查看内存占用排名(单位:MB) SELECT SUBSTRING_INDEX(event_name, '/', 2) AS category, ROUND(SUM(current_alloc)/1024/1024, 2) AS current_alloc_mb FROM sys.memory_global_by_current_bytes GROUP BY category ORDER BY current_alloc_mb DESC; ``` - **重点关注**:异常增长的组件(如`Temporary`、`Query Cache`)。 --- ### **三、检查常见泄漏场景** 1. **连接泄漏** ```sql SHOW STATUS LIKE 'Threads_connected'; SHOW PROCESSLIST; ``` - 若连接数持续增长,检查应用是否未正确关闭连接。 2. **临时表与排序** ```sql SHOW STATUS LIKE 'Created_tmp%'; SHOW STATUS LIKE 'Sort%'; ``` - 大量磁盘临时表(`Created_tmp_disk_tables`)可能因`tmp_table_size`过小导致内存溢出。 3. **插件与存储引擎** ```sql SHOW PLUGINS; SELECT * FROM information_schema.ENGINES; ``` - 禁用第三方插件(如审计插件)观察内存是否稳定。 --- ### **四、高级诊断工具** 1. **Valgrind检测(仅测试环境)** ```bash valgrind --leak-check=full mysqld --console ``` - 输出内存泄漏堆栈信息(需调试符号支持)。 2. **分析核心转储** ```bash # 生成核心转储文件 gcore $(pgrep mysqld) # 使用gdb分析 gdb /usr/sbin/mysqld core.dump ``` - 结合`malloc_zone`或`jemalloc`工具检查堆内存。 --- ### **五、修复与优化** 1. **升级MySQL版本** - 确认是否遇到已知内存泄漏Bug(参考[MySQL Release Notes](https://dev.mysql.com/doc/relnotes/mysql/))。 2. **调整配置参数** ```ini # 限制全局内存(示例) [mysqld] table_open_cache=2000 thread_cache_size=100 max_connections=500 ``` --- ### **六、持续监控** ```bash # 使用Prometheus+Grafana监控 mysql> SELECT * FROM sys.memory_global_total; ``` --- ### **总结** 1. 优先区分正常缓存与泄漏(如`InnoDB Buffer Pool`初始化后应稳定)。 2. 从系统层到MySQL内部逐层缩小范围。 3. 生产环境慎用Valgrind,建议通过Performance Schema和错误日志分析。 若问题仍未解决,可提供以下信息寻求进一步帮助: - MySQL版本、错误日志片段、`SHOW ENGINE INNODB STATUS`输出、内存监控图表。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值