Dav*_*vid 8 java reflection classloader
我试图在我的程序中添加一个javaeditor来在运行时扩展程序.一切正常,除了广泛使用程序(我模拟1000-10000编译器执行).内存使用量上升和上升,看起来有内存泄漏.
在我的程序中,类被加载,构造函数被执行并且类被卸载(没有剩余的实例,并且当我将指针设置为null时,classLoader变得无效).我用JConsole分析了这个过程,当执行垃圾收集器时类会被卸载.
我在内存分析器中做了一个heapdum打开它,问题似乎是在java.net.FactoryURLClassLoader里面(在com.sun.tools.javac.util.List对象中).由于(com.sun.tools.javac)是JDK的一部分而不是JRE,而SystemToolClassLoader是FactoryURLClassLoader对象,我会在那里的某处找到泄漏.当我第一次执行编译器时,SystemToolClassLoader中加载的类的数量从1上升到521,但之后保持不变.
所以我不知道泄漏在哪里,有没有办法重置SystemToolClassLoader?我怎样才能更准确地找到泄漏点.
编辑:好的,我发现它也发生在一个非常简单的例子中.所以它似乎是编译的一部分,我不需要加载类或实例化它:
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
public class Example {
public static void main(String[] args)
{
for (int i =0; i<10000;i++){
try {
System.out.println(i);
compile();
} catch (InstantiationException | IllegalAccessException
| ClassNotFoundException | IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public static void compile() throws IOException, InstantiationException, IllegalAccessException, ClassNotFoundException
{
File source = new File( "src\\Example.java" ); // This File
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileManager = compiler.getStandardFileManager( null, null, null );
Iterable<? extends JavaFileObject> units;
units = fileManager.getJavaFileObjectsFromFiles( Arrays.asList( source ) );
compiler.getTask( null, fileManager, null, null, null, units ).call();
fileManager.close();
}
}
Run Code Online (Sandbox Code Playgroud)
起初,我认为这是一个确定的内存泄漏; 然而,它与SoftReference工作方式直接相关.
Oracle的JVM只会在堆完全耗尽时尝试收集软引用.看起来不可能强制以编程方式收集软引用.
为了确定问题,我在'无限'堆上使用了三个转储:
明显的(双重)实例数增加:Names(500 - > 1k),SharedNameTable(500> 1k),SharedNameTable$NameImpl(数十万)和[LSharedNameTable$NameImpl(500> 1k).
在使用EMA进行分析之后,很明显SharedNameTable有一个静态引用com.sun.tools.javac.util.List显然SoftReference是每个单独SharedNameTable创建的(因此在运行时编译的每个源文件都有一个).所有的$NameImpls都是你的源文件被分割成的标记.而且显然所有的令牌都从未从堆中释放出来并积累到无止境 ......或者他们呢?
我决定测试这是否真的如此.知道了软引用和弱引用的区别,我决定使用一个小堆(-Xms32m -Xmx32m).这样JVM就会被强制释放SharedNameTable或失败OutOfMemoryError.结果不言自明:
-Xmx512m -Xms512m
Total memory: 477233152
Free memory: 331507232
Used memory: 138.97506713867188 MB
Loaded scripts: 500
Total memory: 489816064
Free memory: 203307408
Used memory: 273.23594665527344 MB
Loaded scripts: 1000
The classloader/component "java.net.FactoryURLClassLoader @ 0x8a8a748" occupies 279.709.192 (98,37%) bytes.
Run Code Online (Sandbox Code Playgroud)
-Xmx32m -Xms32m
Total memory: 29687808
Free memory: 25017112
Used memory: 4.454322814941406 MB
Loaded scripts: 500
Total memory: 29884416
Free memory: 24702728
Used memory: 4.941642761230469 MB
Loaded scripts: 1000
One instance of "com.sun.tools.javac.file.ZipFileIndex" loaded by "java.net.FactoryURLClassLoader @ 0x8aa4cc8" occupies 2.230.736 (47,16%) bytes. The instance is referenced by *.*.script.ScriptFileManager @ 0x8ac8230.
Run Code Online (Sandbox Code Playgroud)
(这只是JDK库的链接.)
脚本:
public class Avenger
{
public Avenger()
{
JavaClassScriptCache.doNotCollect(this);
}
public static void main(String[] args)
{
// this method is called after compiling
new Avenger();
}
}
Run Code Online (Sandbox Code Playgroud)
doNotCollect:
private static final int TO_LOAD = 1000;
private static final List<Object> _active = new ArrayList<Object>(TO_LOAD);
public static void doNotCollect(Object o)
{
_active.add(o);
}
System.out.println("Loaded scripts: " + _active.size());
Run Code Online (Sandbox Code Playgroud)
Java 7引入了这个bug:为了加快编译速度,他们引入了SharedNameTable,它使用软引用来避免重新分配,但不幸的是,这只会导致JVM膨胀失控,因为这些软引用永远不会被回收,直到JVM达到其-Xmx内存限制。据称它将在 Java 9 中修复。与此同时,有一个(未记录的)编译器选项可以禁用它:-XDuseUnsharedTable。
| 归档时间: |
|
| 查看次数: |
2035 次 |
| 最近记录: |