Java程序如何有意填满PermGen?

5
有时候,当PermGen满时,Glassfish无法停止。在这种情况下,asadmin stop-domain domain1无法起作用。在Glassfish 2.1.1中,它将永远停留在那里;在3.x中,它会在AS_ADMIN_READTIMEOUT后超时。因此,我现在正在编写我的Glassfish停止脚本,在一定的超时时间后使用kill/kill -9来确保它被停止。
为了完全测试这个脚本,我需要重现这个PermGen满的场景。如何故意填满PermGen?如果有影响的话,我目前使用的是Java 1.7.0_45。我已经写了一个程序来填充堆,但这对我来说是新的,所以我想求助于SO。这可能比我想象的要棘手(不确定),需要一些可以部署到GF中的东西(如.war文件?)。非常感谢,希望能得到帮助!

1
你需要创建许多ClassLoaders。我从未尝试过,但你应该朝这个方向看看。 - EasterBunnyBugSmasher
你了解PermGen吗?对象是如何进入其中的?一旦你回答了这些问题,你就能轻松地想出如何填充PermGen以进行测试。 - Germann Arlington
@EasterBunnyBugSmasher 多个 ClassLoader 会有什么帮助? - Germann Arlington
1
好的,确实不需要很多个类加载器。一个就足够了。你可以这样做:创建一个类文件模板,每次复制它并更改其中的类名和文件名即可。该文件必须在你的类路径中。然后使用ClassLoader.getSystemClassLoader().loadClass("MyClass" + i)来加载类。 - EasterBunnyBugSmasher
1
@Steve Kehlet,现在可能有点晚了,但原因可能是您必须在将类放入永久代之前对其进行有意义的使用,或者通过类加载器特别加载该类。我不知道CGLib是否会立即加载类,但如果我必须猜测,那就是它。 - Mark W
显示剩余7条评论
1个回答

6
我有一些东西要给你。我不知道如何在这里上传jar文件,所以请只添加文件。
方法:ClassGenerator类在while循环中创建一个新的类加载器,并重复加载相同的类,直到其使用完永久代(permgen)。现在你会注意到有一个列表,它保存了已加载类的引用,这是为了防止JVM卸载那些类 :)。
文件说明: 第一张图片显示当你运行程序时它会耗尽永久代空间。第二张图片显示了如果你想在eclipse中设置它的结构。我在eclipse中测试过它,并将其导出为可运行的jar文件,在两种情况下都可以工作。
作为可运行的jar文件运行,它会耗尽永久代。 Program ran out of permgen Eclipse项目设置 enter image description here ClassGenerator类
package com.vkg;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

public class ClassGenerator {
    private static final int BUFFER = 1024;
    private List<Class<?>> classList = new ArrayList<Class<?>>();

    public static void main(String[] args) {
        ClassGenerator classGenerator = new ClassGenerator();
        // Load just some class with class loaders until perm gen space fills.
        while (true) {
            classGenerator.classLoader();
        }
    }

    private void classLoader() {
        ClassLoader classLoader = new ClassLoader() {
            public Class<?> loadClass(String classNameWithPackage)
                    throws ClassNotFoundException {
                if (!classNameWithPackage.contains("DummyClass")) {
                    return super.loadClass(classNameWithPackage);
                }
                String className = classNameWithPackage.replace('.', '/')
                        + ".class";
                byte[] classData = null;
                InputStream inputStream = null;
                try {
                    inputStream = getResourceAsStream(className);
                    byte[] buffer = new byte[BUFFER];
                    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
                    int bytesRead = -1;
                    while ((bytesRead = inputStream.read(buffer, 0, BUFFER)) != -1) {
                        outputStream.write(buffer, 0, bytesRead);
                    }
                    classData = outputStream.toByteArray();
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    if (inputStream != null) {
                        try {
                            inputStream.close();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
                Class<?> c = defineClass(classNameWithPackage, classData, 0,
                        classData.length);
                resolveClass(c);
                System.out
                        .println("Steve I am loading another copy of Dummy class. Soon Permgen will fill.");
                classList.add(c);
                return c;
            }
        };

        try {
            Class.forName("com.vkg.DummyClass", true, classLoader);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

虚拟类。这可以是任何类。该类的唯一目的是被大量加载。没有其他用途。该类不执行任何逻辑。主要逻辑在ClassGenerator.java中。

package com.vkg;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

public class DummyClass {
    public void classLoader() {
        ClassLoader classLoader = new ClassLoader() {
            public Class<?> loadClass(String classNameWithPackage) throws ClassNotFoundException {
                 if(!classNameWithPackage.contains("DummyClass")) {
                     return  super.loadClass(classNameWithPackage);
                 } 
                 String className = classNameWithPackage.replace('.', '/') + ".class";
                byte[] classData = null;
                try {
                    InputStream inputStream = getResourceAsStream(className);
                    byte[] buffer = new byte[1];
                    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
                    int bytesRead = -1;
                    while ((bytesRead = inputStream.read(buffer, 0, 1)) != -1) {
                        outputStream.write(buffer, 0, bytesRead);
                    }
                    classData = outputStream.toByteArray();
                }
                catch (IOException e) {
                    e.printStackTrace();
                }

                Class<?> c = defineClass(classNameWithPackage, classData, 0, classData.length);
                resolveClass(c);
                System.out.println("Steve I am loading another copy of Dummy class. Soon Permgen will fill.");
                return c;
            }
        };

        try {
            Class.forName("com.vkg.DummyClass", true, classLoader);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

希望它能帮助您测试服务器崩溃。

我正在尝试!试图弄清楚为什么DummyClass和ClassGenerator中有重复的代码? - Steve Kehlet
正如我在评论中提到的那样。DummyClass 中可以有任何东西都无所谓。该类的唯一目的是被加载 n 次。实际上,该类中没有执行任何逻辑。 - vkg
有趣的是,我正在尝试让它工作,并发现(通过lsof)它正在打开数千个文件描述符,因此我得到了一个NPE而不是预期的PermGen full。正在努力解决中... - Steve Kehlet
在代码中添加一个 finally {} 块来关闭 inputStream,它就可以正常工作了!请查看我的gist。请更新您的答案,我会接受的。非常感谢。 - Steve Kehlet
1
谢谢Steve。我同意你提出的更改。我已经将它们合并了。:) - vkg

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接