<# .SYNOPSIS Compiles and runs the whole ZGC-vs-G1 suite, writing every raw output into results/. .DESCRIPTION Every number published in the companion blog post comes from this script. It is deliberately boring and sequential: GC benchmarks that run concurrently with each other measure the scheduler. .PARAMETER JavaHome Path to a JDK 25 (or newer) installation. Generational ZGC is the only ZGC from JDK 24 onward. .EXAMPLE pwsh scripts/run-all.ps1 -JavaHome 'C:\Program Files\Java\jdk-25.0.3' #> param( [string]$JavaHome = $env:JAVA_HOME, [string]$MavenHome = "$HOME\ankurm-blog-tools\apache-maven-3.9.9", [int]$MeasureSeconds = 60, [int]$WarmupSeconds = 20, [switch]$SkipJmh ) $ErrorActionPreference = 'Stop' if (-not $JavaHome) { throw "Set -JavaHome or JAVA_HOME to a JDK 25+ installation." } $Root = Split-Path -Parent $PSScriptRoot $Java = Join-Path $JavaHome 'bin\java.exe' $Javac = Join-Path $JavaHome 'bin\javac.exe' $Out = Join-Path $Root 'out' $Results = Join-Path $Root 'results' $Logs = Join-Path $Root 'logs' New-Item -ItemType Directory -Force -Path $Out, $Results, $Logs | Out-Null # Fixed heap for every run. -Xms == -Xmx removes heap resizing as a variable; a benchmark that lets # the heap grow during measurement is partly measuring the resize policy. $Heap = @('-Xms2g', '-Xmx2g') Write-Host "==> compiling" -ForegroundColor Cyan $sources = Get-ChildItem "$Root\latency\src", "$Root\tuning\src", "$Root\internals\src", "$Root\analysis\src" ` -Recurse -Filter *.java -ErrorAction SilentlyContinue | ForEach-Object { $_.FullName } & $Javac -d $Out @sources if ($LASTEXITCODE -ne 0) { throw "compilation failed" } # NOTE: several of these commands (notably `java -version` and `-XX:+PrintFlagsFinal`) write to # stderr. PowerShell turns native stderr into error records, and with $ErrorActionPreference='Stop' # that aborts the whole script on a perfectly successful run. Routing through cmd.exe with a plain # 2>&1 merge keeps stderr as text where it belongs. function Invoke-Capture { param([string]$Name, [string[]]$JvmArgs, [string[]]$AppArgs) $file = Join-Path $Results "$Name.txt" Write-Host "==> $Name" -ForegroundColor Cyan $quoted = @("`"$Java`"") + ($JvmArgs + $AppArgs | ForEach-Object { if ($_ -match '[\s]') { "`"$_`"" } else { $_ } }) $line = ($quoted -join ' ') $output = cmd /c "$line 2>&1" $output | Out-File -FilePath $file -Encoding utf8 $output | Select-Object -Last 6 | ForEach-Object { Write-Host " $_" -ForegroundColor DarkGray } Write-Host " -> results/$Name.txt" -ForegroundColor DarkGray } # --------------------------------------------------------------------------------------------- # 1. Environment -- printed once per collector so the results file is self-describing. # --------------------------------------------------------------------------------------------- Invoke-Capture -Name '01-env-zgc' -JvmArgs (@('-XX:+UseZGC') + $Heap) -AppArgs @("$Root\env\Env.java") Invoke-Capture -Name '01-env-g1' -JvmArgs (@('-XX:+UseG1GC') + $Heap) -AppArgs @("$Root\env\Env.java") Invoke-Capture -Name '02-flags-zgc' -JvmArgs (@('-XX:+UseZGC') + $Heap + @('-XX:+PrintFlagsFinal')) -AppArgs @('-version') Invoke-Capture -Name '02-flags-g1' -JvmArgs (@('-XX:+UseG1GC') + $Heap + @('-XX:+PrintFlagsFinal')) -AppArgs @('-version') # --------------------------------------------------------------------------------------------- # 2. Open-loop latency, with full unified GC logging written alongside. # --------------------------------------------------------------------------------------------- $latencyProps = @("-DwarmupSeconds=$WarmupSeconds", "-Dseconds=$MeasureSeconds") Invoke-Capture -Name '03-latency-zgc' ` -JvmArgs (@('-XX:+UseZGC') + $Heap + $latencyProps + @("-Xlog:gc*,gc+heap=debug:file=$Logs\zgc-latency.log:time,uptime,level,tags")) ` -AppArgs @('-cp', $Out, 'com.ankurm.zgc.latency.LatencyHarness') Invoke-Capture -Name '03-latency-g1' ` -JvmArgs (@('-XX:+UseG1GC') + $Heap + $latencyProps + @("-Xlog:gc*,gc+heap=debug:file=$Logs\g1-latency.log:time,uptime,level,tags")) ` -AppArgs @('-cp', $Out, 'com.ankurm.zgc.latency.LatencyHarness') # --------------------------------------------------------------------------------------------- # 3. Allocation stalls -- deliberately undersized heap. This is the section most GC posts skip. # --------------------------------------------------------------------------------------------- $stallHeap = @('-Xms512m', '-Xmx512m') Invoke-Capture -Name '04-stalls-zgc-baseline' ` -JvmArgs (@('-XX:+UseZGC') + $stallHeap + @('-Dseconds=20')) ` -AppArgs @('-cp', $Out, 'com.ankurm.zgc.tuning.AllocationStallDemo') Invoke-Capture -Name '04-stalls-zgc-softmax' ` -JvmArgs (@('-XX:+UseZGC', '-XX:SoftMaxHeapSize=400m') + $stallHeap + @('-Dseconds=20')) ` -AppArgs @('-cp', $Out, 'com.ankurm.zgc.tuning.AllocationStallDemo') Invoke-Capture -Name '04-stalls-zgc-spiketolerance' ` -JvmArgs (@('-XX:+UseZGC', '-XX:ZAllocationSpikeTolerance=5') + $stallHeap + @('-Dseconds=20')) ` -AppArgs @('-cp', $Out, 'com.ankurm.zgc.tuning.AllocationStallDemo') # The same stress under G1: ZGC's stalls become G1's evacuation failures and Full GCs. Invoke-Capture -Name '04-stalls-g1' ` -JvmArgs (@('-XX:+UseG1GC') + $stallHeap + @('-Dseconds=20', "-Xlog:gc:file=$Logs\g1-stress.log:time,uptime")) ` -AppArgs @('-cp', $Out, 'com.ankurm.zgc.tuning.AllocationStallDemo') # --------------------------------------------------------------------------------------------- # 4. Page-size / humongous behaviour. # --------------------------------------------------------------------------------------------- Invoke-Capture -Name '05-pages-zgc' ` -JvmArgs (@('-XX:+UseZGC') + $Heap) -AppArgs @('-cp', $Out, 'com.ankurm.zgc.internals.PageSizeDemo') Invoke-Capture -Name '05-pages-g1' ` -JvmArgs (@('-XX:+UseG1GC') + $Heap) -AppArgs @('-cp', $Out, 'com.ankurm.zgc.internals.PageSizeDemo') # --------------------------------------------------------------------------------------------- # 5. GC log analysis -- turns the raw unified logs above into pause distributions. # --------------------------------------------------------------------------------------------- Invoke-Capture -Name '06-gclog-zgc' -JvmArgs @() -AppArgs @('-cp', $Out, 'com.ankurm.zgc.analysis.GcLogParser', "$Logs\zgc-latency.log") Invoke-Capture -Name '06-gclog-g1' -JvmArgs @() -AppArgs @('-cp', $Out, 'com.ankurm.zgc.analysis.GcLogParser', "$Logs\g1-latency.log") # --------------------------------------------------------------------------------------------- # 6. JMH throughput / barrier microbenchmarks. # --------------------------------------------------------------------------------------------- if (-not $SkipJmh) { Write-Host "==> building JMH module" -ForegroundColor Cyan Push-Location "$Root\jmh" $env:JAVA_HOME = $JavaHome & (Join-Path $MavenHome 'bin\mvn.cmd') -q -B clean package Pop-Location foreach ($gc in @('UseZGC', 'UseG1GC')) { $tag = if ($gc -eq 'UseZGC') { 'zgc' } else { 'g1' } # IMPORTANT: the GC flag must go in -jvmArgs. JMH forks a fresh JVM for every trial, and a # GC flag on the outer `java -jar` command is NOT inherited by the fork. Getting this wrong # is the single most common way to publish a GC benchmark that measured the default GC twice. Invoke-Capture -Name "07-jmh-$tag" -JvmArgs @('-jar', "$Root\jmh\target\benchmarks.jar") ` -AppArgs @('-jvmArgs', "-XX:+$gc -Xms2g -Xmx2g", '-prof', 'gc', '-rf', 'json', '-rff', "$Results\07-jmh-$tag.json") } } Write-Host "" Write-Host "All raw output is in $Results" -ForegroundColor Green