我遇到了一个错误,其中我们的一个服务器应用程序几乎每秒都在使用越来越多的内存,并且我设法过滤出一个简短的示例,该示例仍然显示了该行为:

public class TestGetLastModifiedTime {
    private static final Path PATH = Paths.get("D:\\test.txt");
    private static final ScheduledExecutorService SCHEDULER = Executors.newScheduledThreadPool(1);

    public static void main(String[] args) {
        SCHEDULER.scheduleAtFixedRate(() -> getLastModifiedTime(), 0, 1, TimeUnit.SECONDS);
    }

    private static void getLastModifiedTime() {
        try {
            FileTime lastModifiedTime = Files.getLastModifiedTime(PATH);
        } catch (IOException ex) {
            throw new UncheckedIOException(ex);
        }
    }
}

在Windows 8.1和Java 8u20上运行。

通过VisualVM,我观察到最大堆大小并没有增加,并且堆本身一直在增加。同时,我在Windows任务管理器中观察到,产生的java.exe进程每秒保持使用(保留)更多内存。

有趣的是,当我从VisualVM中执行GC时,所有使用的堆内存几乎都重置为零,并且java.exe进程的使用内存不会像预期的那样减少,因为它被认为是保留的。

但是,在完成GC之后,内存使用率仍然每秒增加一次,而现在肯定有足够的可用堆空间。

元空间也不受影响。

对我来说,这真的很香,看起来JVM有内存泄漏。

谁能帮我一下,解释一下这里发生了什么事?

最佳答案

由于以下原因,我不认为它是泄漏:

  • 您提到触发gc时,内存使用率将恢复为默认值。这不是泄漏的工作方式。发生泄漏时,这些对象很容易到达,即使经过重复的垃圾回收,堆大小也不会显着减少。
  • 不断增长的堆并不意味着泄漏。这也可能确实意味着创建了太多对象。这是正常的,完全可以。在您的情况下,就像它被循环调用一样。是的
  • 在我的计算机上,在杀死进程之前,java -Xmx20M TestGetLastModifiedTime运行了10分钟完全正常。如果发生泄漏,它将最终抛出OutOfMemoryError或重复的gc的
  • 过多
  • 如果您在visualvm中观察到,则内存消耗在2mb和2.8mb之间跳跃。几乎没有任何关于泄漏的怀疑。此外,这种大量的内存消耗是可以预期的,因为Files.getLastModifiedTime(PATH)ExecutorService在内部会在这里和那里创建小的实用程序对象。所以对我来说看起来很好。

  • 我的机器上的行为:

    关键是,Windows管理器显示出更高的堆使用率。这是很有可能。 JVM可以根据需要保留空间。与不使用gc相比,增加堆利用率当然可以。哪一个完全有道理(有钱人为什么要节俭?)。

    这就是为什么诸如观察Windows Manager/free -m等工具无法正确观察内部情况的原因。为了快速估算,您可能需要执行以下操作:
    jstat -gccapacity 9043  | tail -n 1 | awk '{ print $4, $5, $6, $10 }' | python -c "import sys; nums = [x.split(' ') for x in sys.stdin.readlines()]; print(str(sum([float(x) for x in nums[0]]) / 1024.0) + ' mb');"
    # change the pid from 9043 to your jvm process id.
    #jvm process id can be known by running `jcmd` command (available as part of jdk)
    

    free -m/Windows Manager相比,这仍然可以为您提供更好的估计

    10-04 12:17
    查看更多