【问题标题】:java.lang.OutOfMemoryError: GC overhead limit exceeded [duplicate]java.lang.OutOfMemoryError:超出 GC 开销限制 [重复]
【发布时间】:2011-08-15 21:52:19
【问题描述】:

我在创建几个(数十万个)HashMap 对象的程序中遇到此错误,每个对象有几个(15-20)个文本条目。在提交到数据库之前,这些字符串都必须被收集(而不是分解成更小的数量)。

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

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

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

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

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

related topic in StackOverflow. 中也讨论了这个问题

【问题讨论】:

  • 您可能需要更改您的算法并使用一些更有效的数据结构。你能告诉我们你试图实现的算法需要这么多的 HashMap 吗?
  • 我只是在阅读非常大的文本文件(每个文件数十万行),我无法控制这些文件,即它们无法分解。对于每一行文本,构造一个包含几个(实际上大约 10 个)小字符串值的 HashMap,一次又一次地使用相同的数据库字段名称。理想情况下,我希望能够在将数据发送到数据库之前读取整个文件。
  • 听起来在将数据发送到数据库之前读取整个文件是一个非常糟糕的解决方案......事实上,在可用内存的非常真实的限制内,它根本不起作用。你为什么要这样做呢? “一次又一次地使用相同的数据库字段名称”是什么意思?字段名称作为键或值?如果它们的字段是键,那么只需使用一个数组,该字段由它的位置隐含......如果它们是值,那么在将它们添加到地图之前实习它们。了解数据是什么会有所帮助。干杯。基思。
  • 它们是具有恒定值的键。实习生似乎确实有帮助,谢谢。

标签: java hashmap heap-memory g1gc


【解决方案1】:

您实际上是内存不足,无法顺利运行该过程。想到的选项:

  1. 像你提到的那样指定更多的内存,先试试-Xmx512m这样的东西
  2. 尽可能使用小批量的 HashMap 对象,以便一次处理
  3. 如果您有很多重复的字符串,请在它们上使用String.intern(),然后再将它们放入HashMap
  4. 使用HashMap(int initialCapacity, float loadFactor) 构造函数来调整您的情况

【讨论】:

  • 我已经使用了接近 HashMap 的初始容量,所以程序在那里几乎是最优的。
  • 如果它适用于更多内存,是否有理由不使用它?如果你使用-Xms128m -Xmx1024m 之类的东西,它实际上只会增长到你的最大值。似乎是最简单的选择。
  • 是的,我猜是最快的。我将 intern() 用于一些可能重复的值,问题也消失了。
  • 使用字符串intern 方法一直是个很糟糕的主意。因为字符串池的大小是固定的,并且在运行时需要时不能增长。 JVM 工程师 Aleksey Shipilev 甚至就这个主题发表了演讲(“Java.lang.String Catechism”)。
【解决方案2】:

嗯……你要么需要:

  1. 彻底重新考虑您的算法和数据结构,使其不再需要所有这些小 HashMap。

  2. 创建一个外观,允许您根据需要在内存中分页进出这些 HashMap。一个简单的 LRU 缓存可能就是票。

  3. 增加 JVM 可用的内存。如有必要,如果您拥有托管此野兽的机器的管理权,那么即使购买更多 RAM 也可能是最快、最便宜的解决方案。话虽如此:我通常不喜欢“投入更多硬件”解决方案,特别是如果可以在合理的时间范围内想到替代算法解决方案的话。如果你不断地在每一个问题上投入更多的硬件,你很快就会遇到收益递减规律。

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

【讨论】:

  • 见上面我的 cmets。用例非常简单,我正在寻找一种方法来处理整个大文件,而不会在过程中间中断。谢谢!
【解决方案3】:

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

您应该尝试重新考虑您的算法。在这种情况下,我会就该主题提供更多帮助,但在您提供有关问题背景的更多信息之前,我无法提供任何信息。

【讨论】:

  • 见上面我的 cmets。用例非常简单,我正在寻找一种方法来处理整个大文件,而不会在过程中间中断。谢谢!
【解决方案4】:

为了记录,我们今天遇到了同样的问题。我们使用这个选项修复了它:

-XX:-UseConcMarkSweepGC

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

【讨论】:

    【解决方案5】:

    @takrl:此选项的默认设置是:

    java -XX:+UseConcMarkSweepGC
    

    这意味着,此选项默认情况下是不活动的。所以当你说你使用了这个选项 “+XX:UseConcMarkSweepGC” 我假设您使用的是以下语法:

    java -XX:+UseConcMarkSweepGC
    

    这意味着您明确激活了此选项。 对于Java HotSpot VM Options@这个的正确语法和默认设置 document

    【讨论】:

    • 在我们的例子中,使用 -XX:+UseConcMarkSweepGC 降低了在高负载/高内存压力情况下出现“OutOfMemoryError:GC 开销限制超出”错误的风险,但另一方面它用完了更多 CPU,因此在正常负载情况下,请求的执行时间要长 5-10%。
    【解决方案6】:

    这帮助我摆脱了这个错误。这个选项禁用 -XX:+DisableExplicitGC

    【讨论】:

      【解决方案7】:

      使用替代的 HashMap 实现 (Trove)。标准 Java HashMap 具有 >12 倍的内存开销。 可以阅读详情here

      【讨论】:

      • net.sf.trove4jtrove4j3.0.3
      【解决方案8】:

      如果出现错误:

      “内部编译器错误:java.lang.OutOfMemoryError: java.lang.AbstractStringBuilder 超出了 GC 开销限制”

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

      【讨论】:

        【解决方案9】:

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

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

        如果操作正确,您的算法甚至不应该注意到变化——这里正确意味着使用一个类来表示表,甚至像哈希图一样给它一个 put(key, value) 和一个 get(key) 方法。

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

        【讨论】:

          【解决方案10】:

          如果在垃圾收集上花费了太多时间,并行收集器将抛出 OutOfMemoryError。特别是,如果超过 98% 的总时间花在垃圾回收上,而回收的堆少于 2%,则会抛出 OutOfMemoryError。此功能旨在防止应用程序长时间运行,而由于堆太小而几乎没有进展。如有必要,可以通过在命令行中添加选项-XX:-UseGCOverheadLimit 来禁用此功能。

          【讨论】:

          【解决方案11】:

          以下内容对我有用。只需添加以下sn-p:

          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"
              }
          }
          

          【讨论】:

          【解决方案12】:

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

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

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

          【讨论】:

            【解决方案13】:

            借助 Eclipse MATVisualVM 等配置文件工具修复应用程序中的内存泄漏

            对于JDK 1.7.x 或更高版本,使用G1GC与其他 GC 算法中的 2% 相比,垃圾回收的花费为 10%。

            除了用-Xms1g -Xmx2g设置堆内存,试试`

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

            查看oracle 文章以微调这些参数。

            关于SE中G1GC的一些问题:

            Java 7 (JDK 7) garbage collection and documentation on G1

            Java G1 garbage collection in production

            Agressive garbage collector strategy

            【讨论】:

              【解决方案14】:

              你需要在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
              

              【讨论】:

                【解决方案15】:

                如果您有 java8,并且可以使用 G1 垃圾收集器,则使用以下命令运行您的应用程序:

                 -XX:+UseG1GC -XX:+UseStringDeduplication
                

                这告诉G1去寻找相似的Strings并且只在内存中保留一个,其他的只是内存中指向那个String的指针。

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

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

                【讨论】:

                • 谢谢乔治。帮助我编译 Apache Camel:export MAVEN_OPTS="-Xms3000m -Xmx3000m -XX:+UseG1GC -XX:+UseStringDeduplication"
                • 欢迎您,注意 CPU 使用率,因为 G1 GC 对它的要求更高。
                【解决方案16】:

                为此,请在您的应用程序 gradle 文件中的 android 闭包下使用以下代码。

                dexOptions { javaMaxHeapSize "4g" }

                【讨论】:

                  猜你喜欢
                  • 2017-12-27
                  • 2020-09-08
                  • 2020-07-24
                  • 1970-01-01
                  • 2019-06-29
                  • 2015-04-25
                  • 1970-01-01
                  相关资源
                  最近更新 更多