编写Java代码制造一个内存溢出的情况

 更新时间:2015年07月10日 12:02:40   投稿:goldensun  
这篇文章主要介绍了编写Java代码制造一个内存溢出的情况,或许这种有意制造能够更好地帮助理解Java中的内存溢出情况XD 需要的朋友可以参考下

 这将会是一篇比较邪恶的文章,当你想在某个人的生活中制造悲剧时你可能会去google搜索它。在Java的世界里,内存溢出仅仅只是你在这种情况下可能会引入的一种bug。你的受害者会在办公室里度过几天甚至是几周的不眠之夜。

在这篇文章中我将会介绍两种溢出方式,它们都是比较容易理解和重现的。并且它们都是来源现实项目的案例研究,但是为了让你清晰地掌握,我把它们简化了。

不过放心,在我们遇到和解决了很过溢出bug之后,类似的案例将会比你想象得更加普遍。

先来一个进入状态的,在使用HashSet/HashMap时,所用键值没有或者其equals()/hashCode()方法不正确,这会导致一个臭名昭著的错误。
 

class KeylessEntry {
 
  static class Key {
   Integer id;
 
   Key(Integer id) {
     this.id = id;
   }
 
   @Override
   public int hashCode() {
     return id.hashCode();
   }
  }
 
  public static void main(String[] args) {
   Map m = new HashMap();
   while (true)
     for (int i = 0; i < 10000; i++)
      if (!m.containsKey(i))
        m.put(new Key(i), "Number:" + i);
  }
}

当你运行上面的代码时,你可能会期望它运行起来永远不会出问题,毕竟内置的缓存方案只会增加到10,000个元素,然后就不会再增加了,所有的key都已经出现在 HashMap中。然而,事情并非如此。元素将会一直增长, 因为Key这个类没有在hashCode()后实现一个合适的equals()方法。


解决方法很简单,只要和下面的示例一样添加一个equals方法就可以了。但是在找到问题所在之前,你肯定已经花费了不少宝贵的脑细胞。
 

@Override
public boolean equals(Object o) {
  boolean response = false;
  if (o instanceof Key) {
   response = (((Key)o).id).equals(this.id);
  }
  return response;
}

下一个你得提醒朋友的是和String处理相关的操作。它的表现会很诡异,特别是结合JVM版本差异的时候。String的内部工作机制在 JDK 7u6中被改变了,所以如果你发现产品环境只是小版本号的区别,那么你已经准备好条件了。把类似下面的代码给你的朋友调试,然后问他为什么这个bug只会在产品中出现。
 

class Stringer {
  static final int MB = 1024*512;
 
  static String createLongString(int length){
   StringBuilder sb = new StringBuilder(length);
   for(int i=0; i < length; i++)
     sb.append('a');
   sb.append(System.nanoTime());
   return sb.toString();
  }
 
  public static void main(String[] args){
   List substrings = new ArrayList();
   for(int i=0; i< 100; i++){
     String longStr = createLongString(MB);
     String subStr = longStr.substring(1,10);
     substrings.add(subStr);
   }
  }
}

上面的代码出了什么问题呢?当它在JDK 7u6之前的版本上运行的时候,返回的字符串将会保存一个对那个1M左右大小的字符串的引用,如果你运行的时候设置为-Xmx100m,你会得到一个意想不到的oom错误。结合你实验环境中平台和版本的差异,伤脑经的事情就产生了。

现在如果你想掩盖你的足迹,我们可以引进一些更加高级的概念。比如

  •     在不同的类加载器中载入有破坏性的代码,在加载的类被原始类加载器删除后保持对它的引用,可以模拟一个类加载器溢出
  •     把攻击性的代码隐藏在finalize方法中,使得程序表现变得不可预测
  •     在一个长期运行的线程中加入棘手的组合,它可能在ThreadLocals中保存了一些可以被线程池访问的东西,以便管理应用线程。


我希望我们给了你一些思考的原材料以及当你想修理某人时的一些素材。这将带来无穷无尽的调试。除非你的朋友使用 Plumbr来查找溢出的所在地。

相关文章

  • IDEA实现序列化时如何自动生成serialVersionUID的步骤

    IDEA实现序列化时如何自动生成serialVersionUID的步骤

    这篇文章主要介绍了IDEA实现序列化时如何自动生成serialVersionUID的步骤,首先安装GenerateSerialVersionUID插件,当出现添加serialVersionUID选项,选中则会自动生成serialVersionUID,感兴趣的朋友一起学习下吧
    2024-02-02
  • Java中多种循环Map的常见方式详解

    Java中多种循环Map的常见方式详解

    Java中的Map是一种键值对存储的数据结构,其中每个键都唯一,与一个值相关联,下面这篇文章主要给大家介绍了关于Java中多种循环Map的常见方式,文中给出了详细的代码示例,需要的朋友可以参考下
    2024-01-01
  • java使用链表来模拟栈的入栈出栈操作实例代码

    java使用链表来模拟栈的入栈出栈操作实例代码

    这篇文章主要介绍了java 使用链表来模拟栈的入栈出栈操作,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-03-03
  • SpringBoot中的@RestControllerAdvice注解详解

    SpringBoot中的@RestControllerAdvice注解详解

    这篇文章主要介绍了SpringBoot中的@RestControllerAdvice注解详解,RestControllerAdvice注解用于创建全局异常处理类,用于捕获和处理整个应用程序中的异常,需要的朋友可以参考下
    2024-01-01
  • java操作ftp下载文件示例

    java操作ftp下载文件示例

    这篇文章主要介绍了java操作ftp下载文件的示例,需要的朋友可以参考下
    2014-02-02
  • Spring Cloud Gateway编码实现任意地址跳转的示例

    Spring Cloud Gateway编码实现任意地址跳转的示例

    本文主要介绍了Spring Cloud Gateway编码实现任意地址跳转的示例,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-12-12
  • Springboot中@scheduled注解解析

    Springboot中@scheduled注解解析

    这篇文章主要介绍了Springboot中@scheduled注解解析,定时任务就是在指定时间执行程序,或周期性执行计划任务,Java中实现定时任务的方法有很多,本文从从JDK自带的一些方法来实现定时任务的需求,需要的朋友可以参考下
    2023-09-09
  • MongoDB 整合SpringBoot举例介绍

    MongoDB 整合SpringBoot举例介绍

    这篇文章主要介绍了MongoDB 整合SpringBoot的相关知识,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧
    2025-05-05
  • SpringBoot如何监控Redis中某个Key的变化(自定义监听器)

    SpringBoot如何监控Redis中某个Key的变化(自定义监听器)

    这篇文章主要介绍了SpringBoot如何监控Redis中某个Key的变化(自定义监听器),具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-09-09
  • 关于快速测试API接口的一个新技能

    关于快速测试API接口的一个新技能

    这篇文章主要给大家介绍了关于快速测试API接口的一个新技能,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2018-06-06

最新评论