Skip to content

Measure object memory consumption in Clojure

Notifications You must be signed in to change notification settings

clojure-goes-fast/clj-memory-meter

Repository files navigation

clj-memory-meter CircleCI

clj-memory-meter is a Clojure library that allows you to inspect how much memory an object occupies at runtime, together with all its child fields. It is a wrapper around Java Agent for Memory Measurements.

Extra features compared to jamm:

  1. Can be added to the project as a simple dependency (don't have to provide a separate agent file and point to it with a JVM option).
  2. Loadable at runtime.
  3. Human-readable size output.

jamm JAR file is shipped together with clj-memory-meter and unpacked at runtime.

Usage

JDK11+: you must start your application with JVM option -Djdk.attach.allowAttachSelf, otherwise the agent will not be able to dynamically attach to the running process. For Leiningen, add :jvm-opts ["-Djdk.attach.allowAttachSelf"] to project.clj. For tools.deps, add the same :jvm-opts to deps.edn or write -J-Djdk.attach.allowAttachSelf explicitly in your REPL command.

Add com.clojure-goes-fast/clj-memory-meter to your dependencies:

Once loaded, you can measure objects like this:

(require '[clj-memory-meter.core :as mm])

;; measure calculates total memory occupancy of the object

(mm/measure "Hello, world!")
;=> "72 B"

(mm/measure [])
;=> "240 B"

(mm/measure (into {} (map #(vector % (str %)) (range 100))))
;=> "9.6 KiB"

;; :shallow true calculates only memory occupied by the object itself,
;; without children

(mm/measure (object-array (repeatedly 100 #(String. "hello"))) :shallow true)
;=> "416 B"
(mm/measure (object-array (repeatedly 100 #(String. "hello"))))
;=> "2.8 KiB"

;; :bytes true can be passed to return the size in bytes as a number

(mm/measure (object-array (repeatedly 100 #(String. "hello"))) :bytes true)
;=> 2848

;; :debug true can be passed to print the object hierarchy. You can also pass an
;; integer number to limit the number of nested levels printed.

(mm/measure (apply list (range 4)) :debug true)

; root [clojure.lang.PersistentList] 256 bytes (40 bytes)
;   |
;   +--_first [java.lang.Long] 24 bytes (24 bytes)
;   |
;   +--_rest [clojure.lang.PersistentList] 192 bytes (40 bytes)
;     |
;     +--_first [java.lang.Long] 24 bytes (24 bytes)
;     |
;     +--_rest [clojure.lang.PersistentList] 128 bytes (40 bytes)
;       |
;       +--_first [java.lang.Long] 24 bytes (24 bytes)
;       |
;       +--_rest [clojure.lang.PersistentList] 64 bytes (40 bytes)
;         |
;         +--_first [java.lang.Long] 24 bytes (24 bytes)

;; Custom MemoryMeter object can be passed. See what you can configure here:
;; https://github.com/jbellis/jamm/blob/master/src/org/github/jamm/MemoryMeter.java

Note on JDK17+: Starting with Java 17, JVM no longer allows accessing private fields of classes residing in external modules. On those versions of Java, JAMM and clj-memory-meter utilize Unsafe to get into such private fields. As any Unsafe usage, it can potentially crash the application. Use at your own risk. Also, the Unsafe itself may go away in the future versions of Java.

License

jamm is distributed under Apache-2.0. See APACHE_PUBLIC_LICENSE file. The location of the original repository is https://github.com/jbellis/jamm.


clj-memory-meter is distributed under the Eclipse Public License. See ECLIPSE_PUBLIC_LICENSE.

Copyright 2018-2024 Alexander Yakushev