{"id":19858615,"url":"https://github.com/clojure-goes-fast/clj-memory-meter","last_synced_at":"2025-05-15T01:05:39.406Z","repository":{"id":49374785,"uuid":"123902861","full_name":"clojure-goes-fast/clj-memory-meter","owner":"clojure-goes-fast","description":"Measure object memory consumption in Clojure","archived":false,"fork":false,"pushed_at":"2025-05-13T15:47:51.000Z","size":178,"stargazers_count":308,"open_issues_count":0,"forks_count":12,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-05-13T16:49:23.392Z","etag":null,"topics":["clojure","heap","memory"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/clojure-goes-fast.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"license/APACHE_PUBLIC_LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2018-03-05T10:23:05.000Z","updated_at":"2025-05-13T15:47:55.000Z","dependencies_parsed_at":"2024-04-11T08:27:50.309Z","dependency_job_id":"dbca5f88-d13a-4e43-81bd-13a4e072d256","html_url":"https://github.com/clojure-goes-fast/clj-memory-meter","commit_stats":{"total_commits":32,"total_committers":2,"mean_commits":16.0,"dds":0.03125,"last_synced_commit":"a840bfcf8a88dbfc5d8ab77e75439423eee2c341"},"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clojure-goes-fast%2Fclj-memory-meter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clojure-goes-fast%2Fclj-memory-meter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clojure-goes-fast%2Fclj-memory-meter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clojure-goes-fast%2Fclj-memory-meter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/clojure-goes-fast","download_url":"https://codeload.github.com/clojure-goes-fast/clj-memory-meter/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254000820,"owners_count":21997441,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["clojure","heap","memory"],"created_at":"2024-11-12T14:24:07.768Z","updated_at":"2025-05-15T01:05:39.399Z","avatar_url":"https://github.com/clojure-goes-fast.png","language":"Clojure","funding_links":[],"categories":["Clojure"],"sub_categories":[],"readme":"# clj-memory-meter [![CircleCI](https://img.shields.io/circleci/build/github/clojure-goes-fast/clj-memory-meter/master.svg)](https://dl.circleci.com/status-badge/redirect/gh/clojure-goes-fast/clj-memory-meter/tree/master) ![](https://img.shields.io/badge/dependencies-none-brightgreen) [![](https://img.shields.io/clojars/dt/com.clojure-goes-fast/clj-memory-meter?color=teal)](https://clojars.org/com.clojure-goes-fast/clj-memory-meter) [![](https://img.shields.io/badge/-changelog-blue.svg)](CHANGELOG.md)\n\n**clj-memory-meter** is a Clojure library that let's you inspect how much memory\nan object occupies in the heap. It is a wrapper around [Java Agent for Memory\nMeasurements](https://github.com/jbellis/jamm).\n\nExtra features compared to **jamm**:\n\n1. Can be added to the project as a simple dependency (don't have to provide a\nseparate agent file and point to it with a JVM option).\n2. Loadable at runtime.\n3. Human-readable size output.\n4. Memory usage tracing.\n\n**jamm** JAR file is shipped together with **clj-memory-meter** and unpacked at\nruntime.\n\n## Usage\n\nOn JDK11 and above, you must start your application with JVM option\n`-Djdk.attach.allowAttachSelf`, otherwise the agent will not be able to\ndynamically attach to the running process.\n\n - For Leiningen, add `:jvm-opts [\"-Djdk.attach.allowAttachSelf\"]` to\n `project.clj`.\n - For tools.deps, add the same `:jvm-opts` to `deps.edn` or write\n`-J-Djdk.attach.allowAttachSelf` explicitly in your REPL command.\n\nAdd `com.clojure-goes-fast/clj-memory-meter` to your dependencies:\n\n[![](https://clojars.org/com.clojure-goes-fast/clj-memory-meter/latest-version.svg)](https://clojars.org/com.clojure-goes-fast/clj-memory-meter)\n\nOnce loaded, you can measure objects like this:\n\n```clojure\n(require '[clj-memory-meter.core :as mm])\n\n;; measure calculates total memory occupancy of the object\n\n(mm/measure \"Hello, world!\")\n;=\u003e \"72 B\"\n\n(mm/measure [])\n;=\u003e \"240 B\"\n\n(mm/measure (into {} (map #(vector % (str %)) (range 100))))\n;=\u003e \"9.6 KiB\"\n\n;; :shallow true calculates only memory occupied by the object itself,\n;; without children\n\n(mm/measure (object-array (repeatedly 100 #(String. \"hello\"))) :shallow true)\n;=\u003e \"416 B\"\n(mm/measure (object-array (repeatedly 100 #(String. \"hello\"))))\n;=\u003e \"2.8 KiB\"\n\n;; :bytes true can be passed to return the size in bytes as a number\n\n(mm/measure (object-array (repeatedly 100 #(String. \"hello\"))) :bytes true)\n;=\u003e 2848\n\n;; :debug true can be passed to print the object hierarchy. You can also pass an\n;; integer instead of true to limit the number of nested levels printed.\n\n(mm/measure (apply list (range 4)) :debug true)\n\n; root [clojure.lang.PersistentList] 256 bytes (40 bytes)\n;   |\n;   +--_first [java.lang.Long] 24 bytes (24 bytes)\n;   |\n;   +--_rest [clojure.lang.PersistentList] 192 bytes (40 bytes)\n;     |\n;     +--_first [java.lang.Long] 24 bytes (24 bytes)\n;     |\n;     +--_rest [clojure.lang.PersistentList] 128 bytes (40 bytes)\n;       |\n;       +--_first [java.lang.Long] 24 bytes (24 bytes)\n;       |\n;       +--_rest [clojure.lang.PersistentList] 64 bytes (40 bytes)\n;         |\n;         +--_first [java.lang.Long] 24 bytes (24 bytes)\n\n;; Custom MemoryMeter object can be passed. See what you can configure here:\n;; https://github.com/jbellis/jamm/blob/master/src/org/github/jamm/MemoryMeter.java\n```\n\n### Troubleshooting\n\nStarting with Java 17, JVM no longer allows access to private fields of classes\nresiding in external modules. On newer Java versions, clj-memory-meter utilizes\nUnsafe to get into such private fields. As any Unsafe usage, it can potentially\ncrash the application. Use at your own risk.\n\nBecause of Unsafe, you may eventually run into errors like this one:\n\n```\nExecution error (UnsupportedOperationException) at sun.misc.Unsafe/objectFieldOffset (Unsafe.java:645).\ncan't get field offset on a hidden class: private final java.util.regex.Pattern$BmpCharPredicate java.util.regex.Pattern$BmpCharPredicate$$Lambda$22/0x80000002c.arg$1\n```\n\nThe only way to prevent this from happening is to start the REPL with\n`--add-opens` JVM options for the in-module private classes. Expand the block\nbelow for the tools.deps alias I use locally for my development REPL.\n\n\u003cdetails\u003e\n  \u003csummary\u003eClick to show full :add-opens alias\u003c/summary\u003e\n  \u003cpre\u003e\u003ccode\u003e:add-opens {:jvm-opts [\"--add-opens=java.base/java.io=ALL-UNNAMED\"\n                       \"--add-opens=java.base/java.lang=ALL-UNNAMED\"\n                       \"--add-opens=java.base/java.lang.annotation=ALL-UNNAMED\"\n                       \"--add-opens=java.base/java.lang.invoke=ALL-UNNAMED\"\n                       \"--add-opens=java.base/java.lang.module=ALL-UNNAMED\"\n                       \"--add-opens=java.base/java.lang.ref=ALL-UNNAMED\"\n                       \"--add-opens=java.base/java.lang.reflect=ALL-UNNAMED\"\n                       \"--add-opens=java.base/java.math=ALL-UNNAMED\"\n                       \"--add-opens=java.base/java.net=ALL-UNNAMED\"\n                       \"--add-opens=java.base/java.net.spi=ALL-UNNAMED\"\n                       \"--add-opens=java.base/java.nio=ALL-UNNAMED\"\n                       \"--add-opens=java.base/java.nio.channels=ALL-UNNAMED\"\n                       \"--add-opens=java.base/java.nio.channels.spi=ALL-UNNAMED\"\n                       \"--add-opens=java.base/java.nio.charset=ALL-UNNAMED\"\n                       \"--add-opens=java.base/java.nio.charset.spi=ALL-UNNAMED\"\n                       \"--add-opens=java.base/java.nio.file=ALL-UNNAMED\"\n                       \"--add-opens=java.base/java.nio.file.attribute=ALL-UNNAMED\"\n                       \"--add-opens=java.base/java.nio.file.spi=ALL-UNNAMED\"\n                       \"--add-opens=java.base/java.security=ALL-UNNAMED\"\n                       \"--add-opens=java.base/java.security.cert=ALL-UNNAMED\"\n                       \"--add-opens=java.base/java.security.interfaces=ALL-UNNAMED\"\n                       \"--add-opens=java.base/java.security.spec=ALL-UNNAMED\"\n                       \"--add-opens=java.base/java.text=ALL-UNNAMED\"\n                       \"--add-opens=java.base/java.text.spi=ALL-UNNAMED\"\n                       \"--add-opens=java.base/java.time=ALL-UNNAMED\"\n                       \"--add-opens=java.base/java.time.chrono=ALL-UNNAMED\"\n                       \"--add-opens=java.base/java.time.format=ALL-UNNAMED\"\n                       \"--add-opens=java.base/java.time.temporal=ALL-UNNAMED\"\n                       \"--add-opens=java.base/java.time.zone=ALL-UNNAMED\"\n                       \"--add-opens=java.base/java.util=ALL-UNNAMED\"\n                       \"--add-opens=java.base/java.util.concurrent=ALL-UNNAMED\"\n                       \"--add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED\"\n                       \"--add-opens=java.base/java.util.concurrent.locks=ALL-UNNAMED\"\n                       \"--add-opens=java.base/java.util.function=ALL-UNNAMED\"\n                       \"--add-opens=java.base/java.util.jar=ALL-UNNAMED\"\n                       \"--add-opens=java.base/java.util.regex=ALL-UNNAMED\"\n                       \"--add-opens=java.base/java.util.spi=ALL-UNNAMED\"\n                       \"--add-opens=java.base/java.util.stream=ALL-UNNAMED\"\n                       \"--add-opens=java.base/java.util.zip=ALL-UNNAMED\"\n                       \"--add-opens=java.base/javax.crypto=ALL-UNNAMED\"\n                       \"--add-opens=java.base/javax.crypto.interfaces=ALL-UNNAMED\"\n                       \"--add-opens=java.base/javax.crypto.spec=ALL-UNNAMED\"\n                       \"--add-opens=java.base/javax.net=ALL-UNNAMED\"\n                       \"--add-opens=java.base/javax.net.ssl=ALL-UNNAMED\"\n                       \"--add-opens=java.base/javax.security.auth=ALL-UNNAMED\"\n                       \"--add-opens=java.base/javax.security.auth.callback=ALL-UNNAMED\"\n                       \"--add-opens=java.base/javax.security.auth.login=ALL-UNNAMED\"\n                       \"--add-opens=java.base/javax.security.auth.spi=ALL-UNNAMED\"\n                       \"--add-opens=java.base/javax.security.auth.x500=ALL-UNNAMED\"\n                       \"--add-opens=java.base/javax.security.cert=ALL-UNNAMED\"\n                       \"--add-opens=java.desktop/sun.java2d.marlin=ALL-UNNAMED\"\n                       \"--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED\"\n                       \"--add-opens=java.base/sun.nio.ch=ALL-UNNAMED\"\n                       \"--add-opens=jdk.management/com.sun.management.internal=ALL-UNNAMED\"]}\n   \u003c/code\u003e\u003c/pre\u003e\n\u003c/details\u003e\n\n## Memory usage tracing\n\n`clj-memory-meter.trace` provides a way to instrument functions so that they\nreport heap usage before and after the invocation, and also memory size of the\narguments and return values. This blog post describes why you would want to use\nit and how: [Tracking memory usage with\nclj-memory-meter.trace](https://clojure-goes-fast.com/blog/tracking-memory-usage/).\n\nHere's a short example:\n\n```clj\n(require '[clj-memory-meter.trace :as cmm.trace])\n\n(defn make-numbers [n]\n  (vec (repeatedly n #(rand-int 10000))))\n\n(defn avg [numbers]\n  (/ (reduce + numbers) (count numbers)))\n\n(defn distribution [m]\n  (-\u003e\u003e (range 1 m)\n       (mapv #(make-numbers (* 1000000 %)))\n       (mapv avg)))\n\n(cmm.trace/trace-var #'make-numbers)\n(cmm.trace/trace-var #'avg)\n(cmm.trace/trace-var #'distribution)\n\n(cmm.trace/with-relative-usage\n  (distribution 5))\n\n; Initial used heap: 63.2 MiB (1.5%)\n; │ (example/distribution \u003c24 B\u003e) | Heap: +472 B (+0.0%)\n; │ │ (example/make-numbers \u003c24 B\u003e) | Heap: +1.5 KiB (+0.0%)\n; │ │ └─→ \u003c20.2 MiB\u003e | Heap: +20.2 MiB (+0.5%)\n; │ │\n; │ │ (example/make-numbers \u003c24 B\u003e) | Heap: +20.2 MiB (+0.5%)\n; │ │ └─→ \u003c40.5 MiB\u003e | Heap: +60.7 MiB (+1.5%)\n; │ │\n; │ │ (example/make-numbers \u003c24 B\u003e) | Heap: +60.7 MiB (+1.5%)\n; │ │ └─→ \u003c60.7 MiB\u003e | Heap: +121.6 MiB (+3.0%)\n; │ │\n; │ │ (example/make-numbers \u003c24 B\u003e) | Heap: +121.6 MiB (+3.0%)\n; │ │ └─→ \u003c80.9 MiB\u003e | Heap: +202.7 MiB (+4.9%)\n; │ │\n; │ │ (example/avg \u003c20.2 MiB\u003e) | Heap: +230.7 MiB (+5.6%)\n; │ │ └─→ \u003c152 B\u003e | Heap: +202.7 MiB (+4.9%)\n; │ │\n; │ │ (example/avg \u003c40.5 MiB\u003e) | Heap: +229.7 MiB (+5.6%)\n; │ │ └─→ \u003c152 B\u003e | Heap: +202.7 MiB (+4.9%)\n; │ │\n; │ │ (example/avg \u003c60.7 MiB\u003e) | Heap: +297.4 MiB (+7.3%)\n; │ │ └─→ \u003c152 B\u003e | Heap: +202.7 MiB (+4.9%)\n; │ │\n; │ │ (example/avg \u003c80.9 MiB\u003e) | Heap: +295.0 MiB (+7.2%)\n; │ │ └─→ \u003c152 B\u003e | Heap: +202.7 MiB (+4.9%)\n; │ └─→ \u003c864 B\u003e | Heap: +23.3 KiB (+0.0%)\n; Final used heap: +23.2 KiB (+0.0%)\n```\n\nUse `trace-var` to instrument a function. You can undo the instrumenting by\ncalling `untrace-var` on it or redefining the function. Once instrumented, the\nfunction will print the current heap usage before and after its execution.\n\nBy default, the absolute heap usage numbers are printed. But if you wrap the\ntop-level call in `with-relative-usage` macro (like in the example above), each\nheap usage report will be relative to the **beginning** of execution (not\nrelative to the previous heap report). So, in the example, `Heap: +20.2 MiB` and\nthen `Heap: +60.7 MiB` means that the heap usage grew by 60 megabytes, not 80.\n\nIf the traced function gets called a lot, it will significantly slow down\nexecution. **Don't use this in production.** Also, you can disable the following\nfeatures to reduce the overhead:\n\n- `*calculate-argument-and-return-sizes*` — bind to `false` to skip measuring\n  traced function arguments and returned values;\n- `*force-gc-around-traced-functions*` — bind to `false` to avoid calling\n  `(System/gc)` at traced function boundaries. Note that this will make heap\n  usage reports not very accurate since they will also include collectable\n  garbage.\n\n## License\n\njamm is distributed under Apache-2.0.\nSee [APACHE_PUBLIC_LICENSE](license/APACHE_PUBLIC_LICENSE) file. The location of the original\nrepository\nis\n[https://github.com/jbellis/jamm](https://github.com/jbellis/jamm).\n\n---\n\nclj-memory-meter is distributed under the Eclipse Public License.\nSee [ECLIPSE_PUBLIC_LICENSE](license/ECLIPSE_PUBLIC_LICENSE).\n\nCopyright 2018-2025 Oleksandr Yakushev\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fclojure-goes-fast%2Fclj-memory-meter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fclojure-goes-fast%2Fclj-memory-meter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fclojure-goes-fast%2Fclj-memory-meter/lists"}