测量代码单元的内存使用情况

我有一个函数memory ,它需要一个函数并测量它的内存使用情况:

import java.lang.management.ManagementFactory

def memory[T](
    f: ⇒ T
)(
    mu: Long ⇒ Unit
): T = {
    val memoryMXBean = ManagementFactory.getMemoryMXBean
    memoryMXBean.gc()
    val usedBefore = memoryMXBean.getHeapMemoryUsage.getUsed
    println(s"${memoryMXBean.getObjectPendingFinalizationCount()} pending, used $usedBefore")
    val r = f
    memoryMXBean.gc()
    val usedAfter = memoryMXBean.getHeapMemoryUsage.getUsed
    println(s"${memoryMXBean.getObjectPendingFinalizationCount()} pending, used $usedAfter")
    mu(usedAfter - usedBefore)
    r
}

获取new Array[Byte](1024*1024)使用的内存量应该返回1MB。

memory{new Array[Byte](1024*1024)}{r=>println(s"$r byte")}

但是第一次调用内存会返回一个负面结果,后续调用(即使是使用不同的bodys)内存使用情况也很好:

scala> memory{new Array[Byte](1024*1024)}{r=>println(s"$r byte")}
0 pending, used 45145040
0 pending, used 45210384
65344 byte                <- 65kb != 1MB

scala> memory{new Array[Byte](1024*1024)}{r=>println(s"$r byte")}
0 pending, used 45304512
0 pending, used 46353104
1048592 byte              <- Correct

在两个memoryMXBean.getHeapMemoryUsage之间的某个地方被释放,但在那里没有挂起的对象被释放。 当你有一个空的主体时,这个行为也可以被确定(记得重新启动scala控制台来获得这个结果):

scala> memory{}{r=>println(s"$r byte")}
0 pending, used 44917584
0 pending, used 44025552
-892032 byte              <- 800kb less memory?

scala> memory{}{r=>println(s"$r byte")}
0 pending, used 44070440
0 pending, used 44069960
-480 byte                 <- This is ok

在控制台上执行gc()getHeapMemoryUsage产生如下结果:

scala> import java.lang.management.ManagementFactory; val memoryMXBean = ManagementFactory.getMemoryMXBean; memoryMXBean.setVerbose(true)
import java.lang.management.ManagementFactory
memoryMXBean: java.lang.management.MemoryMXBean = sun.management.MemoryImpl@2f98635e

scala> memoryMXBean.gc(); memoryMXBean.getHeapMemoryUsage
[GC (System.gc())  57400K->44462K(109056K), 0,0148555 secs]
[Full GC (System.gc())  44462K->39602K(109056K), 0,2641397 secs]
res1: java.lang.management.MemoryUsage = init = 33554432(32768K) used = 41358440(40389K) committed = 111673344(109056K) max = 239075328(233472K)

scala> memoryMXBean.gc(); memoryMXBean.getHeapMemoryUsage
[GC (System.gc())  46702K->40258K(111104K), 0,0025801 secs]
[Full GC (System.gc())  40258K->39631K(111104K), 0,1988796 secs]
res2: java.lang.management.MemoryUsage = init = 33554432(32768K) used = 40583120(39631K) committed = 113770496(111104K) max = 239075328(233472K)

41358440 - 40583120 = 775320 ,内存使用量减少将近800kb(请参阅used )。

为什么第一次测量返回错误的结果? 有没有办法解决这个问题,而不是两次运行该方法?

在Arch Linux上使用Scala 2.12.1-20161205-201300-2787b47 (OpenJDK 64-Bit Server VM, Java 1.8.0_112)

谢谢!


使用JAMM

如果您想检查JVM上的数据结构消耗多少内存,则应该查看Instrumentation库,例如JAMM。 它通过遍历要测量的对象的对象图以及利用有关正在运行的JVM上的内存布局的知识来工作。

请注意,您将返回的数据特定于您正在使用的JVM版本和体系结构。 在不同的体系结构中,由于指针大小和编码不同,内存消耗可能会有所不同。 而在不同的JVM上,即使是内存布局也可能不同。

不过,这是在JVM上实现高效数据结构的强大工具。

下面是你将如何使用Scala的JAMM:

val o = new Array[Byte](1024*1024)
val mm = new MemoryMeter()
println("Size of new Array[Byte](1024*1024): " + mm.measureDeep(o))

结果如下:

Size of new Array[Byte](1024*1024): 1048592

JAMM库是一个挂钩到JVM中的Java代理。 因此,使用JAMM需要下载jamm jar并添加一个参数(例如-javaagent:jamm-0.3.0.jar )到java选项,最好使用javaOptions sbt键。

自动记忆测试

请注意,如果您依赖于您编写的某些数据结构的紧凑内存中表示,则应该执行自动化测试,以确保内存中表示符合您的预期。 有关如何设置这个的灵感,这里是一个最小的项目,它导入并配置JAMM Java代理以进行测试。

为了游玩,你可以将你的测试代码添加到JammTest并用sbt test:run运行它。


您遇到的问题是内存使用量未被准确计入以提高性能。 这表现在两个方面

  • 使用的内存用于尚未收集的活动对象和对象。 当你创建一个大对象时,你可以触发一个集合,并且使用比以前更少的内存。
  • 较小的对象从线程本地分配缓冲区或TLAB分配。 TLAB是每个线程的本地缓冲区,以最大限度地减少对Eden空间的争用,从而允许线程同时分配。 不好的一面是你没有看到每个TLAB有多少被使用,并且偶尔会看到大量的跳跃。 解决这个问题的一个简单方法是将TLAB关闭-XX:-UseTLAB ,即使是new Object()也会得到准确的帐户(假设GC不会发生)
  • 链接地址: http://www.djcxy.com/p/80173.html

    上一篇: Measure memory usage of code unit

    下一篇: How to properly measure process memory usage on Linux?