diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/info/ProcessInfo.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/info/ProcessInfo.java index 85f46e0266..3d3d7aedda 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/info/ProcessInfo.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/info/ProcessInfo.java @@ -19,11 +19,13 @@ package org.springframework.boot.info; import java.lang.management.ManagementFactory; import java.lang.management.MemoryMXBean; import java.lang.management.MemoryUsage; +import java.lang.management.PlatformManagedObject; /** * Information about the process of the application. * * @author Jonatan Ivanov + * @author Andrey Litvitski * @since 3.3.0 */ public class ProcessInfo { @@ -72,6 +74,31 @@ public class ProcessInfo { return new MemoryInfo(); } + /** + * Virtual threads information for the process. These values provide details about the + * current state of virtual threads, including the number of mounted threads, queued + * threads, the parallelism level, and the thread pool size. + * @return an instance of {@link VirtualThreadsInfo} containing information about + * virtual threads, or {@code null} if the VirtualThreadSchedulerMXBean is not + * available. + * @since 3.5.0 + */ + @SuppressWarnings("unchecked") + public VirtualThreadsInfo getVirtualThreads() { + try { + Class mxBeanClass = (Class) Class + .forName("jdk.management.VirtualThreadSchedulerMXBean"); + Object bean = ManagementFactory.getPlatformMXBean(mxBeanClass); + return new VirtualThreadsInfo((Integer) mxBeanClass.getMethod("getMountedVirtualThreadCount").invoke(bean), + (Long) mxBeanClass.getMethod("getQueuedVirtualThreadCount").invoke(bean), + (Integer) mxBeanClass.getMethod("getParallelism").invoke(bean), + (Integer) mxBeanClass.getMethod("getPoolSize").invoke(bean)); + } + catch (ReflectiveOperationException ex) { + return null; + } + } + public long getPid() { return this.pid; } @@ -84,6 +111,46 @@ public class ProcessInfo { return this.owner; } + /** + * Virtual threads information. + * + * @since 3.5.0 + */ + public static class VirtualThreadsInfo { + + private final int mounted; + + private final long queued; + + private final int parallelism; + + private final int poolSize; + + public VirtualThreadsInfo(int mounted, long queued, int parallelism, int poolSize) { + this.mounted = mounted; + this.queued = queued; + this.parallelism = parallelism; + this.poolSize = poolSize; + } + + public long getMounted() { + return this.mounted; + } + + public long getQueued() { + return this.queued; + } + + public int getParallelism() { + return this.parallelism; + } + + public int getPoolSize() { + return this.poolSize; + } + + } + /** * Memory information. * diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/info/ProcessInfoTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/info/ProcessInfoTests.java index 37ba3b0173..fb2558aa75 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/info/ProcessInfoTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/info/ProcessInfoTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,8 +17,11 @@ package org.springframework.boot.info; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledForJreRange; +import org.junit.jupiter.api.condition.JRE; import org.springframework.boot.info.ProcessInfo.MemoryInfo.MemoryUsageInfo; +import org.springframework.boot.info.ProcessInfo.VirtualThreadsInfo; import static org.assertj.core.api.Assertions.assertThat; @@ -26,6 +29,7 @@ import static org.assertj.core.api.Assertions.assertThat; * Tests for {@link ProcessInfo}. * * @author Jonatan Ivanov + * @author Andrey Litvitski */ class ProcessInfoTests { @@ -54,4 +58,23 @@ class ProcessInfoTests { assertThat(nonHeapUsageInfo.getMax()).isEqualTo(-1); } + @Test + @EnabledForJreRange(min = JRE.JAVA_24) + void virtualThreadsInfoIfAvailable() { + ProcessInfo processInfo = new ProcessInfo(); + VirtualThreadsInfo virtualThreadsInfo = processInfo.getVirtualThreads(); + assertThat(virtualThreadsInfo).isNotNull(); + assertThat(virtualThreadsInfo.getMounted()).isGreaterThanOrEqualTo(0); + assertThat(virtualThreadsInfo.getQueued()).isGreaterThanOrEqualTo(0); + assertThat(virtualThreadsInfo.getParallelism()).isGreaterThan(0); + assertThat(virtualThreadsInfo.getPoolSize()).isGreaterThanOrEqualTo(0); + } + + @Test + @EnabledForJreRange(max = JRE.JAVA_23) + void virtualThreadsInfoIfNotAvailable() { + ProcessInfo processInfo = new ProcessInfo(); + assertThat(processInfo.getVirtualThreads()).isNull(); + } + }