编写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来查找溢出的所在地。
猜你喜欢
- springboot多模块项目添加一新模块选择Maven Module,填写模块名若空白,catalog目录可选择internal更改包名完
- Android手势解锁本文讲述的是一个手势解锁的库,可以定制显示隐藏宫格点、路径、并且带有小九宫格显示图,和震动!让你学会使用这个简单,高效
- 官方文档:https://central.sonatype.org/publish/publish-maven/#a-complete-ex
- 本文实例讲述了java用接口、多态、继承、类计算三角形和矩形周长及面积的方法。分享给大家供大家参考。具体如下:定义接口规范:/** * @
- 本文实例讲解的是如何画一个满满圆形水波纹loadingview,这类效果应用场景很多,比如内存占用百分比之类的,分享给大家供大家参考,具体内
- 本文实例为大家分享了Silverlight实现星星闪烁动画展示的具体代码,供大家参考,具体内容如下原理很简单,生成1000个圆,从随机数来布
- 定义装饰者模式:在不改变原有对象的基础之上,动态的将功能附加到对象上,提供了继承更有弹性的替代方案,也体现了开闭原则案例需求一个人去咖啡店点
- yml 文件规则yml文件的好处,天然的树状结构,一目了然,实质上跟properties是差不多的。不支持tab缩进可以使用 "-
- SWT中没有AWT的BorderLayout布局管理器。下面是SWT下的自定义实现: BorderLayout.java package s
- 本文实例讲述了C#实现将浮点数表示的货币数量以汉字大写形式输出的方法。分享给大家供大家参考。具体如下:1.函数代码注:本段代码中能转化的最大
- 基于IntelliJ Platform Plugin搭建环境步骤File->New->Project选择IntelliJ Pla
- 写在前面:可能是临近期末了,各种课程设计接踵而来,最近在csdn上看到2个一样问答,那就是编写一个基于socket的聊天程序,正好最近刚用s
- Java面向对象之猜拳游戏,供大家参考,具体内容如下1 要求与电脑进行猜拳并记录分数。2 Computer.java 源代码(电脑自动随机出
- 环境信息名称版本号Spring Boot2.4.5Idea2021.3.2服务端实现导入依赖<dependency>  
- 一. 线程池简介1. 线程池的概念:线程池就是首先创建一些线程,它们的集合称为线程池。使用线程池可以很好地提高性能,线程池在系统启动时即创建
- 本文实例讲述了Android编程使用pull方式解析xml格式文件的方法。分享给大家供大家参考,具体如下:上次已经说过使用Android s
- 1 Get请求数据项目地址:https://github.com/Snowstorm0/learn-get-post1.1 Controll
- 用servlet实现一个注册的小功能 ,后台获取数据。注册页面:注册页面代码 :<!DOCTYPE html><html&
- 修改readme.txt文件如下:Git is a distributed version control system.Git is fr
- 编码&解码 通过下图我们可以了解在javaWeb中有哪些地方有转码:用户想服务器发送一个HTTP请求,需要编码的地方有ur