编写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来查找溢出的所在地。


猜你喜欢
- Android SDK已经提供有进度条组件ProgressDialog组件,但用的时候我们会发现可能风格与我们应用的整体风格不太搭配,而且P
- Spring * 监测每个Controller或方法的执行时长首先写一个类(TestInterceptor)让他继承HandlerInter
- 这一篇,给大家介绍一下ImageView控件的使用,ImageView主要是用来显示图片,可以对图片进行放大、缩小、旋转的功能。androi
- forword跳转页面的三种方式:1.使用serlvet/** * 使用forward跳转,传递基本类型参数到页面  
- 什么是树?简单认识树 在生活中,有杨树,石榴树,枣树,而在计算机中的树呢,是一种非线性结构,是由 n(n>=0) 个有限节点
- void UpdateContactSign() {&n
- 什么是Java泛型Java 泛型(generics)是 Jdk 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制, 该机制允许程序
- 1.导入pom依赖<!-- mybatis-->  
- 前言代理模式,我们这里结合JAVA的静态代理和 * 来说明,类比Spring AOP面向切面编程:增强消息,也是代理模式。而我们的静态代理
- 函数指针最近看android camera 的source ,发现大量的call back ,多线程,有必要对其中的基础 :函数指针复习一下
- 一、编码(转成字节数组)GetBytes 1、ASII码:字符串中的每个字符用一个字节表示。每个字符实际上只使用了7位,从00h
- 1.概述MybatisPlus是国产的第三方插件, 它封装了许多常用的CURDapi,免去了我们写mapper.xml的重复劳动,这里介绍了
- 前言我们平时在开发的时候,发起网络请求前,会需要显示一个Loading,一般的做法都是在xml布局上添加好Loading,然后在Activi
- 前言上一篇文章用贝塞尔曲线画了一个看起来不错的小红点功能,技术上没什么难度,主要就是数学上的计算。这篇文章也差不多,模仿了一个常用的滑动解锁
- 1.memchrmemchr的函数声明:void *memchr(const void *str, int c, size_t n);作用:
- 前言之前做移动端开发,都不清楚WebService是啥东东,现在接触c#,项目中有三处WebService调用,就不得不与其打交道了,最近碰
- 假如使用绝对路径,没有任何问题,就是移植性不太好。假如使用相对路径,则要注意当前路径“.”是在哪儿?一般我们都会在配置文件中加入log文件的
- 本文实例讲述了C#使用Object类实现栈的方法。分享给大家供大家参考,具体如下:Stack类的代码:using System;using
- 背景在工控软件的开发中很多业务场景就是使用图表控件展示设备和工艺参数。如下图案例:实现思路通常简单的做法是使用图表控件实现,常用的图表控件有
- 使用C#在不借助第三方插件的情况下将Excel中的数据转换成DataSet/// <summary>