Files.getLastModifiedTime()是否存在内存泄漏问题?

7

我遇到了一个bug,我们的某个服务器应用程序每秒钟使用的内存越来越多,我已经筛选出一个简短的示例,仍然显示出这种行为:

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存在内存泄漏。

有人能帮助我解决这个问题并解释一下这里发生了什么吗?


没有抛出任何异常吗? - fge
这就是垃圾回收的工作原理。程序不断分配内存,最终变成垃圾。当它无法从空闲池中再分配更多内存时,它会收集垃圾。只有在堆在 GC(无论是由 JVM 自动触发还是手动触发)之后没有减少时才会出现问题。 - kdgregory
@kdgregory 如果堆中有足够的空间可用,那么就不需要分配额外的内存。 - skiwi
2
请将您的lambda表达式与手动创建File对象并使用File.lastModified()进行比较。 - Pimgd
此外,将“-Xmx”设置为大约8 MB。看看是否会出现内存不足的情况。 - Pimgd
显示剩余4条评论
1个回答

7
我认为以下原因说明这不是泄漏:
  1. 您提到当您触发gc时,内存使用量会回归默认值。这不是泄漏的工作方式。当存在泄漏时,这些对象是强可达的,即使进行重复垃圾收集,堆大小也不会显着减少。
  2. 增长的堆不意味着泄漏。它也可能真正意味着创建了太多对象。这很正常,也很好。而在您的情况下,由于它在循环中调用。是的
  3. 在我的机器上,java -Xmx20M TestGetLastModifiedTime运行得非常顺利,持续10分钟,然后我杀死了进程。如果有泄漏,它最终会抛出OutOfMemoryError或者有太多重复的gc
  4. 如果您在visualvm中观察,内存消耗量在2mb和2.8mb之间跳动。这几乎没有任何泄漏的疑虑。此外,这么多的内存消耗是可以预料的,因为Files.getLastModifiedTime(PATH)ExecutorService在内部会创建一些小型实用程序对象。所以我认为这看起来非常好。

在我的机器上的行为:

enter image description here

关于窗口管理器显示更高的堆使用情况。这是很有可能的。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提供了更好的估算。


我更倾向于本地资源泄漏的思路,即在调用Files.getLastModifiedTime()时,JVM中的底层C代码可能会导致内存泄漏。也许我在问题中没有表达清楚。 - skiwi
1
当使用JVisualVM进行监控时,我猜测JMX不断发送有关内存使用情况的更新的开销要比每秒钟执行一次Files.getLastModifiedTime调用的内存消耗大得多... - Holger
@skiwi 我在使用Ubuntu。在我的电脑上看起来正常。可能是Windows平台的问题。不同平台之间执行相同操作可能会有不同的结果。因此,如果在Windows上使用类似的逻辑时,你仍然需要进行确认。 - Jatin
@Holger 这也会在没有通过 JVisualVM 监控的情况下发生。 - skiwi
1
@skiwi:我已经明白了,即使没有使用JVisualVM,问题仍然存在。我想强调的是,当尝试分析这个特定的问题时,JVisualVM生成的图表很可能会导致错误的方向。而JMX的副作用甚至可能会隐藏问题,使其难以分析... - Holger

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