11/*
2- * Copyright (c) 2024, 2024 , Oracle and/or its affiliates. All rights reserved.
2+ * Copyright (c) 2024, 2026 , Oracle and/or its affiliates. All rights reserved.
33 * Copyright (c) 2024, 2024, Red Hat Inc. All rights reserved.
4+ * Copyright (c) 2026, 2026, IBM Inc. All rights reserved.
45 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
56 *
67 * This code is free software; you can redistribute it and/or modify it
3940import java .util .ArrayList ;
4041import java .util .Arrays ;
4142import java .util .List ;
43+ import java .util .concurrent .CountDownLatch ;
4244import java .util .concurrent .atomic .AtomicReference ;
45+ import java .util .concurrent .locks .LockSupport ;
4346
4447import org .graalvm .nativeimage .Platform ;
4548import org .junit .BeforeClass ;
4952
5053public class JCmdTest {
5154 @ BeforeClass
52- public static void checkForJFR () {
55+ public static void checkForJcmd () {
5356 assumeTrue ("skipping JCmd tests" , VMInspectionOptions .hasJCmdSupport ());
5457 }
5558
@@ -76,8 +79,7 @@ public void testBadSocketFile() throws IOException, InterruptedException {
7679 checkJCmdConnection ();
7780
7881 /* Delete the socket file. */
79- String tempDir = System .getProperty ("java.io.tmpdir" );
80- Path attachFile = Paths .get (tempDir , ".java_pid" + ProcessHandle .current ().pid ());
82+ Path attachFile = getTempFilePath (".java_pid" , "" );
8183 boolean deletedSocketFile = Files .deleteIfExists (attachFile );
8284 assertTrue (deletedSocketFile );
8385
@@ -128,6 +130,91 @@ public void testJfr() throws IOException, InterruptedException {
128130 assertOutputContainsLines (jcmd , "Stopped recording \" JCmdTest\" ." );
129131 }
130132
133+ @ Test
134+ public void testThreadPrint () throws IOException , InterruptedException {
135+ Process jcmd = runJCmd ("Thread.print" );
136+ assertOutputContainsStrings (jcmd , "Threads dumped." );
137+ }
138+
139+ @ Test
140+ public void testThreadDumpToTextFile () throws IOException , InterruptedException {
141+ BlockerHelper blocker = new BlockerHelper ();
142+ CountDownLatch parked = new CountDownLatch (1 );
143+
144+ Thread parkedThread = new Thread (() -> {
145+ parked .countDown ();
146+ LockSupport .park (blocker );
147+ }, "test-park-blocker-thread" );
148+
149+ parkedThread .start ();
150+
151+ /* Wait until parkedThread is parked. */
152+ parked .await ();
153+ while (!parkedThread .getState ().equals (Thread .State .WAITING )) {
154+ Thread .sleep (10 );
155+ }
156+
157+ Path textFile = getTempFilePath ("test_thread_dump" , ".txt" );
158+ Process jcmd = runJCmd ("Thread.dump_to_file" , textFile .toString ());
159+ assertOutputContainsStrings (jcmd , "Created" );
160+ assertTrue (Files .size (textFile ) > 0 );
161+ checkThreadDump (textFile );
162+ assertTrue (Files .deleteIfExists (textFile ));
163+
164+ /* Unpark the thread and wait until it terminates. */
165+ LockSupport .unpark (parkedThread );
166+ parkedThread .join ();
167+ }
168+
169+ private static void checkThreadDump (Path textFile ) throws IOException {
170+ String dump = Files .readString (textFile );
171+ assertTrue ("Dump should contain the blocked thread's name" , dump .contains ("test-park-blocker-thread" ));
172+ assertTrue ("Dump should contain the thread's state" , dump .contains ("WAITING" ));
173+ assertTrue ("Dump should contain text 'parking to wait for'" , dump .contains ("parking to wait for" ));
174+ assertTrue ("Dump should report the blocker object" , dump .contains (BlockerHelper .class .getSimpleName ()));
175+ assertTrue ("Dump should report the process ID" , dump .contains (String .valueOf (ProcessHandle .current ().pid ())));
176+ assertTrue ("Dump should report at least some stacktrace lines" , dump .contains (" at " ));
177+ }
178+
179+ @ Test
180+ public void testThreadDumpToJsonFile () throws IOException , InterruptedException {
181+ Path jsonFile = getTempFilePath ("test_thread_dump" , ".json" );
182+ Process jcmd = runJCmd ("Thread.dump_to_file" , "-format=json" , jsonFile .toString ());
183+ assertOutputContainsStrings (jcmd , "Created" );
184+ assertTrue (Files .size (jsonFile ) > 0 );
185+ assertTrue (Files .deleteIfExists (jsonFile ));
186+ }
187+
188+ @ Test
189+ public void testVM () throws IOException , InterruptedException {
190+ Process jcmd = runJCmd ("VM.command_line" );
191+ assertOutputContainsStrings (jcmd , "VM Arguments:" , "java_command:" );
192+
193+ jcmd = runJCmd ("VM.native_memory" );
194+ assertOutputContainsStrings (jcmd , "Native memory tracking" );
195+
196+ jcmd = runJCmd ("VM.system_properties" );
197+ assertOutputContainsStrings (jcmd , ":" );
198+
199+ jcmd = runJCmd ("VM.uptime" );
200+ assertOutputContainsStrings (jcmd , ":" );
201+
202+ jcmd = runJCmd ("VM.version" );
203+ assertOutputContainsStrings (jcmd , "GraalVM" );
204+ }
205+
206+ @ Test
207+ public void testGC () throws IOException , InterruptedException {
208+ Path dumpFile = getTempFilePath ("heap_dump" , ".hprof" );
209+ Process jcmd = runJCmd ("GC.heap_dump" , dumpFile .toString ());
210+ assertOutputContainsStrings (jcmd , "Dumped to:" );
211+ assertTrue (Files .size (dumpFile ) > 0 );
212+ assertTrue (Files .deleteIfExists (dumpFile ));
213+
214+ jcmd = runJCmd ("GC.run" );
215+ assertOutputContainsStrings (jcmd , "Command executed successfully" );
216+ }
217+
131218 private static void checkJCmdConnection () throws IOException , InterruptedException {
132219 Process jcmd = runJCmd ("help" );
133220 assertOutputContainsLines (jcmd , "help" );
@@ -175,4 +262,12 @@ private static String[] getOutput(Process process) throws InterruptedException {
175262 }
176263 return lines ;
177264 }
265+
266+ private static Path getTempFilePath (String prefix , String suffix ) {
267+ String tempDir = System .getProperty ("java.io.tmpdir" );
268+ return Paths .get (tempDir , prefix + ProcessHandle .current ().pid () + suffix );
269+ }
270+
271+ private static final class BlockerHelper {
272+ }
178273}
0 commit comments