分享
定制
問(wèn)題:
剛參加的一個(gè)面試,要我用Java寫(xiě)一個(gè)內(nèi)存泄露程序。
這題完全沒(méi)有思路,
有好心人能給出一個(gè)例子嗎?
回答:
造成內(nèi)存泄漏,就是讓運(yùn)行的程序無(wú)法訪問(wèn)存儲(chǔ)在內(nèi)存中的對(duì)象,下面是Java實(shí)現(xiàn):
創(chuàng)建一個(gè)長(zhǎng)時(shí)間運(yùn)行的線程(使用線程池泄露的速度更快)。
線程通過(guò)ClassLoader加載某個(gè)類(lèi)(也可以用自定義ClassLoader)。
這個(gè)類(lèi)分配了大量?jī)?nèi)存(例如new byte[1000000]),賦給靜態(tài)字段存儲(chǔ)對(duì)它的強(qiáng)引用,然后在ThreadLocal中存儲(chǔ)對(duì)自身的引用。還可以分配額外的內(nèi)存,這樣泄漏的速度更快(其實(shí)只要泄漏Class實(shí)例就足夠了)。
這個(gè)線程會(huì)清除所有自定義類(lèi)及加載它的ClassLoader的引用。
重復(fù)執(zhí)行。
這個(gè)方法之所以奏效,是因?yàn)門(mén)hreadLocal保留了對(duì)該對(duì)象的引用,對(duì)象引用保留了對(duì)Class的引用,而Class引用又保留了對(duì)ClassLoader的引用。反過(guò)來(lái),ClassLoader會(huì)保留通過(guò)它加載的所有類(lèi)的引用。
(在許多JVM實(shí)現(xiàn)中情況更糟,尤其Java 7之前版本。因?yàn)镃lass和ClassLoader會(huì)直接分配到permgen中,GC不進(jìn)行回收)。但是,無(wú)論JVM如何處理類(lèi)卸載,ThreadLocal仍然會(huì)阻止被回收的Class對(duì)象)。
這種方案還可以變化為,頻繁地重新部署碰巧用到ThreadLocal的應(yīng)用程序。這時(shí)像Tomcat這樣的應(yīng)用程序容器會(huì)像篩子一樣泄漏內(nèi)存。(因?yàn)閼?yīng)用程序容器會(huì)像上面那樣啟動(dòng)線程,并且每次重新部署應(yīng)用程序時(shí),都會(huì)使用新的ClassLoader)
更新:鑒于大家強(qiáng)烈要求,這里給出一個(gè)演示程序。
ClassLoaderLeakExample.java
import java.io.IOException;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.Path;
/**
* ClassLoader泄漏演示
*
* <p>要查看實(shí)際運(yùn)行效果,請(qǐng)將此文件復(fù)制到某個(gè)臨時(shí)目錄,
* 然后運(yùn)行:
* <pre>{@code
* javac ClassLoaderLeakExample.java
* java -cp .ClassLoaderLeakExample
* }</pre>
*
* <p>可以看到內(nèi)存不斷增加!在我的系統(tǒng)上,使用JDK 1.8.0_25,開(kāi)始
* 短短幾秒鐘就收到了OutofMemoryErrors
*
* <p>這個(gè)類(lèi)用到了一些Java 8功能,主要用于
* I/O 操作同樣的原理可以適用于
* Java 1.2以后的任何Java版本
*/
public final class ClassLoaderLeakExample {
static volatile boolean running = true;
public static void main(String[] args) throws Exception {
Thread thread = new LongRunningThread();
try {
thread.start();
System.out.println("Running, press any key to stop.");
System.in.read();
} finally {
running = false;
thread.join();
}
}
/**
* 線程的實(shí)現(xiàn)只是循環(huán)調(diào)用
* {@link #loadAndDiscard()}
*/
static final class LongRunningThread extends Thread {
@Override public void run() {
while(running) {
try {
loadAndDiscard();
} catch (Throwable ex) {
ex.printStackTrace();
}
try {
Thread.sleep(100);
} catch (InterruptedException ex) {
System.out.println("Caught InterruptedException, shutting down.");
running = false;
}
}
}
}
/**
* 這是一個(gè)簡(jiǎn)單的ClassLoader實(shí)現(xiàn),只能加載一個(gè)類(lèi)
* 即LoadedInChildClassLoader類(lèi).這里需要解決一些麻煩
* 必須確保每次得到一個(gè)新的類(lèi)
* (而非系統(tǒng)class loader提供的
* 重用類(lèi)).如果此子類(lèi)所在JAR文件不在系統(tǒng)的classpath中,
* 不需要這么麻煩.
*/
static final class ChildOnlyClassLoader extends ClassLoader {
ChildOnlyClassLoader() {
super(ClassLoaderLeakExample.class.getClassLoader());
}
@Override protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
if (!LoadedInChildClassLoader.class.getName().equals(name)) {
return super.loadClass(name, resolve);
}
try {
Path path = Paths.get(LoadedInChildClassLoader.class.getName()
+ ".class");
byte[] classBytes = Files.readAllBytes(path);
Class<?> c = defineClass(name, classBytes, 0, classBytes.length);
if (resolve) {
resolveClass(c);
}
return c;
} catch (IOException ex) {
throw new ClassNotFoundException("Could not load " + name, ex);
}
}
}
/**
* Helper方法會(huì)創(chuàng)建一個(gè)新的ClassLoader, 加載一個(gè)類(lèi),
* 然后丟棄對(duì)它們的所有引用.從理論上講,應(yīng)該不會(huì)影響GC
* 因?yàn)闆](méi)有引用可以逃脫該方法! 但實(shí)際上,
* 結(jié)果會(huì)像篩子一樣泄漏內(nèi)存.
*/
static void loadAndDiscard() throws Exception {
ClassLoader childClassLoader = new ChildOnlyClassLoader();
Class<?> childClass = Class.forName(
LoadedInChildClassLoader.class.getName(), true, childClassLoader);
childClass.newInstance();
// 該方法返回時(shí),將無(wú)法訪問(wèn)
// childClassLoader或childClass的引用,
// 但是這些對(duì)象仍會(huì)成為GC Root!
}
/**
* 一個(gè)看起來(lái)人畜無(wú)害的類(lèi),沒(méi)有做什么特別的事情.
*/
public static final class LoadedInChildClassLoader {
// 獲取一些bytes.對(duì)于泄漏不是必需的,
// 只是讓效果出得更快一些.
// 注意:這里開(kāi)始真正泄露內(nèi)存,這些bytes
// 每次迭代都為這個(gè)final靜態(tài)字段創(chuàng)建了!
static final byte[] moreBytesToLeak = new byte[1024 * 1024 * 10];
private static final ThreadLocal<LoadedInChildClassLoader> threadLocal
= new ThreadLocal<>();
public LoadedInChildClassLoader() {
// 在ThreadLocal中存儲(chǔ)對(duì)這個(gè)類(lèi)的引用
threadLocal.set(this);
}
}
}
【使用錘子簡(jiǎn)歷小程序制作簡(jiǎn)歷】
零經(jīng)驗(yàn)實(shí)習(xí)簡(jiǎn)歷模板
21254人用過(guò)
學(xué)生求職簡(jiǎn)歷模板
52754人用過(guò)
申請(qǐng)研究生簡(jiǎn)歷模板
2324人用過(guò)
經(jīng)典工作簡(jiǎn)歷模板
6254人用過(guò)
投行咨詢簡(jiǎn)歷模板
12465人用過(guò)
產(chǎn)品經(jīng)理簡(jiǎn)歷模板
7532人用過(guò)
程序員簡(jiǎn)歷模板
7457人用過(guò)
留學(xué)英文簡(jiǎn)歷模板
4554人用過(guò)