Skip to content

Commit 4647809

Browse files
### 0.6.0 (June 14, 2026)
* On a failed leak check, JMemoryBuddy now reads back the heap dump it creates and prints the strong-reference path from a GC root to each not-collected object — no need to open VisualVM/MAT for the common case. Dependency-free; disable with `-Djmemorybuddy.printPath=false`. Also added new release process.
1 parent 34f4b58 commit 4647809

13 files changed

Lines changed: 670 additions & 13 deletions

File tree

.github/workflows/main.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ jobs:
1212

1313
steps:
1414
- uses: actions/checkout@v4
15+
with:
16+
# full history + tags, required for the git-derived version
17+
fetch-depth: 0
1518
- uses: actions/setup-java@v4
1619
with:
1720
distribution: 'temurin'
@@ -22,3 +25,13 @@ jobs:
2225

2326
- name: Run tests
2427
run: ./gradlew test
28+
29+
- name: Publish snapshot to Sandec Artifactory
30+
if: matrix.jdk == 21 && github.ref == 'refs/heads/master' && github.event_name == 'push'
31+
run: ./publishSandecArtifactory.sh --snapshot-only
32+
env:
33+
SANDEC_ARTIFACTORY_USERNAME: ${{secrets.SANDEC_ARTIFACTORY_USERNAME}}
34+
SANDEC_ARTIFACTORY_PASSWORD: ${{secrets.SANDEC_ARTIFACTORY_PASSWORD}}
35+
SANDEC_SIGNING_KEY_ID: ${{secrets.SANDEC_SIGNING_KEY_ID}}
36+
SANDEC_SIGNING_SECRET_KEY: ${{secrets.SANDEC_SIGNING_SECRET_KEY}}
37+
SANDEC_SIGNING_PASSWORD: ${{secrets.SANDEC_SIGNING_PASSWORD}}

.github/workflows/release.yml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
tags:
6+
- '[0-9]*'
7+
workflow_dispatch:
8+
9+
env:
10+
SANDEC_ARTIFACTORY_USERNAME: ${{secrets.SANDEC_ARTIFACTORY_USERNAME}}
11+
SANDEC_ARTIFACTORY_PASSWORD: ${{secrets.SANDEC_ARTIFACTORY_PASSWORD}}
12+
SANDEC_SIGNING_KEY_ID: ${{secrets.SANDEC_SIGNING_KEY_ID}}
13+
SANDEC_SIGNING_SECRET_KEY: ${{secrets.SANDEC_SIGNING_SECRET_KEY}}
14+
SANDEC_SIGNING_PASSWORD: ${{secrets.SANDEC_SIGNING_PASSWORD}}
15+
SANDEC_SONATYPE_AUTH_TOKEN: ${{secrets.SANDEC_SONATYPE_AUTH_TOKEN}}
16+
17+
jobs:
18+
release:
19+
name: 'Release'
20+
runs-on: ubuntu-latest
21+
if: github.repository_owner == 'Sandec'
22+
steps:
23+
- name: Checkout
24+
uses: actions/checkout@v4
25+
with:
26+
# full history + tags, required for the git-derived version
27+
fetch-depth: 0
28+
29+
- name: Setup Java 21
30+
uses: actions/setup-java@v4
31+
with:
32+
distribution: 'temurin'
33+
java-version: 21
34+
35+
- name: Print release version
36+
run: ./gradlew help -q | grep "Version derived"
37+
38+
- name: Publish to Sandec Artifactory
39+
run: ./publishSandecArtifactory.sh
40+
41+
- name: Publish to Maven Central
42+
run: ./publishMavenCentral.sh

CHANGELOG.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
### 0.5.7 (TBD)
2-
1+
### 0.6.0 (June 14, 2026)
2+
* On a failed leak check, JMemoryBuddy now reads back the heap dump it creates and prints the strong-reference path from a GC root to each not-collected object — no need to open VisualVM/MAT for the common case. Dependency-free; disable with `-Djmemorybuddy.printPath=false`.
33

44
### 0.5.6 (October 29, 2024)
55
* Changed module name to `one.jpro.jmemorybuddy`.

README.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,17 @@ You can also use the method `assertCollectable` and `assertNotCollectable` to ch
6060

6161

6262
#### Analyzing the heap dump:
63-
JMemoryBuddy makes it easy to analyze the heap dump because all problematic instances are wrapped inside a class with the name `AssertCollectable`. Just search your heap dump with your prefered tool for this class name:
63+
On a failed leak check JMemoryBuddy reads back the heap dump and prints the strong-reference path from a GC root to each not-collected object directly in the test output — usually enough to find the cause without opening any tool:
64+
65+
```
66+
JMemoryBuddy — path to GC root for leak: PageDoc @58a903060:
67+
GC root [Java frame] DataRepository
68+
↓ DataRepository.listeners
69+
...
70+
PageDoc ← leaked
71+
```
72+
73+
Disable with `-Djmemorybuddy.printPath=false`. For a deeper look, open the dump in your preferred tool — all watched instances are referenced by a `TrackedWeakReference`, so you can search for that class name:
6474
![visualvm](/screenshot-visualvm.png)
6575

6676

@@ -74,6 +84,8 @@ You can configure VisualVM with SystemProperties:
7484
| Tables | Effect | Default |
7585
| ------------- |:-------------:| -----:|
7686
| -Djmemorybuddy.createHeapDump | Should a heap dump created on failure? | true |
87+
| -Djmemorybuddy.printPath | Print the path from a GC root to each not-collected object on failure. | true |
88+
| -Djmemorybuddy.maxObjects | Skip the path analysis when the dump has more objects than this (avoids heavy in-process analysis). | 8000000 |
7789
| -Djmemorybuddy.output | The folder where the heap dump gets saved. | if target exists, then "target" otherwise "build" |
7890

7991
The following values usually shouldn't be changed but might be useful to make tests more stable or reduce the time required.

RELEASING.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Releasing JMemoryBuddy
2+
3+
The version is derived from the latest reachable git tag (`X.Y.Z`, no prefix):
4+
5+
- HEAD exactly on tag `X.Y.Z`, clean tree → version `X.Y.Z`
6+
- commits after the tag, or a dirty tree → version `X.Y.(Z+1)-SNAPSHOT`
7+
- no tag → `0.5.7-SNAPSHOT` (fallback)
8+
9+
The derived version is printed at the start of every build.
10+
11+
## Releasing a version
12+
13+
```bash
14+
./tagRelease.sh jmemorybuddy X.Y.Z
15+
```
16+
17+
The first argument is the project name, guarding against running this in the wrong repo.
18+
The script only tags: it verifies a clean tree on an up-to-date `master` and a dated
19+
`### X.Y.Z` CHANGELOG entry, then tags and pushes `X.Y.Z`. The tag push triggers
20+
`.github/workflows/release.yml`, which builds and publishes via the scripts:
21+
22+
| Script | Registry | Versions |
23+
|---|---|---|
24+
| `publishSandecArtifactory.sh` | Sandec Artifactory | snapshots (every `master` push, via CI) and releases |
25+
| `publishMavenCentral.sh` | Maven Central (Sonatype Central Portal) | releases only (refuses snapshots) |
26+
27+
Maven Central publishing is `AUTOMATIC` (see `build.gradle`): the tag-triggered deployment
28+
is uploaded and released to Maven Central without a manual portal step — the tag is the gate,
29+
so don't tag until the CHANGELOG and version are right (releases can't be unpublished). There
30+
is no version bump and no bump-back — after the release, builds become `X.Y.(Z+1)-SNAPSHOT`.
31+
32+
## Required repository secrets
33+
34+
- `SANDEC_ARTIFACTORY_USERNAME` / `SANDEC_ARTIFACTORY_PASSWORD`
35+
- `SANDEC_SIGNING_KEY_ID` / `SANDEC_SIGNING_SECRET_KEY` / `SANDEC_SIGNING_PASSWORD` — GPG
36+
- `SANDEC_SONATYPE_AUTH_TOKEN` — Sonatype Central Portal token
37+
38+
## Note on the first tag-derived release
39+
40+
The latest existing tag is `0.5.5`, while `0.5.6` was published manually (untagged) — so until
41+
a new tag exists, builds derive `0.5.6-SNAPSHOT`. The next release should be `0.5.7`
42+
(`./tagRelease.sh jmemorybuddy 0.5.7`), which realigns the tags with the published versions.

build.gradle

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,23 @@ plugins {
66
}
77

88
group = 'one.jpro'
9-
version = "$JMEMORYBUDDY_VERSION"
9+
10+
// The version is derived from the latest reachable git tag (X.Y.Z, no prefix):
11+
// - HEAD exactly on the tag, clean tree -> X.Y.Z (release build)
12+
// - commits after the tag, or dirty tree -> X.Y.(Z+1)-SNAPSHOT
13+
// - no tag / no git -> 0.5.7-SNAPSHOT
14+
def gitDescribe = providers.exec {
15+
commandLine 'git', 'describe', '--tags', '--dirty', '--match', '[0-9]*'
16+
ignoreExitValue = true
17+
}.standardOutput.asText.map { it.trim() }
18+
version = {
19+
def matcher = gitDescribe.getOrElse('') =~ /^(\d+)\.(\d+)\.(\d+)(-\d+-g[0-9a-f]+)?(-dirty)?$/
20+
if (!matcher.matches()) return '0.5.7-SNAPSHOT'
21+
def (major, minor, patch) = [matcher.group(1), matcher.group(2), matcher.group(3).toInteger()]
22+
def isRelease = matcher.group(4) == null && matcher.group(5) == null
23+
isRelease ? "${major}.${minor}.${patch}" : "${major}.${minor}.${patch + 1}-SNAPSHOT"
24+
}()
25+
logger.quiet("Version derived from git tags: {} (git describe: {})", version, gitDescribe.getOrElse('') ?: '<no tag found>')
1026

1127
java {
1228
sourceCompatibility = 11
@@ -108,5 +124,5 @@ signing {
108124
mavenCentral {
109125
repoDir = layout.buildDirectory.dir('staging-deploy')
110126
authToken = System.getenv("SANDEC_SONATYPE_AUTH_TOKEN")
111-
publishingType = 'USER_MANAGED'
127+
publishingType = 'AUTOMATIC'
112128
}

gradle.properties

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
JMEMORYBUDDY_VERSION=0.5.7-SNAPSHOT
2-
31
MAVEN_CENTRAL_PUBLISH_PLUGIN_VERSION=1.2.0
42
JUNIT_VERSION=5.11.3
53
LOGBACK_VERSION=1.5.12

publishMavenCentral.sh

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/bin/bash
2+
# Assembles the signed bundle and uploads it to Maven Central (Sonatype Central Portal).
3+
set -e
4+
cd "$(dirname "$0")"
5+
6+
VERSION=$(./gradlew help -q 2>/dev/null | grep "Version derived" | sed -E 's/.*git tags: ([^ ]+).*/\1/')
7+
echo "Publishing version $VERSION to Maven Central"
8+
9+
if [[ "$VERSION" == *-SNAPSHOT ]]; then
10+
echo "error: $VERSION is a SNAPSHOT — Maven Central only takes releases (tag with ./tagRelease.sh)"
11+
exit 1
12+
fi
13+
14+
./gradlew "publishAllPublicationsToStaging-deployRepository" publishToMavenCentralPortal

publishSandecArtifactory.sh

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#!/bin/bash
2+
# Publishes to the Sandec Artifactory.
3+
# With --snapshot-only, refuses release versions (used by the per-commit CI job;
4+
# releases reach Artifactory through the tag-triggered release workflow).
5+
set -e
6+
cd "$(dirname "$0")"
7+
8+
if [ -z "$SANDEC_ARTIFACTORY_USERNAME" ]; then
9+
echo "error: SANDEC_ARTIFACTORY_USERNAME/PASSWORD not set"
10+
exit 1
11+
fi
12+
13+
VERSION=$(./gradlew help -q 2>/dev/null | grep "Version derived" | sed -E 's/.*git tags: ([^ ]+).*/\1/')
14+
echo "Publishing version $VERSION to Sandec Artifactory"
15+
16+
if [ "$1" = "--snapshot-only" ] && [[ "$VERSION" != *-SNAPSHOT ]]; then
17+
echo "Version $VERSION is a release — skipping (the release workflow publishes releases)."
18+
exit 0
19+
fi
20+
21+
./gradlew publishAllPublicationsToArtifactoryRepository

0 commit comments

Comments
 (0)