【问题标题】:Java high memory usage - GC StringsJava 高内存使用 - GC 字符串
【发布时间】:2016-12-08 11:45:44
【问题描述】:

我正在尝试编写对资源影响最小的代码,但我遇到了我不理解的 GC 行为。

  1. 显然字符串不会立即从内存中清除,即使它们不再使用。

    for(int i = 0; i < 999999999; i++)
        System.out.println("Test");
    

Memory usage graph

根据图表,我假设在每次循环运行时都会创建一个新的 String 对象,但在下一次循环运行时它不会自动清除 - 如果是这种情况,我想知道它为什么会发生如果我误读了情况,我想知道“幕后”到底发生了什么。

  1. 当我将睡眠添加到我在图表上方显示的代码中时,它变得稳定了,这是什么原因?

    for(int i = 0; i < 999999999; i++){
    
        System.out.println("Test");
    
        try{
            Thread.sleep(1);
        }
        catch(Exception e){}
    }
    

Stable graph

我对给定案例还有几个问题:

  • 可以强制 GC 更具攻击性吗?我的意思是缩短对象的生命周期,而不是减少 JVM 分配的内存?

  • 如果我在变量中插入一个空值,它会影响到被 GC 清除之前的时间吗?

  • 当我需要对字符串运行大量正则表达式匹配时,使用字符串的正确方法是什么?

  • 声明 String 对象“过时”以便 GC 将其清除的最佳方法是什么?

  • 出现上述情况是否是因为Java对Strings进行了自动实习,如果是,有没有办法取消它?

非常感谢!

【问题讨论】:

  • 你完全错了。无论您循环多少次,您的示例中都只创建了一个字符串。
  • "Test" 将是 interned,只有一个实例
  • @Kayaman 你能说得更具体点吗?,高内存使用的原因是什么...
  • 我认为当您使用 System.out.println("Test") 循环 999999999 次时出现问题。您正在将它写在屏幕上(它使用了内存)。最终使用的内存是 999999999 * 4 位。这等效于使用大字符串数组。如果你更换。 System.out.println("Test") with example String test = "test".
  • @Kayaman,我在我的项目中得到了相同的结果,所以我创建了最简单的代码来显示问题......有人能告诉我为什么内存使用率如此之高吗? 50MB RAM 用于只有 print&for 循环的代码,这是没有意义的。非常感谢!

标签: java string memory garbage-collection jvm


【解决方案1】:

我假设每次循环运行都会创建一个新的 String 对象

不,如果它在每次迭代中创建一个新字符串,你会得到更多的垃圾。

在这种垃圾率下,它可能是分析器正在分配一些对象。

一个字符串字面量被创建一次。 (在 JVM 中)

但在下一次循环运行时不会自动清除

正确,即使它是在每次迭代时创建的,GC 只在需要时运行,但在每次迭代中执行它的成本会非常高。

当我将睡眠添加到我在图表上方显示的代码中时,它变得稳定了,这是什么原因?

您的应用程序大大减慢了速度。

可以强制 GC 更具攻击性吗?

您可以使 Eden 空间更小,但这会降低您的应用程序的速度。

如果我在变量中插入一个空值,它会影响到它被 GC 清除之前的时间吗?

不,这很少做任何事情。

当我需要在字符串上运行大量正则表达式匹配时,使用字符串的正确方法是什么

正则表达式会产生很多垃圾。如果您想减少分配并加速您的应用程序,请避免使用正则表达式。

我最近将一些常用的正则表达式替换为直接字符串处理,从而将应用程序的速度提高了 3 倍。

声明 String 对象“过时”以便 GC 将其清除的最佳方法是什么?

在有限的范围内使用它。当作用域结束时,对它的引用也会结束,它可以被 GCed。

出现上述情况是不是因为Java做了一个自动实习生

一旦 String 被实习,它就不会被重新创建。

对于字符串,如果是,有没有办法取消它?

当然,每次都强制它创建一个新字符串。这当然会产生更多的垃圾并且速度要慢得多(并且代码更长),但是如果您愿意,可以这样做。

【讨论】:

    【解决方案2】:

    垃圾收集器在收集时间或多或少时收集。

    • 是的,取决于您使用的收集器。您可以设置数十个虚拟机属性,其中一些会相互影响。
    • 我认为它不会出现在“较新”的 JDK 中
    • 通常你不在乎。说到 GC,更重要的是不要将大量数据加载到内存中。字符串的一个特点是它的实习生,但字符串也会像其他对象一样被 gc 处理。
    • 当不再引用字符串/实习生时(退出大括号时)
    • 不,确实会出现这种情况,因为java的GC是这样工作的……

    我可以解释基于 CMS/ParNew 的 GC 效果(因为我最了解这个组合),它的工作原理如下: 堆被分成两个区域(我现在不包括 PermGen)。 老老少少 Young被分成'eden'和'copy'(或幸存者) 当你生成一个新对象时,它将进入 Young->Eden。在某个时候,eden 将达到其最大内存,然后未使用的对象将被删除,仍然具有引用的对象将被复制到 Young->Copy。

    随着程序继续运行,Young->Copy 将达到其最大内存。会在另一个Young->Copy内存空间中再次复制。

    在某些时候,它不能再这样做了,所以一些对象会从 Young->Copy 移动到 Old,这取决于复制计数器(我认为)。旧堆的情况相同。

    那么你可以调整什么?首先,您通常具有吞吐量(批处理)和低延迟(网页),ParNew/CMS 组合用于低延迟。

    由于我最了解 ParNew/CMS,我将首先解释您可以考虑调整的内容:

    • 您可以调整最大内存(内存越大意味着管理越多,应用程序需要运行的内存越少越好……一般来说)
    • 您可以在年轻人和老年人之间调整堆配给
    • 您可以在 Young 中调整 eden 和 copy 之间的比率
    • 您可以调整 CMS 开始收集周期的时间

    然后还有更多。根据我的个人经验,对于大型应用程序,我们通常使用以下设置:

    • 将最小和最大内存固定为相同大小(最大堆不变)
    • 新旧比例约为 1:4 到 1:7
    • 禁用 System.gc()
    • 记录很多 gc 的东西
    • 在 OutOfMemory 上发出警报
    • 每周对日志进行分析并决定调整参数。 (一次只有一个参数;)

    如果你真的想知道一切的背后是什么,我建议你读一本书,因为真的,真的,真的发生了很多事情。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-04-01
      • 2015-10-24
      • 1970-01-01
      • 1970-01-01
      • 2011-08-29
      • 1970-01-01
      • 1970-01-01
      • 2019-11-12
      相关资源
      最近更新 更多