简体   繁体   English

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

[英]Measure memory usage of code unit

I have a function memory that takes a function and measures the memory usage of it: 我有一个函数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
}

Getting the amount of memory used by new Array[Byte](1024*1024) should return 1MB. 获取new Array[Byte](1024*1024)使用的内存量应返回1MB。

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

But the very first call of memory returns a negative result, subsequent calls measure (even with different bodys) the memory usage just fine: 但是第一次调用内存会返回负结果,随后的调用可以测量(即使使用不同的主体)内存使用情况也很好:

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

Somewhere between the two memoryMXBean.getHeapMemoryUsage something gets freed, but there where no pending object to be freed. 在两个memoryMXBean.getHeapMemoryUsage之间的某个位置被释放了一些东西,但是没有待释放的对象被释放。 This behaviour can be also determined when you have an empty body (remember to restart the scala console to get this result): 当您的身体为空时,也可以确定此行为(请记住重新启动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

Also executing the gc() and getHeapMemoryUsage on the console produces this result: 同样在控制台上执行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 , almost 800kb less memory usage (see used ). 41358440 - 40583120 = 775320 ,几乎减少了800kb的内存使用量(请参阅已used )。

Why does the very first measurement return a wrong result? 为什么第一次测量会返回错误的结果? Is there a way to fix this other than running the method twice? 除了两次运行该方法之外,还有其他方法可以解决此问题吗?

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

Thanks! 谢谢!

Using JAMM 使用JAMM

If you want to check how much memory a data structure on the JVM consumes, you should look into instrumentation libraries such as JAMM . 如果要检查JVM上的数据结构消耗了多少内存,则应查看JAMM之类的工具库。 It works by traversing the object graph of the object you want to measure, and exploiting knowledge about the memory layout on the JVM you are running on. 它通过遍历您要测量的对象的对象图并利用有关您正在运行的JVM上的内存布局的知识来工作。

Note that the data you will get back is specific to the JVM version and architecture you are using. 请注意,您将获得的数据特定于所使用的JVM版本体系结构 On different architectures, the memory consumption might be different because of different pointer size and encoding. 在不同的体系结构上,由于指针大小和编码的不同,内存消耗可能会有所不同。 And on different JVMs, even the memory layout might be different. 在不同的JVM上,甚至内存布局也可能不同。

Nevertheless, this is a powerful tool to implement highly efficient data structures on the JVM. 但是,这是在JVM上实现高效数据结构的强大工具。

Here is how you would use JAMM from scala: 这是从scala使用JAMM的方式:

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

And here is the result: 结果如下:

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

The JAMM library is a java agent that hooks into the JVM. JAMM库是挂接到JVM的Java代理。 Therefore, using JAMM requires downloading the jamm jar and adding a parameter (eg -javaagent:jamm-0.3.0.jar ) to the java options, preferably using the javaOptions sbt key. 因此,使用JAMM要求下载jamm jar并向java选项添加参数(例如-javaagent:jamm-0.3.0.jar ),最好使用javaOptions sbt键。

Automated memory tests 自动内存测试

Note that if you rely on compact in-memory representation for some data structures you write, you should have automated tests that ensure that the in-memory representation is as you expect. 请注意,如果您依赖紧凑的内存表示形式来编写某些数据结构,则应该进行自动化测试以确保内存表示形式符合您的期望。 For inspiration on how to set this up, here is a minimal project that imports and configures the JAMM java agent for the tests. 为了启发如何进行设置,这是一个最小的项目 ,用于导入和配置JAMM Java代理以进行测试。

To play around, you can just add your test code to JammTest and run it with sbt test:run . 要进行测试,您只需将测试代码添加到JammTest并使用sbt test:run运行它。

The problem you have is that memory usage is not accurately accounted for to improve performance. 您遇到的问题是无法正确考虑内存使用情况以提高性能。 This shows in two areas 这显示在两个方面

  • The memory used is for live objects and object not yet collected. 所使用的内存用于活动对象和尚未收集的对象。 When you create a large object you can trigger a collection and end up with less memory in use than before. 创建大对象时,您可以触发集合并最终使用的内存少于以前。
  • Smaller objects are allocated from a Thread Local Allocation Buffer or TLAB. 较小的对象是从线程本地分配缓冲区或TLAB中分配的。 A TLAB is a local buffer for each thread to minimise the contention on the Eden space and thus allow thread to allocate concurrently. TLAB是每个线程的本地缓冲区,以最大程度地减少对Eden空间的争用,从而允许线程并发分配。 The down side is you don't see how much of each of these TLABs are used and only see big jumps in usage occasionally. 不利的一面是,您看不到每个TLAB的使用量,仅偶尔看到使用量的飞跃。 A simple way around this is to turn the TLABs off -XX:-UseTLAB and you will get accurate account for even new Object() (Assuming a GC doesn't occur) 解决此问题的一种简单方法是关闭-XX:-UseTLAB ,您甚至可以准确获得new Object() (假设未发生GC)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM