java.lang.OutOfMemoryError: GC overhead limit exceeded

我在一个程序中得到这个错误,该程序创建了几个(数十万)HashMap对象,每个对象有几个(15-20)文本条目。在将这些字符串提交到数据库之前,必须收集所有这些字符串(而不将其分解为更小的数量)。

根据Sun的说法,错误发生在“如果在垃圾收集上花费了太多的时间:如果超过98%的总时间花在垃圾收集上,而不到2%的堆被恢复,则会抛出OutOfMemoryError”。

显然,可以使用命令行将参数传递给JVM

  • 增加堆的大小,通过“-Xmx1024m”(或更多),或者
  • 通过"-XX:-UseGCOverheadLimit"完全禁用错误检查。

第一种方法工作得很好,第二种方法在另一个java.lang中结束。OutOfMemoryError,这次是关于堆的。

那么,问题是:对于特定的用例(即几个小HashMap对象),是否有任何编程替代方案?例如,如果我使用HashMap clear()方法,问题就会消失,但存储在HashMap中的数据也会消失!:-)

这个问题在StackOverflow中的相关主题。中也有讨论

618305 次浏览

您实际上正在耗尽内存,无法顺利运行进程。想到的选项有:

  1. 像你提到的那样指定更多的内存,先尝试一些介于两者之间的东西,比如-Xmx512m
  2. 如果可能的话,使用更小批量的HashMap对象一次性处理
  3. 如果你有很多重复的字符串,在将它们放入HashMap中之前,对它们使用String.intern()
  4. 使用HashMap(int initialCapacity, float loadFactor)构造函数为您的情况进行调优

嗯……你需要:

  1. 完全重新思考你的算法&数据结构,这样它就不需要所有这些小hashmap。

  2. 创建一个facade,允许您根据需要将这些hashmap分页到内存中。一个简单的LRU-cache可能就是需要的。

  3. 增加JVM可用的内存。如果有必要,甚至购买更多的RAM可能是最快速、最便宜的解决方案,如果您可以管理托管这个野兽的机器的话。话虽如此,我通常不喜欢“投入更多硬件”的解决方案,特别是如果可以在合理的时间框架内想出替代算法解决方案的话。如果你继续在这些问题上投入更多的硬件,你很快就会遇到收益递减定律。

你到底想做什么?我怀疑有更好的方法来解决你的实际问题。

如果您正在创建数十万个哈希映射,那么您使用的哈希映射可能远远超过实际需要;除非处理大文件或图形,否则存储简单数据不应超过Java内存限制。

你应该重新考虑一下你的算法。在这种情况下,我会在这个问题上提供更多的帮助,但在你提供更多关于问题背景的信息之前,我不能提供任何信息。

郑重声明,我们今天也遇到了同样的问题。我们通过使用这个选项来修复它:

-XX:-UseConcMarkSweepGC

显然,这修改了用于垃圾收集的策略,使问题消失了。

@takrl:默认设置为:

java -XX:+UseConcMarkSweepGC

,这意味着该选项默认不激活。所以当你说你使用了期权 “+XX:UseConcMarkSweepGC” 我假设您正在使用以下语法:

java -XX:+UseConcMarkSweepGC

这意味着你显式地激活了这个选项。 对于Java HotSpot VM Options @ this的正确语法和默认设置 文档 < / p >

这帮助我摆脱了这个错误。此选项将禁用 - xx: + DisableExplicitGC < / p >
使用替代HashMap实现(宝库)。标准Java HashMap有12倍的内存开销。 可以读取详细信息在这里.

如果出现错误:

"内部编译器错误:java.lang.OutOfMemoryError: GC开销限制超过java.lang.AbstractStringBuilder"

将java堆空间增加到2GB,即-Xmx2g.

不要在等待结束时将整个结构存储在内存中。

将中间结果写入数据库中的临时表,而不是hashmap——从功能上讲,数据库表相当于hashmap,即两者都支持按键访问数据,但表不受内存限制,因此在这里使用索引表而不是hashmap。

如果操作正确,您的算法甚至不应该注意到这个变化——这里的正确意思是使用一个类来表示表,甚至像hashmap一样给它一个put(键,值)和get(键)方法。

当中间表完成时,从中间表而不是从内存中生成所需的sql语句。

如果在垃圾收集上花费了太多时间,并行收集器将抛出OutOfMemoryError。特别是,如果超过98%的总时间花在垃圾收集上,而不到2%的堆被恢复,OutOfMemoryError将被抛出。此特性旨在防止应用程序运行较长时间,而由于堆太小而进展很少或没有进展。如果需要,可以通过在命令行中添加选项-XX:-UseGCOverheadLimit来禁用该特性。

下面的方法对我很有效。只需添加以下片段:

dexOptions {
javaMaxHeapSize "4g"
}

你的build.gradle:

android {
compileSdkVersion 23
buildToolsVersion '23.0.1'


defaultConfig {
applicationId "yourpackage"
minSdkVersion 14
targetSdkVersion 23
versionCode 1
versionName "1.0"


multiDexEnabled true
}


buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}


packagingOptions {


}


dexOptions {
javaMaxHeapSize "4g"
}
}

对于我的情况,使用-Xmx选项增加内存是解决方案。

我用java读取了一个10g的文件,每次我都得到相同的错误。当top命令中的RES列的值达到-Xmx选项中设置的值时,就会发生这种情况。然后使用-Xmx选项增加内存,一切正常。

还有一点。当我在我的用户帐户中设置JAVA_OPTSCATALINA_OPTS并再次增加内存时,我得到了相同的错误。然后,我在我的代码中打印这些环境变量的值,它们给我的值与我设置的值不同。原因是Tomcat是该进程的根,然后由于我不是su-doer,我要求管理员在Tomcat中增加catalina.sh中的内存。

使用概要文件工具(如eclipse VisualVM)来修复应用程序中的内存泄漏

对于JDK 1.7.x或更高版本,使用G1GC,它在垃圾收集上花费10%,而不是其他GC算法的2%

除了使用-Xms1g -Xmx2g设置堆内存之外,请尝试'

-XX:+UseG1GC
-XX:G1HeapRegionSize=n,
-XX:MaxGCPauseMillis=m,
-XX:ParallelGCThreads=n,
-XX:ConcGCThreads=n`

请参阅甲骨文文章来微调这些参数。

关于SE中G1GC的一些问题:

Java 7 (JDK 7)垃圾收集和G1上的文档

Java G1垃圾回收在生产中

积极的垃圾回收策略

你需要在Jdeveloper中增加内存大小,转到setDomainEnv.cmd。

set WLS_HOME=%WL_HOME%\server
set XMS_SUN_64BIT=256
set XMS_SUN_32BIT=256
set XMX_SUN_64BIT=3072
set XMX_SUN_32BIT=3072
set XMS_JROCKIT_64BIT=256
set XMS_JROCKIT_32BIT=256
set XMX_JROCKIT_64BIT=1024
set XMX_JROCKIT_32BIT=1024


if "%JAVA_VENDOR%"=="Sun" (
set WLS_MEM_ARGS_64BIT=-Xms256m -Xmx512m
set WLS_MEM_ARGS_32BIT=-Xms256m -Xmx512m
) else (
set WLS_MEM_ARGS_64BIT=-Xms512m -Xmx512m
set WLS_MEM_ARGS_32BIT=-Xms512m -Xmx512m
)
and


set MEM_PERM_SIZE_64BIT=-XX:PermSize=256m
set MEM_PERM_SIZE_32BIT=-XX:PermSize=256m


if "%JAVA_USE_64BIT%"=="true" (
set MEM_PERM_SIZE=%MEM_PERM_SIZE_64BIT%


) else (
set MEM_PERM_SIZE=%MEM_PERM_SIZE_32BIT%
)


set MEM_MAX_PERM_SIZE_64BIT=-XX:MaxPermSize=1024m
set MEM_MAX_PERM_SIZE_32BIT=-XX:MaxPermSize=1024m

如果你有java8,你可以使用G1垃圾收集器,然后运行你的应用程序:

 -XX:+UseG1GC -XX:+UseStringDeduplication

这告诉G1寻找相似的字符串,并在内存中只保留其中一个字符串,其他字符串在内存中只是指向该字符串的指针。

当你有很多重复的字符串时,这很有用。此解决方案可能有效,也可能无效,这取决于每个应用程序。

更多关于
的信息 https://blog.codecentric.de/en/2014/08/string-deduplication-new-feature-java-8-update-20-2/ http://java-performance.info/java-string-deduplication/ < / p >

为此使用下面的代码在你的应用程序gradle文件下的android关闭。

< p > dexOptions { javaMaxHeapSize“4 g” } < / p >