如何在Java中创建内存泄漏?
我刚刚接受了一次采访,我被要求用Java创建一个内存泄漏 不用说,我觉得自己很傻,根本不知道如何开始创建一个如何在Java中创建内存泄漏?,java,memory,memory-leaks,Java,Memory,Memory Leaks,我刚刚接受了一次采访,我被要求用Java创建一个内存泄漏 不用说,我觉得自己很傻,根本不知道如何开始创建一个 例如什么?可能通过JNI使用外部本机代码 使用纯Java,这几乎是不可能的 但这是一种“标准”类型的内存泄漏,当您无法再访问内存时,它仍然属于应用程序所有。您可以保留对未使用对象或打开流的引用,而不必在之后关闭它们。一个简单的方法是将哈希集与不正确(或不存在的)hashCode()或equals()一起使用,然后继续添加“重复项”。与其像应该的那样忽略重复项,集合只会不断增长,而您将无法
例如什么?可能通过JNI使用外部本机代码 使用纯Java,这几乎是不可能的
但这是一种“标准”类型的内存泄漏,当您无法再访问内存时,它仍然属于应用程序所有。您可以保留对未使用对象或打开流的引用,而不必在之后关闭它们。一个简单的方法是将哈希集与不正确(或不存在的)
hashCode()
或equals()
一起使用,然后继续添加“重复项”。与其像应该的那样忽略重复项,集合只会不断增长,而您将无法删除它们
如果希望这些坏键/元素挂起,可以使用静态字段,如
class BadKey {
// no hashCode or equals();
public final String key;
public BadKey(String key) { this.key = key; }
}
Map map = System.getProperties();
map.put(new BadKey("key"), "value"); // Memory leak even if your threads die.
创建静态映射并不断向其添加硬引用。这些将永远不会被垃圾收集
public class Leaker {
private static final Map<String, Object> CACHE = new HashMap<String, Object>();
// Keep adding until failure.
public static void addToCache(String key, Object value) { Leaker.CACHE.put(key, value); }
}
公共类泄漏程序{
私有静态最终映射缓存=新HashMap();
//继续添加直到失败。
公共静态void addToCache(字符串键,对象值){Leaker.CACHE.put(键,值);}
}
每当您保留对不再需要的对象的引用时,就会出现内存泄漏。有关内存泄漏如何在Java中表现出来的示例,以及您可以做些什么。如果您不理解,下面的示例是一个毫无意义的示例。或者至少JDBC希望开发人员在丢弃或丢失对实例的引用之前关闭连接
、语句
和结果集
实例,而不是依赖于finalize
的实现
void doWork()
{
try
{
Connection conn = ConnectionFactory.getConnection();
PreparedStatement stmt = conn.preparedStatement("some query"); // executes a valid query
ResultSet rs = stmt.executeQuery();
while(rs.hasNext())
{
... process the result set
}
}
catch(SQLException sqlEx)
{
log(sqlEx);
}
}
上面的问题是,连接
对象没有关闭,因此物理连接将保持打开状态,直到垃圾回收器发现无法访问为止。GC将调用finalize
方法,但有些JDBC驱动程序不实现finalize
,至少与连接.close的实现方式不同。由此产生的行为是,虽然由于收集了无法访问的对象而将回收内存,但与连接
对象关联的资源(包括内存)可能根本不会回收
在这种情况下,连接
的finalize
方法无法清除所有内容,实际上,您可能会发现到数据库服务器的物理连接将持续几个垃圾收集周期,直到数据库服务器最终发现该连接不存在(如果存在),应该关闭
即使JDBC驱动程序要实现finalize
,也有可能在finalize期间抛出异常。由此产生的行为是,与现在“休眠”对象关联的任何内存都不会被回收,因为finalize
保证只被调用一次
上述在对象终结期间遇到异常的场景与另一个可能导致内存泄漏的场景相关——对象复活。对象复活通常是通过从另一个对象创建对最终确定的对象的强引用来有意完成的。当对象复活被误用时,它将与其他内存泄漏源一起导致内存泄漏
你可以想出更多的例子,比如
- 管理一个
列表
实例,在该实例中,您只向列表中添加而不从列表中删除(尽管您应该删除不再需要的元素),或者
- 打开
套接字
s或文件
s,但在不再需要它们时不关闭它们(类似于上面涉及连接
类的示例)
- 关闭JavaEE应用程序时不卸载单例。显然,加载singleton类的类加载器将保留对该类的引用,因此将永远不会收集singleton实例。当部署应用程序的新实例时,通常会创建一个新的类加载器,而以前的类加载器将由于单例而继续存在
以下是一种在纯Java中创建真正内存泄漏(运行代码无法访问但仍存储在内存中的对象)的好方法:
- 应用程序创建一个长时间运行的线程(或者使用线程池来更快地泄漏)
- 线程通过(可选的)自定义
类加载器加载类
- 该类分配一大块内存(例如
新字节[1000000]
),在静态字段中存储对它的强引用,然后在线程本地
中存储对自身的引用。分配额外的内存是可选的(泄漏类实例就足够了),但这会使泄漏工作更快
- 应用程序清除对自定义类或加载它的
ClassLoader
的所有引用
- 重复一遍
由于Oracle的JDK中采用了
ThreadLocal
的实现方式,这会造成内存泄漏:
- 每个
Thread
都有一个私有字段threadLocals
,它实际存储线程本地值
- 此映射中的每个键都是对
ThreadLocal
对象的弱引用,因此ThreadLocal
对象被垃圾收集后,其条目将从映射中删除
- 但是每个值都是一个强引用,因此当一个值(直接或间接)指向作为其键的
ThreadLocal
对象时,只要线程存在,该对象就不会被垃圾收集,也不会从映射中删除
在本例中,强引用链如下所示:
...
this.muchSmallerString = new String(veryLongString.substring(0, 1));
...
public class Main {
public static void main(String args[]) {
Socket s = new Socket(InetAddress.getByName("google.com"),80);
s=null;
/* at this point, because you didn't close the socket properly, */
/* you have a leak of a native descriptor, which uses memory. */
}
}
线程class MemorableClass {
static final ArrayList list = new ArrayList(100);
}
String str = readString(); // read lengthy string any source db,textbox/jsp etc..
// This will place the string in memory pool from which you can't remove
str.intern();
try {
BufferedReader br = new BufferedReader(new FileReader(inputFile));
...
...
} catch (Exception e) {
e.printStacktrace();
}
try {
Connection conn = ConnectionFactory.getConnection();
...
...
} catch (Exception e) {
e.printStacktrace();
}
getServletContext().setAttribute("SOME_MAP", map);
public static void main(String[] args) {
while (true) {
Element first = new Element();
first.next = new Element();
first.next.next = first;
}
}
public class Example1 {
public Example2 getNewExample2() {
return this.new Example2();
}
public class Example2 {
public Example2() {}
}
}
public class Referencer {
public static Example2 GetAnExample2() {
Example1 ex = new Example1();
return ex.getNewExample2();
}
public static void main(String[] args) {
Example2 ex = Referencer.GetAnExample2();
// As long as ex is reachable; Example1 will always remain in memory.
}
}
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import sun.misc.Unsafe;
public class TestUnsafe {
public static void main(String[] args) throws Exception{
Class unsafeClass = Class.forName("sun.misc.Unsafe");
Field f = unsafeClass.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);
System.out.print("4..3..2..1...");
try
{
for(;;)
unsafe.allocateMemory(1024*1024);
} catch(Error e) {
System.out.println("Boom :)");
e.printStackTrace();
}
}
}
public class StringLeaker
{
private final String muchSmallerString;
public StringLeaker()
{
// Imagine the whole Declaration of Independence here
String veryLongString = "We hold these truths to be self-evident...";
// The substring here maintains a reference to the internal char[]
// representation of the original string.
this.muchSmallerString = veryLongString.substring(0, 1);
}
}
...
this.muchSmallerString = new String(veryLongString.substring(0, 1));
...
...
this.muchSmallerString = veryLongString.substring(0, 1).intern();
...
pubclic class RequestProcessor {
private static final Logger logger = Logger.getLogger(RequestProcessor.class);
public void doSomething() {
....
final List<String> hugeList = new ArrayList<String>(10000);
new Thread() {
public void run() {
logger.info("Child thread spawned")
for(String s:hugeList) {
....
}
}
}.start();
}
}
class Leakee {
public void check() {
if (depth > 2) {
Leaker.done();
}
}
private int depth;
public Leakee(int d) {
depth = d;
}
protected void finalize() {
new Leakee(depth + 1).check();
new Leakee(depth + 1).check();
}
}
public class Leaker {
private static boolean makeMore = true;
public static void done() {
makeMore = false;
}
public static void main(String[] args) throws InterruptedException {
// make a bunch of them until the garbage collector gets active
while (makeMore) {
new Leakee(0).check();
}
// sit back and watch the finalizers chew through memory
while (true) {
Thread.sleep(1000);
System.out.println("memory=" +
Runtime.getRuntime().freeMemory() + " / " +
Runtime.getRuntime().totalMemory());
}
}
}
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class BigJarCreator {
public static void main(String[] args) throws IOException {
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(new File("big.jar")));
zos.putNextEntry(new ZipEntry("resource.txt"));
zos.write("not too much in here".getBytes());
zos.closeEntry();
zos.putNextEntry(new ZipEntry("largeFile.out"));
for (int i=0 ; i<10000000 ; i++) {
zos.write((int) (Math.round(Math.random()*100)+20));
}
zos.closeEntry();
zos.close();
}
}
javac BigJarCreator.java
java -cp . BigJarCreator
public class MemLeak {
public static void main(String[] args) throws InterruptedException {
int ITERATIONS=100000;
for (int i=0 ; i<ITERATIONS ; i++) {
MemLeak.class.getClassLoader().getResourceAsStream("resource.txt");
}
System.out.println("finished creation of streams, now waiting to be killed");
Thread.sleep(Long.MAX_VALUE);
}
}
javac MemLeak.java
java -Xmx2m -classpath .:big.jar MemLeak
MemLeak.class.getClassLoader().getResourceAsStream("resource.txt").close();
static void leakMe(final Object object) {
new Thread() {
public void run() {
Object o = object;
for (;;) {
try {
sleep(Long.MAX_VALUE);
} catch (InterruptedException e) {}
}
}
}.start();
}
private static final Map<String, Info> myCache = new HashMap<>();
public void getInfo(String key)
{
// uses cache
Info info = myCache.get(key);
if (info != null) return info;
// if it's not in cache, then fetch it from the database
info = Database.fetch(key);
if (info == null) return null;
// and store it in the cache
myCache.put(key, info);
return info;
}
class A {
B bRef;
}
class B {
A aRef;
}
public class Main {
public static void main(String args[]) {
A myA = new A();
B myB = new B();
myA.bRef = myB;
myB.aRef = myA;
myA=null;
myB=null;
/* at this point, there is no access to the myA and myB objects, */
/* even though both objects still have active references. */
} /* main */
}
public class Main {
public static void main(String args[]) {
Socket s = new Socket(InetAddress.getByName("google.com"),80);
s=null;
/* at this point, because you didn't close the socket properly, */
/* you have a leak of a native descriptor, which uses memory. */
}
}
public static Map<String, Integer> pseudoQueryDatabase();
class EntryHolder {
Map.Entry<String, Integer> entry;
EntryHolder(Map.Entry<String, Integer> entry) {
this.entry = entry;
}
}
public class LeakTest {
private final List<EntryHolder> holdersCache = new ArrayList<>();
private static final int MAP_SIZE = 100_000;
public void run() {
// create 500 entries each holding a reference to an Entry of a TreeMap
IntStream.range(0, 500).forEach(value -> {
// create map
final Map<String, Integer> map = pseudoQueryDatabase();
final int index = new Random().nextInt(MAP_SIZE);
// get random entry from map
for (Map.Entry<String, Integer> entry : map.entrySet()) {
if (entry.getValue().equals(index)) {
holdersCache.add(new EntryHolder(entry));
break;
}
}
// to observe behavior in visualvm
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
public static Map<String, Integer> pseudoQueryDatabase() {
final Map<String, Integer> map = new TreeMap<>();
IntStream.range(0, MAP_SIZE).forEach(i -> map.put(String.valueOf(i), i));
return map;
}
public static void main(String[] args) throws Exception {
new LeakTest().run();
}
}