ããã¯ããªã«ãããããŠæžãããã®ïŒ
åã«ãVirtual Threadsã«é¢ãããšã³ããªãŒãæžããŸããã
Java 21で正式版になったJEP 444(Virtual Threads)に関するAPIを試す - CLOVER🍀
Virtual Threadsを使ってHTTPサーバー/クライアントを書いて、スレッドまわりの動きを確認してみる(スレッドダンプの取得付き) - CLOVER🍀
ãã®äžã§ãpinningïŒãã³çãïŒã«ã€ããŠã®ç¢ºèªãå°ãå¿ããŠããã®ã§ç¶ããããããšæããŸãã
Virtual Threadsã®pinningïŒãã³çãïŒ
JEP 444ïŒVirtual ThreadsïŒã«ã€ããŠã¯ãã¡ãã
Virtual Threadsã«ã€ããŠã¯ããããããŠã¯èª¬æããŸããã
ãã¡ãã®ãšã³ããªãŒãåç §ããšããããšã§ã
Java 21で正式版になったJEP 444(Virtual Threads)に関するAPIを試す - CLOVER🍀
pinningïŒãã³çãïŒã¯ããã©ãããã©ãŒã ã¹ã¬ããããä»®æ³ã¹ã¬ãããã¢ã³ããŠã³ãã§ããªãç¶æ
ã®ããšãèšããŸãã
以äžã®ã±ãŒã¹ãããããã§ãã
- When it executes code inside a synchronized block or methodïŒ
synchronized
ãããã¯ãsynchronized
ã¡ãœããå ã®ã³ãŒããå®è¡ããŠããæïŒ - When it executes a native method or a foreign functionïŒãã€ãã£ãã¡ãœãããJEP 424ã®Foreign Functionãå®è¡ããŠããæïŒ
pinningïŒãã³çãïŒãçºçããŠãã¢ããªã±ãŒã·ã§ã³ããããããªãããšã¯ãããŸããããã¹ã±ãŒã©ããªãã£ã代åã«ãªãå¯èœæ§ããããŸããã æ°ã«éãã®ãããã©ãããã©ãŒã ã¹ã¬ãããã²ãšã€ã®ä»®æ³ã¹ã¬ããã«çžãããŠãä»ã®ä»®æ³ã¹ã¬ãããåãããæ°ãæžã£ãŠããŸãããã§ããã
ãããåé¿ããã«ã¯ãsynchronized
ãããã¯ããã³synchronized
ã¡ãœããã䜿ããªããŠæžãããã«ããããšã¯ãã¡ããã§ããã
ããã¯ãå¿
èŠãªå Žåã¯ReentrantLock
ã䜿ãããã«ä¿®æ£ããããšã§ãã
ãããŠãsynchronized
ãããã¯ã§ã¢ã³ããŠã³ãã§ããªããªãããšã¯ä»¥äžã®ãšã³ããªãŒã§ç¢ºèªããŠããŸãã
Virtual Threadsを使ってHTTPサーバー/クライアントを書いて、スレッドまわりの動きを確認してみる(スレッドダンプの取得付き) - CLOVER🍀
ReentrantLock
ã®å©çšã§ããã°åé¡ãªãããšã確èªããŠããŸãã
pinningïŒãã³çãïŒãçºçããããšã¯ã以äžã®æ¹æ³ã§èšé²ã§ããããã§ãã
- A JDK Flight Recorder (JFR) event is emitted when a thread blocks while pinned (see JDK Flight Recorder).
- The system property jdk.tracePinnedThreads triggers a stack trace when a thread blocks while pinned. Running with -Djdk.tracePinnedThreads=full prints a complete stack trace when a thread blocks while pinned, highlighting native frames and frames holding monitors. Running with -Djdk.tracePinnedThreads=short limits the output to just the problematic frames.
ä»åã¯ãjdk.tracePinnedThreads
ã·ã¹ãã ããããã£ãè©ŠããŠã¿ãããšæããŸããããã¯ãpinningïŒãã³çãïŒãçºçããæã«
ã¹ã¿ãã¯ãã¬ãŒã¹ãåºåãããã®ã§ãã
æå®æ¹æ³ã«ã¯2çš®é¡ããããã§ãã
-Djdk.tracePinnedThreads=full
⊠pinningïŒãã³çãïŒãçºçããæã«ãå®å šãªã¹ã¿ãã¯ãã¬ãŒã¹ãšãã€ãã£ããã¬ãŒã ããã³ã¢ãã¿ãŒãä¿æããŠãããã¬ãŒã ã匷調衚瀺ããã-Djdk.tracePinnedThreads=short
⊠åé¡ã®ãããã¬ãŒã ã®ã¿ãåºåããã
ãã¡ãã®ããã¥ã¡ã³ãã«ãèšèŒããããŸãã
åäœç¢ºèªã«ã¯ããã¡ãã§æžããHTTPãµãŒããŒã䜿ãããšã«ããŸãã
Virtual Threadsを使ってHTTPサーバー/クライアントを書いて、スレッドまわりの動きを確認してみる(スレッドダンプの取得付き) - CLOVER🍀
ã§ã¯ãè©ŠããŠã¿ãŸãããã
ç°å¢
ä»åã®ç°å¢ã¯ãã¡ãã
$ java --version openjdk 21.0.2 2024-01-16 OpenJDK Runtime Environment (build 21.0.2+13-Ubuntu-122.04.1) OpenJDK 64-Bit Server VM (build 21.0.2+13-Ubuntu-122.04.1, mixed mode, sharing) $ mvn --version Apache Maven 3.9.6 (bc0240f3c744dd6b6ec2920b3cd08dcc295161ae) Maven home: $HOME/.sdkman/candidates/maven/current Java version: 21.0.2, vendor: Private Build, runtime: /usr/lib/jvm/java-21-openjdk-amd64 Default locale: ja_JP, platform encoding: UTF-8 OS name: "linux", version: "5.15.0-101-generic", arch: "amd64", family: "unix"
æºå
Mavenã®èšå®ã§ãããç¹ã«äŸåé¢ä¿ãªã©ã¯äœ¿ããŸããã
<properties> <maven.compiler.release>21</maven.compiler.release> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> </properties>
ãœãŒã¹ã³ãŒãã¯ãã¡ãã
src/main/java/org/littlewings/virtualthreads/SimpleHttpServer.java
package org.littlewings.virtualthreads; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; import java.io.IOException; import java.io.OutputStream; import java.io.UncheckedIOException; import java.net.InetSocketAddress; import java.net.URI; import java.nio.charset.StandardCharsets; import java.time.Duration; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; import java.util.function.Consumer; public class SimpleHttpServer { private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss"); private HttpServer httpServer; SimpleHttpServer(HttpServer httpServer) { this.httpServer = httpServer; } public static void main(String... args) { String host; int port; if (args.length > 1) { host = args[0]; port = Integer.parseInt(args[1]); } else if (args.length > 0) { host = "localhost"; port = Integer.parseInt(args[0]); } else { host = "localhost"; port = 8080; } SimpleHttpServer simpleHttpServer = SimpleHttpServer.create(host, port); simpleHttpServer.start(); Runtime.getRuntime().addShutdownHook(Thread.ofPlatform().unstarted(simpleHttpServer::stop)); } private static void log(String message) { Thread currentThread = Thread.currentThread(); String threadName = currentThread.getName(); System.out.printf("[%s] - %s - %s%n", LocalDateTime.now().format(FORMATTER), threadName, message); } public static SimpleHttpServer create(String host, int port) { try { HttpServer httpServer = HttpServer.create(new InetSocketAddress(host, port), 0); log(String.format("jdk.virtualThreadScheduler.parallelism = %s", System.getProperty("jdk.virtualThreadScheduler.parallelism", ""))); log(String.format("jdk.virtualThreadScheduler.maxPoolSize = %s", System.getProperty("jdk.virtualThreadScheduler.maxPoolSize", ""))); log(String.format("jdk.tracePinnedThreads = %s", System.getProperty("jdk.tracePinnedThreads", ""))); // httpServer.setExecutor(Executors.newVirtualThreadPerTaskExecutor()); httpServer.setExecutor(Executors.newThreadPerTaskExecutor(Thread.ofVirtual().name("handler-", 1).factory())); httpServer.createContext("/", createHandler()); return new SimpleHttpServer(httpServer); } catch (IOException e) { throw new UncheckedIOException(e); } } public static SimpleHttpServer create(int port) { return create("localhost", port); } static HttpHandler createHandler() { ReentrantLock lock = new ReentrantLock(); ReentrantLock lock2 = new ReentrantLock(); Object synchronizedLockObject = new Object(); Object synchronizedLockObject2 = new Object(); return httpExchange -> { URI requestUri = httpExchange.getRequestURI(); String method = httpExchange.getRequestMethod(); String requestPath = requestUri.getPath(); log(String.format("access[%s:%s] start", method, requestPath)); Consumer<String> writeResponse = responseString -> { byte[] binary = responseString.getBytes(StandardCharsets.UTF_8); try { httpExchange.sendResponseHeaders(200, binary.length); try (OutputStream os = httpExchange.getResponseBody()) { os.write(binary); } } catch (IOException e) { throw new UncheckedIOException(e); } }; Duration sleepTime = Duration.ofSeconds(3L); switch (requestPath) { case "/sleep" -> { try { TimeUnit.SECONDS.sleep(sleepTime.toSeconds()); } catch (InterruptedException e) { // ignore } writeResponse.accept("sleep."); } case "/heavy" -> { long startTime = System.currentTimeMillis(); while (true) { for (int i = 0; i < 100000; i++) { // loop } long elapsedTime = System.currentTimeMillis() - startTime; if (elapsedTime > sleepTime.toMillis()) { break; } } writeResponse.accept("heavy."); } case "/lock" -> { try { lock.lock(); TimeUnit.SECONDS.sleep(sleepTime.toSeconds()); } catch (InterruptedException e) { // ignore } finally { lock.unlock(); } writeResponse.accept("lock."); } case "/lock2" -> { try { lock2.lock(); TimeUnit.SECONDS.sleep(sleepTime.toSeconds()); } catch (InterruptedException e) { // ignore } finally { lock2.unlock(); } writeResponse.accept("lock2."); } case "/synchronized-lock" -> { synchronized (synchronizedLockObject) { try { TimeUnit.SECONDS.sleep(sleepTime.toSeconds()); } catch (InterruptedException e) { // ignore } writeResponse.accept("synchronized lock."); } } case "/synchronized-lock2" -> { synchronized (synchronizedLockObject2) { try { TimeUnit.SECONDS.sleep(sleepTime.toSeconds()); } catch (InterruptedException e) { // ignore } writeResponse.accept("synchronized lock2."); } } default -> writeResponse.accept("Hello World."); } log(String.format("access[%s:%s] end", method, requestPath)); }; } public void start() { httpServer.start(); log(String.format("simple http server[%s:%d], started.", httpServer.getAddress().getHostString(), httpServer.getAddress().getPort())); } public void stop() { httpServer.stop(1); log(String.format("simple http server[%s:%d], shutdown.", httpServer.getAddress().getHostString(), httpServer.getAddress().getPort())); } }
åºæ¬çã«ãããšã®ãšã³ããªãŒãšå
容ã¯ã»ãŒå€ããŠããŸãããJDKã®HttpServer
ã䜿ã£ãŠãVirtual Threadsã䜿ã£ãç°¡åãªHTTPãµãŒããŒã
æžããŠããŸãã
public static SimpleHttpServer create(String host, int port) { try { HttpServer httpServer = HttpServer.create(new InetSocketAddress(host, port), 0); log(String.format("jdk.virtualThreadScheduler.parallelism = %s", System.getProperty("jdk.virtualThreadScheduler.parallelism", ""))); log(String.format("jdk.virtualThreadScheduler.maxPoolSize = %s", System.getProperty("jdk.virtualThreadScheduler.maxPoolSize", ""))); log(String.format("jdk.tracePinnedThreads = %s", System.getProperty("jdk.tracePinnedThreads", ""))); // httpServer.setExecutor(Executors.newVirtualThreadPerTaskExecutor()); httpServer.setExecutor(Executors.newThreadPerTaskExecutor(Thread.ofVirtual().name("handler-", 1).factory())); httpServer.createContext("/", createHandler()); return new SimpleHttpServer(httpServer); } catch (IOException e) { throw new UncheckedIOException(e); } }
ã¢ã¯ã»ã¹ãã¹ã«å¿ããŠã以äžã®5çš®é¡ã®åŠçãè¡ããŸãã
/sleep
⊠æå®ããç§æ°ã ãTimeUnit#sleep
ã§ã¹ãªãŒãïŒãããã¯æäœã®ä»£ããïŒ/heavy
⊠æå®ããç§æ°ã ãã«ãŒãïŒCPUãæ¶è²»ããåŠçïŒ/lock
ã/lock2
⊠ããããç°ãªãReentrantLock
ã®ã€ã³ã¹ã¿ã³ã¹ã䜿ã£ãŠããã¯ãååŸããæå®ããç§æ°ã ãTimeUnit#sleep
ã§ã¹ãªãŒã/synchronized-lock
ã/synchronized-lock2
⊠ããããç°ãªãã€ã³ã¹ã¿ã³ã¹ã«å¯ŸããŠsynchronized
ã§ããã¯ãååŸããæå®ããç§æ°ã ãTimeUnit#sleepã§ã¹ãªãŒã- ãã以å€ã®ã㹠⊠å³åº§ã«
Hello World.
ãè¿ã
ã¢ã¯ã»ã¹ãã¹ããšã®åŠçã¯ãcase
ã«ãªã£ãŠããŸãã
ãã®äžã§pinningïŒãã³çãïŒãçºçããããªã®ã¯ã/synchronized-lock
ã/synchronized-lock2
ããªãšæããŸãã/heavy
ãä»®æ³ã¹ã¬ããã
æçžããç¶ããŸãããã©ããªããã§ããããïŒ
ã§ã¯ãåãããŠã¿ãŸãããã
確èªããŠã¿ã
ãŸãã¯ã³ã³ãã€ã«ã
$ mvn compile
èµ·åã¯ã以äžã®ã³ãã³ãã§è¡ããŸãã
$ java \ -Djdk.virtualThreadScheduler.parallelism=1 \ -Djdk.virtualThreadScheduler.maxPoolSize=1 \ -Djdk.tracePinnedThreads=[full or short] \ -cp target/classes \ org.littlewings.virtualthreads.SimpleHttpServer
ã·ã¹ãã ããããã£jdk.virtualThreadScheduler.parallelism
ãšjdk.virtualThreadScheduler.maxPoolSize
ã
1ã«ããŠäžŠå床ããã³å©çšå¯èœãªãã©ãããã©ãŒã ã¹ã¬ããã®æ°ã1ã«ããŠããŸãã
-Djdk.tracePinnedThreads=full
ãš-Djdk.tracePinnedThreads=short
ã§ãããã確èªããŠã¿ãŸãããã
-Djdk.tracePinnedThreads=full
æåã¯ã-Djdk.tracePinnedThreads=full
ã§ç¢ºèªããŠã¿ãŸãããã
$ java \ -Djdk.virtualThreadScheduler.parallelism=1 \ -Djdk.virtualThreadScheduler.maxPoolSize=1 \ -Djdk.tracePinnedThreads=full \ -cp target/classes \ org.littlewings.virtualthreads.SimpleHttpServer
åã¢ã¯ã»ã¹ãã¹ããšã«ç¢ºèªããŠã¿ãŸãã
/sleep
TimeUnit#sleep
ã§ã¹ãªãŒãããã/sleep
ã«ã¢ã¯ã»ã¹ã2ã€ã®ãªã¯ãšã¹ãã¯ãå¥ã
ã®ã¿ãŒããã«ã§é£ç¶ã§å®è¡ããŠããŸãã
## ã²ãšã€ç® $ time curl localhost:8080/sleep sleep. real 0m3.277s user 0m0.014s sys 0m0.000s ## 2ã€ç® $ time curl localhost:8080/sleep sleep. real 0m3.013s user 0m0.008s sys 0m0.004s
HTTPãµãŒããŒåŽã®ãã°äžã¯ãå€ãã£ããšããã¯ãããŸããã
[2024-04-03 23:09:24] - handler-1 - access[GET:/sleep] start [2024-04-03 23:09:24] - handler-2 - access[GET:/sleep] start [2024-04-03 23:09:27] - handler-1 - access[GET:/sleep] end [2024-04-03 23:09:27] - handler-2 - access[GET:/sleep] end
/heavy
空ã«ãŒããåããŠCPUãæ¶è²»ãã/heavy
ã
## ã²ãšã€ç® $ time curl localhost:8080/heavy heavy. real 0m3.014s user 0m0.006s sys 0m0.005s ## 2ã€ç® $ time curl localhost:8080/heavy heavy. real 0m5.642s user 0m0.004s sys 0m0.003s
ãã¡ãã®ãã°ããå€ãã£ããšããã¯ãããŸããã§ããã
[2024-04-03 23:13:26] - handler-5 - access[GET:/heavy] start [2024-04-03 23:13:29] - handler-5 - access[GET:/heavy] end [2024-04-03 23:13:29] - handler-6 - access[GET:/heavy] start [2024-04-03 23:13:32] - handler-6 - access[GET:/heavy] end
/lock
ReentrantLock
ã䜿ã/lock
ã
## ã²ãšã€ç® $ time curl localhost:8080/lock lock. real 0m3.012s user 0m0.001s sys 0m0.009s ## 2ã€ç® $ time curl localhost:8080/lock lock. real 0m5.662s user 0m0.006s sys 0m0.005s
ãã°ã
[2024-04-03 23:26:15] - handler-9 - access[GET:/lock] start [2024-04-03 23:26:16] - handler-10 - access[GET:/lock] start [2024-04-03 23:26:18] - handler-9 - access[GET:/lock] end [2024-04-03 23:26:21] - handler-10 - access[GET:/lock] end
ReentrantLock
ã䜿ã£ãŠããã®ã§åé¡ãªãã§ããã/lock2
ã¯ããé£ã°ãããšã«ããŸãã
/synchronized-lock
synchronized
ã§ããã¯ãåãã/synchronized-lock
ãè©ŠããŠã¿ãŸãã
## ã²ãšã€ç® $ time curl localhost:8080/synchronized-lock synchronized lock. real 0m3.013s user 0m0.008s sys 0m0.003s ## 2ã€ç® $ time curl localhost:8080/synchronized-lock synchronized lock. real 0m5.543s user 0m0.001s sys 0m0.009s
ãã°ã¯ãããªããŸããã
[2024-04-03 23:27:36] - handler-13 - access[GET:/synchronized-lock] start Thread[#39,ForkJoinPool-1-worker-2,5,CarrierThreads] java.base/java.lang.VirtualThread$VThreadContinuation.onPinned(VirtualThread.java:183) java.base/jdk.internal.vm.Continuation.onPinned0(Continuation.java:393) java.base/java.lang.VirtualThread.parkNanos(VirtualThread.java:621) java.base/java.lang.VirtualThread.sleepNanos(VirtualThread.java:791) java.base/java.lang.Thread.sleep(Thread.java:556) java.base/java.util.concurrent.TimeUnit.sleep(TimeUnit.java:446) org.littlewings.virtualthreads.SimpleHttpServer.lambda$createHandler$1(SimpleHttpServer.java:160) <== monitors:1 jdk.httpserver/com.sun.net.httpserver.Filter$Chain.doFilter(Filter.java:98) jdk.httpserver/sun.net.httpserver.AuthFilter.doFilter(AuthFilter.java:82) jdk.httpserver/com.sun.net.httpserver.Filter$Chain.doFilter(Filter.java:101) jdk.httpserver/sun.net.httpserver.ServerImpl$Exchange$LinkHandler.handle(ServerImpl.java:871) jdk.httpserver/com.sun.net.httpserver.Filter$Chain.doFilter(Filter.java:98) jdk.httpserver/sun.net.httpserver.ServerImpl$Exchange.run(ServerImpl.java:847) java.base/java.util.concurrent.ThreadPerTaskExecutor$TaskRunner.run(ThreadPerTaskExecutor.java:314) java.base/java.lang.VirtualThread.run(VirtualThread.java:309) [2024-04-03 23:27:39] - handler-13 - access[GET:/synchronized-lock] end [2024-04-03 23:27:39] - handler-14 - access[GET:/synchronized-lock] start [2024-04-03 23:27:42] - handler-14 - access[GET:/synchronized-lock] end
ãªãã»ã©ã確ãã«ã¹ã¿ãã¯ãã¬ãŒã¹ãåºåãããŠããŸããããããã©ãã§synchronized
ã«å
¥ã£ãŠããããããããã§ããïŒmonitors
ïŒã
äžå®æéãçµéããŠããåºåãããããšããããå³åºåãããããã§ãããé£ç¶ã§å®è¡ããæã®2åç®ã®æ¹ã¯ã¹ã¿ãã¯ãã¬ãŒã¹ãåºåãããŠ
ããªããã§ããããããã«è¿œå ã§å®è¡ããŠãåºåãããŸããã§ãããããã¯ã©ãããŠãªã®ã§ãããïŒ
ãã®çç±ã¯ãããšã§èª¿ã¹ãŠã¿ãŸããã
/synchronized-lock2
ãé£ã°ãããšã«ããŸãã
-Djdk.tracePinnedThreads=short
次ã¯HTTPãµãŒããŒãåèµ·åããŠã-Djdk.tracePinnedThreads=short
ã§ç¢ºèªããŠã¿ãŸãããã
$ java \ -Djdk.virtualThreadScheduler.parallelism=1 \ -Djdk.virtualThreadScheduler.maxPoolSize=1 \ -Djdk.tracePinnedThreads=short \ -cp target/classes \ org.littlewings.virtualthreads.SimpleHttpServer
/synchronized-lock
ããçµæãããã£ãŠããã®ã§ãsynchronized
ã§ããã¯ãåã/synchronized-lock
ã®ã¿ã§è©ŠããŸãã
## ã²ãšã€ç® $ time curl localhost:8080/synchronized-lock synchronized lock. real 0m3.090s user 0m0.005s sys 0m0.004s ## 2ã€ç® $ time curl localhost:8080/synchronized-lock synchronized lock. real 0m5.700s user 0m0.008s sys 0m0.003s
ãã°ã¯ãã®ããã«ãªããŸãããã¹ã¿ãã¯ãã¬ãŒã¹ãéåžžã«ã·ã³ãã«ãšããããåé¡ã«ãªã£ãç®æã®ã¿ãåºåãããŠããŸãã
[2024-04-03 23:31:08] - handler-1 - access[GET:/synchronized-lock] start Thread[#25,ForkJoinPool-1-worker-1,5,CarrierThreads] org.littlewings.virtualthreads.SimpleHttpServer.lambda$createHandler$1(SimpleHttpServer.java:160) <== monitors:1 [2024-04-03 23:31:11] - handler-1 - access[GET:/synchronized-lock] end [2024-04-03 23:31:11] - handler-2 - access[GET:/synchronized-lock] start [2024-04-03 23:31:14] - handler-2 - access[GET:/synchronized-lock] end
確ãã«short
ã§ãã
ããã§ãã·ã¹ãã ããããã£jdk.tracePinnedThreads
ã®å¹æãããããŸãããã
ããå°ã
ããããã¯ãå°ãå®è£ ãèŠãŠã¿ãŸãããã
ã·ã¹ãã ããããã£jdk.tracePinnedThreadsã®åç §ç®æ
ãã¡ãã§èŠãŠããããã§ãã
private static int tracePinningMode() { String propValue = GetPropertyAction.privilegedGetProperty("jdk.tracePinnedThreads"); if (propValue != null) { if (propValue.length() == 0 || "full".equalsIgnoreCase(propValue)) return 1; if ("short".equalsIgnoreCase(propValue)) return 2; } return 0; }
ãããèŠããšãã·ã¹ãã ããããã£jdk.tracePinnedThreads
ã®ã¿ãæå®ããå Žåã¯full
ãæå®ããå Žåãšå矩ã«ãªãããã§ããã
ãã®çµæã¯å®æ°ãšããŠä¿æãã
private static final int TRACE_PINNING_MODE = tracePinningMode();
VThreadContinuation#onPinned
ã§pinningïŒãã³çãïŒãæ€åºããæã«ãã·ã¹ãã ããããã£jdk.tracePinnedThreads
ãæå®ãããŠããã°
å³åº§ã«åºåãããŸãã
@Override protected void onPinned(Continuation.Pinned reason) { if (TRACE_PINNING_MODE > 0) { boolean printAll = (TRACE_PINNING_MODE == 1); PinnedThreadPrinter.printStackTrace(System.out, printAll); } }
TRACE_PINNING_MODE
ã1ã®å Žåã¯full
ã§ããã
pinningïŒãã³çãïŒã®ç¶æ ãã©ããã¯ããã¡ãã§æ€åºããŸãã
@Hidden private boolean yield0(ContinuationScope scope, Continuation child) { preempted = false; if (scope != this.scope) this.yieldInfo = scope; int res = doYield(); U.storeFence(); // needed to prevent certain transformations by the compiler assert scope != this.scope || yieldInfo == null : "scope: " + scope + " this.scope: " + this.scope + " yieldInfo: " + yieldInfo + " res: " + res; assert yieldInfo == null || scope == this.scope || yieldInfo instanceof Integer : "scope: " + scope + " this.scope: " + this.scope + " yieldInfo: " + yieldInfo + " res: " + res; if (child != null) { // TODO: ugly if (res != 0) { child.yieldInfo = res; } else if (yieldInfo != null) { assert yieldInfo instanceof Integer; child.yieldInfo = yieldInfo; } else { child.yieldInfo = res; } this.yieldInfo = null; } else { if (res == 0 && yieldInfo != null) { res = (Integer)yieldInfo; } this.yieldInfo = null; if (res == 0) onContinue(); else onPinned0(res); } assert yieldInfo == null; return res == 0; }
Continuation#doYield
ã¡ãœããã¯native
ã¡ãœãããªã®ã§è©³çŽ°ã¯èŠãŠããŸããã
int res = doYield();
ãããŠããã®çµæã0ã§ãªãå Žåã¯pinningïŒãã³çãïŒãšå€å®ããŸãã
if (res == 0) onContinue(); else onPinned0(res);
ããããVThreadContinuation#onPinned
ãåŒã³åºãããã§ããã
private void onPinned0(int reason) { onPinned(pinnedReason(reason)); }
Coninuation#pinnedReason
ã®å
容ã¯ãã¡ãã
private static Pinned pinnedReason(int reason) { return switch (reason) { case 2 -> Pinned.CRITICAL_SECTION; case 3 -> Pinned.NATIVE; case 4 -> Pinned.MONITOR; default -> throw new AssertionError("Unknown pinned reason: " + reason); }; }
Pinned
ãšããã®ã¯åæåã§ãpinningïŒãã³çãïŒã®çç±ãè¡šããŸãã
/** Reason for pinning */ public enum Pinned { /** Native frame on stack */ NATIVE, /** Monitor held */ MONITOR, /** In critical section */ CRITICAL_SECTION }
ããšã¯å
ã»ã©ã®ã·ã¹ãã ããããã£jdk.tracePinnedThreads
ãæå®ãããŠããæã«ãVThreadContinuation#onPinned
ãåŒã³åºããŠãã
PinnedThreadPrinter#printStackTrace
ã§ãã¹ã¿ãã¯ãã¬ãŒã¹ãåºåãããšããããšã«ãªããŸãã
/** * Prints the continuation stack trace. * * @param printAll true to print all stack frames, false to only print the * frames that are native or holding a monitor */ static void printStackTrace(PrintStream out, boolean printAll) { List<LiveStackFrame> stack = STACK_WALKER.walk(s -> s.map(f -> (LiveStackFrame) f) .filter(f -> f.getDeclaringClass() != PinnedThreadPrinter.class) .collect(Collectors.toList()) ); // find the closest frame that is causing the thread to be pinned stack.stream() .filter(f -> (f.isNativeMethod() || f.getMonitors().length > 0)) .map(LiveStackFrame::getDeclaringClass) .findFirst() .ifPresentOrElse(klass -> { int hash = hash(stack); Hashes hashes = HASHES.get(klass); synchronized (hashes) { // print the stack trace if not already seen if (hashes.add(hash)) { printStackTrace(stack, out, printAll); } } }, () -> printStackTrace(stack, out, true)); // not found }
ã¹ã¿ãã¯ãã¬ãŒã¹ã1床ããåºåãããªãã£ãã®ã¯ïŒ
å ã»ã©èšèŒããç®æã«çããèŒã£ãŠããã®ã§ãããããã§ããã
int hash = hash(stack); Hashes hashes = HASHES.get(klass); synchronized (hashes) { // print the stack trace if not already seen if (hashes.add(hash)) { printStackTrace(stack, out, printAll); } }
ã¹ã¿ãã¯ãã¬ãŒã ã®ããã·ã¥å€ãåããåãã¹ã¿ãã¯ãã¬ãŒã ããã¯2床åºåããªãããã«ãªã£ãŠããŸãã
/** * Returns a hash of the given stack trace. The hash is based on the class, * method and bytecode index. */ private static int hash(List<LiveStackFrame> stack) { int hash = 0; for (LiveStackFrame frame : stack) { hash = (31 * hash) + Objects.hash(frame.getDeclaringClass(), frame.getMethodName(), frame.getByteCodeIndex()); } return hash; }
synchronized
ãããã¯ãå®è¡ãããã¹ã«2åã¢ã¯ã»ã¹ããã®ã«ã1床ããåºåãããªãã£ãã®ã¯ãããçç±ã§ããã
ãããã«
JEP 444ïŒVirtual ThreadsïŒã®pinningïŒãã³çãïŒãèµ·ãã£ãéã«ãã·ã¹ãã ããããã£jdk.tracePinnedThreads
ã«ãã
ã¹ã¿ãã¯ãã¬ãŒã¹ãåºåãããããšã確èªããŠã¿ãŸããã
pinningïŒãã³çãïŒèªäœã«ã€ããŠã¯ååã確èªããŠããŸãããããã®æ©èœã®ããšãå¿ããŠããã®ã§ä»å確èªããŠãããŠããã£ãã§ãã
pinningïŒãã³çãïŒãšãããã®ãã©ããããã®ããã©ãããæ¡ä»¶ã§ã¹ã¿ãã¯ãã¬ãŒã¹ãåºåãããããšãã§ãããã確èªã§ããŸããããã