java.lang.OutOfMemoryError: Java heap space
程序员文章站
2022-06-13 21:29:04
...
java.lang.OutOfMemoryError: Java heap space
异常描述
java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:2367) ~[na:1.7.0_55]
at java.lang.AbstractStringBuilder.expandCapacity(AbstractStringBuilder.java:130) ~[na:1.7.0_55]
at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:114) ~[na:1.7.0_55]
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:415) ~[na:1.7.0_55]
at java.lang.StringBuilder.append(StringBuilder.java:132) ~[na:1.7.0_55]
at java.lang.StringBuilder.append(StringBuilder.java:128) ~[na:1.7.0_55]
at java.util.AbstractMap.toString(AbstractMap.java:523) ~[na:1.7.0_55]
at com.xxx.xxx.redis.JedisManager.setHash(JedisManager.java:84)
异常代码
// 向 Redis 中存入数据,并设置失效时间
protected boolean setHash(String key, Map<String, String> map, int exp) {
boolean ret = jedisClient.setHash(key, exp, map);
log.debug("Redis Cache Hash Object: [" + map.toString() + "]");
return ret;
}
业务场景
- 增加检索功能,数据量很大,若通过DB 中的 like 操作,访问 DB 过于频繁,增加数据库压力;且无法完成多维度检索
- 预先按照将多个维度按顺序将数据初始化到Redis中,比如 A分类 B品类 C城市 D名称 E型号
- 通过 hscan redis_key cursor(游标,通常取0) count pageSize(分页每页数据数量) match regex(正则) 命令匹配关键字
问题场景
- 测试环境没有复现出上述问题,因为初始化的数据量太小,没有达到预期的峰值;等发布生产后,初始化生产环境数据时,发现初始化的操作不仅超时而且需要查询大量的表中数据,占用数据库的连接,而且代码中出现很多很多的问题
- 通过日志查询到上述问题,当时误以为是应用服务器空间溢出,赶紧检查服务是否正常;Redis服务器也无明显异常
- 预期数据量很大,但开发过程中同事没有正确认识到问题,曾提示过,生产的数据量起码是测试的几百倍;要做适当的启动多个线程分批处理,将数据分散,但均以时间很紧为由拒绝了我的提议,导致发布生产时耽误了大量时间
- 临时解决方案,将原本的多个初始化任务,拆分成多个,每次运营一部分,代码实现中将各个部分冗余在一起了,注释掉一部分发版,执行,再注释,再发。。。
问题分析
- 从日志中分析,是 map.toString() 方法发生异常
- 多态:方法传参 Map
public class Test1 {
public static void main(String[] args) {
Map<String,String> hashMap = new HashMap<>();
map(hashMap); // 输出 hashMap
Map<String,String> linkedHashMap = new LinkedHashMap<>();
map(linkedHashMap); // 输出 linkedHashMap hashMap
// 因为 LinkedHashMap<K,V> extends HashMap<K,V>
// 所以 LinkedHashMap 也是 HashMap
}
public static void map(Map<String,String> map){
if(map instanceof LinkedHashMap){
System.out.println("linkedHashMap");
}
if(map instanceof HashMap){
System.out.println("hashMap");
}
}
}
- map.toString()
// 1.此处 map 为 hashMap ,查看hashMap的toString方法
public final String toString() {
return getKey() + "=" + getValue();
}
// 该 toString 方法是 HashMap 内部类 Entry 的方法,而不是 HashMap 的
// HashMap 的继承关系
public class HashMap<K,V> extends AbstractMap<K,V>
// 此处调用 AbstractMap 的 toString 方法
// 2.AbstractMap toString()
// 通过 StringBuilder 将 key 与 value 拼接
public String toString() {
Iterator<Entry<K,V>> i = entrySet().iterator();
if (! i.hasNext())
return "{}";
StringBuilder sb = new StringBuilder();
sb.append('{');
for (;;) {
Entry<K,V> e = i.next();
K key = e.getKey();
V value = e.getValue();
sb.append(key == this ? "(this Map)" : key);
sb.append('=');
sb.append(value == this ? "(this Map)" : value);
if (! i.hasNext())
return sb.append('}').toString();
sb.append(',').append(' ');
}
}
// 3.StringBuilder append
public StringBuilder append(String str) {
super.append(str);
return this;
}
// 调用 AbstractStringBuilder append 方法
public AbstractStringBuilder append(String str) {
if (str == null) str = "null";
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0)
expandCapacity(minimumCapacity);
}
void expandCapacity(int minimumCapacity) {
int newCapacity = value.length * 2 + 2;
if (newCapacity - minimumCapacity < 0)
newCapacity = minimumCapacity;
if (newCapacity < 0) {
if (minimumCapacity < 0) // overflow
throw new OutOfMemoryError();
newCapacity = Integer.MAX_VALUE;
}
value = Arrays.copyOf(value, newCapacity);
}
// 4.Arrays copy()
public static char[] copyOf(char[] original, int newLength) {
char[] copy = new char[newLength];
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
// 此处创建新的 char[] 数据出现栈溢出异常
- 数据长度
public class Test {
public static void main(String[] args) {
char[] ch = new char[999999999];
ch[0] = 'x';
String s = new String(ch);
System.out.println(s);
}
}
问题总结
- StringBuilder:append操作,每次append都在扩容,与+相比较创建对象的次数的区别
- ”+” 号连接:每一次+操作相当于 new StringBuilder(“a”).append(“b”) ;每一次都会创建新的对象
- 扩容: 添加的字符串的长度 + 原来的字符串中包含的元素个数 > 数组的容量(AbstractStringBuilder 内部维护了一个 char[] value)
- 数组的长度:不是无限大小的,有限制;因为长度是int类型,上限 Integer.MAX_VALUE
- 日志级别:设置了日志的基本级别为info级别,此处的debug是否执行
- debug 执行,执行debug这行代码包括map.toString,但控制台不打印
- 服务器内存使用情况查询
- free
- JVM工具使用(Java VirtualVM):本地模拟如果发生栈溢出,GC的过程
推荐阅读
-
基于java中stack与heap的区别,java中的垃圾回收机制的相关介绍
-
eclipse scala Could not reserve enough space for object heap
-
如何给女朋友讲明白:Java 中 Stack(栈) 与 Heap(堆)
-
java中程序上线报错: tomcat中java.lang.OutOfMemoryError: PermGen space
-
spark大批量读取Hbase时出现java.lang.OutOfMemoryError: unable to create new native thread
-
Error occurred during initialization of VM Could not reserve enough space for 2097152KB object heap
-
在Intellij IDEA下编译Java项目,报错:java.lang.OutOfMemoryError: ...(此处忽略) GC overhead lim
-
Tomcat同时部署多个应用——内存溢出(java.lang.OutOfMemoryError: PermGen space)的解决办法
-
java.lang.OutOfMemoryError: PermGen space及其解决方法
-
ES内存溢出,报错:java.lang.OutOfMemoryError: Java heap space