问题定位

由于应用频繁地Full gc,就dump了内存下来用MAT分析,发现有个map占用了98%的内存,于是找到这个map

private ConcurrentMap<String, String> nick2numid = new ConcurrentHashMap<String, String>();

存放的是nick与id的映射关系,从MAT中找到map的每一个entry如下图所示:

这里解释一下两个概念

Shallow Heap:对象占用了多少内存(单位:字节)

Retained Heap:如果对象被回收会释放多少内存,也就是对象hold住的内存

map的key是一个String类型,其Shallow Heap为32byte,Retained Heap为1104byte。一般对于String类型,具有不可变性,这两个值应该相等才对,带着疑惑找到了问题所在,分步描述如下:

1、从页面传了一个参数到后端,这个参数携带了cookie的内容,恰好就是1104byte这么长,不妨设置这个参数为cookie;

2、后端拿到cookie这个参数后,需要其中的nick的值,采用的是String类的split方法(&做为分隔符)得到一个数组,其中有一项的值为nick=xxx

3、将nick的值做为key放入nick2numid中

nick2numid.put("xxx",id)

最终发现问题出在String类的substring方法上?

分析问题

其实String的split方法上调用了substring方法,先来看看split的源码实现吧

public String[] split(String regex) {
return split(regex, 0);
}
public String[] split(String regex, int limit) {
return Pattern.compile(regex).split(this, limit);
} public String[] split(CharSequence input, int limit) {
ArrayList<String> matchList = new ArrayList<String>();
Matcher m = matcher(input); // Add segments before each match found
while(m.find()) {
if (!matchLimited || matchList.size() < limit - 1) {
String match = input.subSequence(index, m.start()).toString();
matchList.add(match);
index = m.end();
} else if (matchList.size() == limit - 1) { // last one
String match = input.subSequence(index,
input.length()).toString();
matchList.add(match);
index = m.end();
}
}
} public CharSequence subSequence(int beginIndex, int endIndex) {
return this.substring(beginIndex, endIndex);
}

String.split->Pattern.split->subSequence->substring

从以上代码可以看出String类的split方法确实调用了substring方法
下面来看看substring方法源码:

private final char value[];

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

注意value这个char数组,存放的是String每个字符的内容,substring直接依赖了这个数组。如果substring产生的字符串没有被java虚拟机回收,这个char数组也不会被回收。

问题回顾:应用中的nick2numId这个map生命周期很长,只有在用户退出的时候才会删除其中的entry项,恰好这个map非常大,dump内存的时候size已经达到几十万,相当于几十万个cookie被这个map所hold住,内存被耗尽产生Full gc。

解决方案

当时想到几种方案:
1、String.intern()?
2、拿到value数组产生一个新数组?
3、采用分布式缓存来存放nick与id的映射关系?

采用第一种方法是否可行?不可取,原因有2:

intern() 所使用的是一个全局的池,并不需要如此大作用域的缓存;

intern会向常量池中添加内容,持久代空间本来就很小,被nick所占用可能引起OutOfMemoryError!

后来采用的是第二种方法:new String(nick.toCharArray()),很快上线使内存利用率得到提升(fast,not the best)。

JVM中存放生命周期长的大map始终是一个隐患,说不定哪一天由于map元素过多或者删除不及时导致OOM,应尽快采用第3种方案:分布式缓存。nick放入缓存之后,cookie会很快被回收,而且系统的可用内存将会大大提高。

案例分析:java中substring引发的Full gc的更多相关文章

  1. java中substring和indexof() 和lastindexof()

    java中substring和indexof() 和lastindexof() str=str.substring(int beginIndex);截取掉str从首字母起长度为beginIndex的字 ...

  2. java中substring的使用方法

    java中substring的使用方法 str=str.substring(int beginIndex);截取掉str从首字母起长度为beginIndex的字符串,将剩余字符串赋值给str: str ...

  3. 从虚拟机指令执行的角度分析JAVA中多态的实现原理

    从虚拟机指令执行的角度分析JAVA中多态的实现原理 前几天突然被一个"家伙"问了几个问题,其中一个是:JAVA中的多态的实现原理是什么? 我一想,这肯定不是从语法的角度来阐释多态吧 ...

  4. Oracle trunc()函数,decode()函数,substr函数,GREATEST函数,java中substring函数的用法

    --Oracle trunc()函数的用法/**************日期********************/1.select trunc(sysdate) from dual --2013- ...

  5. 分析Java中的length和length()

    在不适用任何带有自动补全功能的IDE的情况下,我们怎么获取一个数组的长度?如何获取字符串的长度? 这里我们先举用实例去分析一下:int[] arr=new int[3]:System.out.prin ...

  6. 详细分析 Java 中实现多线程的方法有几种?(从本质上出发)

    详细分析 Java 中实现多线程的方法有几种?(从本质上出发) 正确的说法(从本质上出发) 实现多线程的官方正确方法: 2 种. Oracle 官网的文档说明 方法小结 方法一: 实现 Runnabl ...

  7. 详细分析 Java 中启动线程的正确和错误方式

    目录 启动线程的正确和错误方式 前文回顾 start 方法和 run 方法的比较 start 方法分析 start 方法的含义以及注意事项 start 方法源码分析 源码 源码中的流程 run 方法分 ...

  8. Java中substring函数的简单应用

    1.删掉一个字符串中的某个字符 /* * 使用Java 中的 substring()函数删掉字符串中的某个字符 * deleteAssignChar函数的参数说明: * str:被操作的字符串 * o ...

  9. 通过String的不变性案例分析Java变量的可变性

    阅读本文之前,请先看以下几个问题: 1.String变量是什么不变?final修饰变量时的不变性指的又是什么不变,是引用?还是内存地址?还是值? 2.java对象进行重赋值或者改变属性时在内存中是如何 ...

随机推荐

  1. JavaWeb学习过程 之c3p0的使用

    这几天在学习使用MVC模式来做几个小项目,在学习的过程中,用到了数据库连接池.便特意去学习了一下. 一.谈一谈为什么要使用数据库连接池 在开发基于数据库的web程序时,传统的模式(在servlet,b ...

  2. Hadoop学习笔记(1)概述

    写在学习笔记之前的话: 寒假已经开始好几天了,似乎按现在的时间算,明天就要过年了.在家的这几天,该忙的也都差不多了,其实也都是瞎忙.接下来的几点,哪里也不去了,静静的呆在家里学点东西.所以学习一下Ha ...

  3. 读取IOS的相应路径

    //    IOS相应路径 NSString* bundlePath = [[NSBundle mainBundle] bundlePath]; NSLog(@"bundlePath = % ...

  4. 一个Sqrt函数引发的血案(转)

    作者: 码农1946  来源: 博客园  发布时间: 2013-10-09 11:37  阅读: 4556 次  推荐: 41   原文链接   [收藏]   好吧,我承认我标题党了,不过既然你来了, ...

  5. android 自定义百度地图放大缩小

    自定义实现Android百度地图的缩放图标,需要自定义一个缩放控件,实现效果如下: 这里的缩放效果,实现了点击按钮可以对地图的放大缩小,通过手势放大与缩小也控制缩放图标的可用状态.具体实现如下: zo ...

  6. 《数字图像处理原理与实践(MATLAB版)》一书之代码Part6

    本文系<数字图像处理原理与实践(MATLAB版)>一书之代码系列的Part6,辑录该书第281至第374页之代码,供有须要读者下载研究使用.代码运行结果请參见原书配图,建议下载代码前阅读下 ...

  7. HTML之标签

    一.HTML 标签 HTML 标记标签通常被称为 HTML 标签 (HTML tag). •HTML 标签是由尖括号包围的关键词,比如 <html> •HTML 标签通常是成对出现的,比如 ...

  8. linux命令:rsync, 同步文件和文件夹的命令

    Usage: rsync [OPTION]... SRC [SRC]... DEST  or   rsync [OPTION]... SRC [SRC]... [USER@]HOST:DEST  or ...

  9. Request.ServerVariables["Url"]

    Request.ServerVariables["Url"] 返回服务器地址 Request.ServerVariables["Path_Info"] 客户端提 ...

  10. FFTW程序Demo

    #include<stdio.h> #include<stdlib.h> #include <fftw3.h> #include<string.h> # ...