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

作者:goldensun 时间:2022-11-13 07:58:12 

 这将会是一篇比较 * 的文章,当你想在某个人的生活中制造悲剧时你可能会去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来查找溢出的所在地。

标签:Java,内存溢出
0
投稿

猜你喜欢

  • Jenkins Pipeline 部署 SpringBoot 应用的教程详解

    2022-09-26 14:06:27
  • Java的运算符和程序逻辑控制你了解吗

    2023-01-19 10:01:59
  • 浅谈Java生命周期管理机制

    2022-02-21 19:07:47
  • 举例讲解Java的Spring框架中AOP程序设计方式的使用

    2022-11-21 21:22:15
  • Java 用Prometheus搭建实时监控系统过程详解

    2023-09-06 12:07:40
  • Android 中 Tweened animation的实例详解

    2022-12-12 15:28:06
  • 带大家认识Java语法之泛型与通配符

    2021-06-04 06:14:46
  • C# 使用Dictionary复制克隆副本及比较是否相等

    2021-05-29 21:33:06
  • Android实现长截屏功能

    2022-03-02 15:20:27
  • SpringMVC @RequestMapping注解详解

    2022-08-08 06:58:14
  • Java设计模式之动态代理模式实例分析

    2022-07-07 17:55:07
  • java中Hashmap的get方法使用

    2023-10-29 13:10:05
  • Java超详细讲解抽象类的原理与用法

    2022-10-31 20:51:42
  • c#文件的复制,移动,创建(实例代码)

    2023-05-29 21:49:14
  • Java面试重点中的重点之Elasticsearch核心原理

    2021-08-03 07:34:16
  • Java listener简介_动力节点Java学院整理

    2022-12-29 10:02:48
  • Java获取视频时长、大小的示例

    2023-01-16 01:48:44
  • Android 架构之数据库框架搭建

    2021-09-28 23:26:06
  • 详解Maven安装教程及是否安装成功

    2021-07-14 00:00:21
  • Android开发中ImageLoder进行图片加载和缓存

    2023-08-18 10:14:30
  • asp之家 软件编程 m.aspxhome.com