我正在测试在 JDK 1.6 中运行的 java 应用程序中堆大小的使用。我使用工具 VisualVM 来监控堆使用情况。我在几分钟内发现最大堆大小使用量约为 500 MB。我使用了调用 System.gc() 的选项“Perform GC”。我第一次使用它时,最大堆减少到 410MB,然后我再次使用它来获得 130MB 和下一次到 85MB。我连续打了四个电话,没有任何间隔。为什么调用 System.gc() 没有在第一次将所有 Heap 收集到 85MB。这背后是否还有其他原因。或者我应该尝试任何其他方法?
3 回答
System.gc() 将在所有对象被扫描一次后返回。
一个对象应该在它被收集后被 finalized()。大多数对象不实现此方法,但对于那些实现的对象,它们被添加到队列中以便稍后清理。这意味着这些对象还不能被清理(不是保存它们的队列节点),即触发 GC 的行为会暂时增加内存消耗。
此外,还有一些对象的 SoftReferences 可能会或可能不会被 GC 清理。假设只有在没有清理太多其他内容的情况下才应清理这些内容。
简而言之,并不是所有的物体都可以在一个周期内清理干净。
System.gc() 请求 JVM 启动垃圾收集。如果您期望在 System.gc() 时立即调用 GC,那么这是一个错误的概念。多次调用它无济于事。无法将 System.gc() 映射到实际的垃圾收集。此外,无论您调用 System.gc() 多少次,JVM 只会在准备好执行 GC 时执行。可能发生的情况是,即使使用第一个 System.gc(),堆大小也会减少,但并不是在您调用它时就立即减少。与您的第一个 System.gc() 相关的垃圾收集可能正在后台完成,同时您的代码正在到达第三个 System.gc() 语句。
如果您非常确定仅添加多个 System.gc() 可以帮助您减少堆大小。然后您需要检查在第一个和最后一个 System.gc() 之间在 JVM 中创建的所有对象。可能有其他线程创建对象。
一个可能的原因可能是java.lang.ref.Reference
类型的使用。如果 GC 要破坏“引用”,这将在 GC 本身完成后发生。任何因此变得无法访问的对象都留给下一个 GC 周期处理。
最终确定的工作方式相同。如果一个对象需要终结,它和所有可从它(仅)到达的对象很可能只能在下一个 GC 周期中收集。
还有一个问题是 GC 的堆收缩算法是非侵略性的。根据Java HotSpot VM 选项页面,如果垃圾回收后 70% 以上是空闲的,GC 只会缩小堆。但是,这是否指的是完整的 GC 尚不完全清楚。因此,您可以让 GC 进行部分 GC 并缩小,然后进行完整 GC 并进一步缩小。
(有些人从System.gc()
javadocs 的措辞中推断出它将执行完整的 GC。但是,我怀疑这实际上是版本/ GC 相关的。)
但老实说,这一切都应该没有实际意义。试图强迫应用程序尽可能多地返还内存是没有意义的。您可能会强迫它丢弃缓存的数据。当应用程序再次激活时,它将开始重新加载其缓存。