无法创建PermGen错误

4
这是一份作业,实话说。我需要写一个程序来生成“java.lang.OutOfMemoryError: PermGen space”错误。
因为我没能参加课程,所以昨天我做了几个小时的研究,这就是我目前的进展。
首先我创建了一个程序,但我不断地得到这个错误:
java.lang.OutOfMemoryError: GC overhead limit exceeded

好的,我做了更多的研究,了解到我没有出现PermGen错误的原因是,尽管我创建了对象(字符串对象),但我没有再次使用它们,所以它们被认为是垃圾。 因此,我改变了我的代码,并不断得到以下结果:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

所以这是我当时的代码:

import java.util.ArrayList;
import java.util.List;

public class Test {

public static void main(String[] args) {
    List<String> test = new ArrayList<String>();
    test.add(new String(""));

    for (;;) {
        for (int i = 0; i<test.size(); i++){
            test.add(test.get(i));
        }
    }
}
}

在VM参数下,我使用了“-XX:PermSize=2m”(我尝试了不同的值)。

有人告诉我,我的代码是错误的,因为它反复使用相同的字符串。所以我试图改变它,但是我仍然没有成功。

然后我找到了这段代码:(导致java.lang.OutOfMemoryError: PermGen空间错误的算法)

Random rnd = new Random();
List<String> interned = new ArrayList<String>();
for (;;) {
    int length = rnd.nextInt(100);
    StringBuilder builder = new StringBuilder();
    String chars = "abcdefghijklmnopqrstuvwxyz";
    for ( int i = 0; i < length; i++ ) {
        builder.append(chars.charAt(rnd.nextInt(chars.length())));
    }
    interned.add(builder.toString().intern());
}

如果我理解正确的话,这会导致PermGen错误?但我仍然得到了Java堆错误。
另外我找到了这个链接:http://javaeesupportpatterns.blogspot.com/2011/10/java-7-features-permgen-removal.html。如果我理解正确的话,那么在使用Java7时,可能应该出现Java堆空间错误?也就是说,不再可能出现PermGen错误?好的,所以我尝试将编译器和项目版本更改为Java 6。但仍然得到了Java堆空间错误。
我知道我没有参加讲座是我的错,但我需要一些帮助来理解我在这里做错了什么或者我漏掉了什么?

1
哦,关于你的例子 - 它们对我来说都很好用。只是你还需要指定最大永久代大小:-XX:MaxPermSize=2M(从我看到的内容中,你只指定了初始值:-XX:PermSize=8M)。 - Petro Semeniuk
他们确实起了作用。谢谢!我已经把它们丢弃了,因为它们没有起作用,但是在我获得更多信息之后,我从未再次尝试过。但我仍然想知道,例如使用-XX:MaxPermSize = 32M,XX:PermSize = 32M是否可能创建PermGen错误,以及如何创建。 - Artur K.
第一个例子 - 如果您使用32M的永久代大小,您将无法遇到PermGen空间错误(正如您在问题中提到的,有人帮助您并说字符串应该是唯一的以填充永久代空间)。 第二个例子 - 您可以使用“-Xms64m -Xmx64m -XX:PermSize = 32M -XX:MaxPermSize = 32M”选项获得32M的使用大小并获得PermGen空间错误。 - Petro Semeniuk
3个回答

6

首先,简单的一行代码可以重现永久代错误(只需递归调用main函数):

public class PermGenError {
    public static void main(String[] args) {
        main(new String[] { (args[0] + args[0]).intern() });
    }
}

应该使用参数启动:
whatever -XX:PermSize=8M -XX:MaxPermSize=8M

第二,为什么你的示例没有产生永久代错误?你对字符串intern的使用是正确的。然而,在最新的JMV规范中,Java虚拟机可以将内部化的字符串从永久代移动到另一个代。现实场景(在生产中发生过 :-))是,您可能会看到永久代占用了99%,应用程序极其缓慢,并且jvm不会抛出任何错误,因为它不断地在各个代之间重新排列对象。
第三,我读过并经常参考任何GC / Memory问题的最佳论文是Tuning Garbage Collection with the 5.0 Java[tm] Virtual Machine 编辑:
问题的更新是:
示例正常工作,但是当我指定较大的perm size时仍无法工作 - 出现'Java heap space'
回答的更新是:
在指定更大的永久代大小时,有两个方面需要注意:
  1. 所有对象首先进入其中一个年轻代。因此,如果您的永久代大小大于第一代Java堆空间的大小,则会首先抛出“PermGen space”错误。在这个特定的例子中,我使用了一个超过永久代大小的大字符串来重现错误。如果您想要重现“PermGen space”错误,则需要指定堆大小。例如:

    • -Xms2048m -Xmx2048m -XX:PermSize=512M -XX:MaxPermSize=512M - 将导致“PermGen space”错误(大堆,小永久代)
    • -Xms512m -Xmx512m -XX:PermSize=2048M -XX:MaxPermSize=2048M - 将导致“Java heap space”错误(小堆,大永久代)
  2. 第二个方面是堆被划分为几代,堆内存本身是碎片化的。因此,当您指定512M的堆大小时,您无法将半GB长的字符串放入内存中(因为字符串在内部实现为数组,并且需要作为一个连续的块存储在内存中)。最大的可能适合的大小大约是其一半的大小(这取决于内存碎片化的程度)。


首先感谢您抽出时间回答我的问题。我觉得这段代码非常巧妙,简单易懂,逻辑清晰,而且我甚至不知道在代码中可以再次调用主类。我一定会记住这个例子。所以我尝试了这段代码,并在使用whatever -XX:PermSize=2M -XX:MaxPermSize=2M时遇到了PermGen空间错误。我不明白的是,为什么它不能使用更大的PermGen空间。我阅读了那篇论文的一些部分,并尝试使用-XX:-UseGCOverheadLimit。但我仍然遇到了Java堆空间异常。虽然我本来期望会遇到PermGen空间错误。 - Artur K.
无法在Java 7中工作,因为字符串池被重定位到堆上。 - divideByZero

3

PermGen Space是Java存储类定义和内部数据的内存区域(这不是应用程序创建和使用的数据)。因此,你实际上不需要一个大而复杂的应用程序。这篇博客文章简要介绍了Java内存的结构以及不同区域的职责。

理论上,通过定义低的perm gen空间(就像你已经使用-XX:PermSize=2m一样),你很快就会得到异常。然后你将一些库添加到应用程序的classpath中(比如Apache commons、HTMLUnit等)。下载几个jar文件,只要应用程序启动并且classloader试图将您的jar文件的类定义存储在PermGen空间中,就会立即出现此错误。

如果这不起作用,请尝试使用Tomcat,在web-app管理器上重复部署一些库并重新加载应用程序。

如果你需要一个导致这个问题的算法,你可以使用JVM参数-XX:+PrintGCDetails来调试正在运行的应用程序。这将显示每次使用GC时使用的PermGen空间的大小。这将帮助你看到是否朝着正确的方向前进,以及你填充哪个内存区域。PermGen空间也存储有方法引用的数组,所以这也可能解决你的问题。


1
所以如果我理解正确,这应该会给我PermGen错误?但我仍然得到了Java堆错误。
java.lang.OutOfMemoryError: GC overhead limit exceeded
是的。发生的情况是您正在触发JVM机制,该机制旨在防止可能耗尽内存的程序在过程中花费太多时间。
请参见GC overhead limit exceeded 您可以使用-XX:-UseGCOverheadLimit开关禁用此功能。 我预计您将随后收到OOME,指出您已经用完了permgen内存。

如果您现在看到以下信息:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

那么问题是您的常规堆空间在耗尽之前已经用完了 permgen 空间:

  • 增加常规堆大小。
  • 确保您的应用程序保留对所有这些 intern'ed 字符串的引用。(如果它们变得不可访问,它们将被垃圾回收,您就不会耗尽 permgen 空间。)

首先感谢您抽出时间回答我的问题。我查看了您给我的参考资料以及其他一些页面。虽然在使用-XX:-UseGCOverheadLimit开关后,我仍然无法获得permgen内存错误。尽管我也希望能够如此。当我将XX:MaxPermSize = 2M设置为工作时,但是这样低的最大值不需要开关。但是,当尝试使用更大的XX:MaxPermSize(4M)来获取此错误时,使用OverHeadLimit开关并没有起作用。我认为我仍然没有完全理解某些东西。 - Artur K.
你现在在 OOME 中收到了什么信息? - Stephen C
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space - Artur K.

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