当前位置: 首页 > 编程笔记 >

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

周阳波
2023-03-14
本文向大家介绍编写Java代码制造一个内存溢出的情况,包括了编写Java代码制造一个内存溢出的情况的使用技巧和注意事项,需要的朋友参考一下

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

 类似资料:
  • JNIEXPORT jint JNICALL Java_nc_mes_pub_hardware_PCI1761_readChanel(JNIEnv*,jobject,jint channel){ }

  • 我想格式化和写入一个大的(1.785.530条目)的内容到一个文本文件。大约85%的条目被处理后,它变得非常慢,然后我得到一个。 即使我: 在我的

  • 在Scrapy中,类似Requests, Response及Items的对象具有有限的生命周期: 他们被创建,使用,最后被销毁。 这些对象中,Request的生命周期应该是最长的,其会在调度队列(Scheduler queue)中一直等待,直到被处理。 更多内容请参考 架构概览 。 由于这些Scrapy对象拥有很长的生命,因此将这些对象存储在内存而没有正确释放的危险总是存在。 而这导致了所谓的”内

  • 内存溢出 内存泄漏指由于疏忽或错误造成程序未能释放已经不再使用的内存。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费。 内存泄漏通常情况下只能由获得程序源代码的程序员才能分析出来, 也是一个比较难以排查的问题。所有需要在开发前知道一些规范 内存溢出一直向一个 属性/变量 写数据 , 写入超过内存最大限

  • 目标 遵循类大小、方法大小和方法名称的最佳实践 了解重构的重要性 保持一致的编码风格和注释用法 使用内置记录功能 最佳编码实践 您现在已经学习了本学习路径的一半内容,已经掌握了足够多的 Java 语法来编写基本 Java 程序。在继续学习更高级的主题之前,目前是一个了解一些最佳编码实践的不错时机。阅读一些可帮助您编写更干净、更可维护的 Java 代码的必要建议。 保持类小巧 目前您已经创建了一些类

  • 一、内存溢出 一种程序运行出现的错误。 当程序运行需要的内存超过了剩余的内存时, 就出抛出内存溢出的错误。 二、内存泄漏 占用的内存没有及时释放。 内存泄露积累多了就容易导致内存溢出。 常见的内存泄露: 意外的全局变量。 没有及时清理的计时器或回调函数。 闭包 // 1. 内存溢出 var obj = {} for (var i = 0; i < 10000; i++) { obj[i] =

  • 本文向大家介绍java内存泄漏与内存溢出关系解析,包括了java内存泄漏与内存溢出关系解析的使用技巧和注意事项,需要的朋友参考一下 这篇文章主要介绍了java内存泄漏与内存溢出关系解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 内存溢出 out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memor

  • 我正处于学习Java的开始阶段,并且有一个作业正在为我提供一些困难(我假设实际上并不困难)。 说明如下: “在一个名为MathCompations的类中,编写一个名为findMin的方法,该方法接受三个整数作为参数并返回三个值中最小的一个。例如,调用findMin(1,10,-1)将返回-1。您必须使用Math类min函数(它只接受2个参数)。在类中编写一个主方法,调用findMin(5,7,3)