Post

Saveole's Tech Review, Issue 1

JDK24发布了,改动还挺多|Spring7预览版先来预览预览|Java + eBPF

Post

  • Java

    • JDK 24 发布了!

      JDK 24 最近(2025-03-18)发布了, 讲下几个让我感兴趣的地方吧。

      • Compact Object Headers

        压缩对象头:将 64 位机器的 HotSpot JVM 对象头从 96 - 128 位压缩为 64 位,且保证由此造成的吞吐/延迟损失不超过 5%,相关的项目是 Liliput。Java 一直以较大的内存开销闻名,很大一块原因就是 Java 对象的冗余设计,在云原生时代这的确是一个劣势。好在社区在这块终于有所动作,最近会花时间看看压缩对象头在内存这块有多大提升。

      • Stream Gatherers

        这个 JEP 是对 Stream API 的增强,用户能够自定义构建流的中间(如 jdk 提供的 filter/map/flatMap/distinct/limit 等返回 Stream<T> 的方法)操作,简单用法:source.gather(...).gather(...).gather(...).collect(...)

      • Ahead-of-Time Class Loading & Linking

        加速 JVM 应用启动时间的技术,通过监控和存储系统在运行时的 class 等信息,在下次启动时直接链接/加载上次存储的文件到内存直接运行,大大减少应用的启动时间。

      • Prepare to Restrict the Use of JNI

        开始限制 JNI 的使用,引导开发者使用 Foreign Function & Memory API

      • Class-File API

        直接操作字节码文件的 API,因为 Java 六个月的发版节奏导致字节码文件格式变化加快,怕第三方字节码操作库如 ASM 等跟不上节奏,官方自己下场干活了,这对于 APM 类软件研发可能是更好的选择。

      • Module Import Declarations (Second Preview)

        类似将 import java.util.* 的引用变更为 import module java.base,如果引用某一个模块的类比较多的话,还是能大大简化 import 语句的。

      • ZGC: Remove the Non-Generational Mode

        上个版本 ZGC 默认使用分代模式,这个版本中是移除 ZGC 的非分代模型。

      • Permanently Disable the Security Manager

        JDK 17 中开始准备移除 Security Manager,现在正式永久移除。

      • Synchronize Virtual Threads without Pinning

        之前版本的 Virtual Thread + synchronized 在执行方法出现阻塞(例如 IO 场景)(pinning)的情况下,VT 不能 unmount 其依赖的底层 OS 线程,可能导致线程饥饿甚至死锁等问题(可以通过 jdk.VirtualThreadPinned JFR 事件监控),Netflix 团队之前也遇到过,JEP491 解决了这个问题。下面是一个由此导致的死锁代码案例:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      
      ```java
      import java.time.Duration;
      import java.util.List;
      import java.util.concurrent.locks.ReentrantLock;
      import java.util.stream.IntStream;
      import java.util.stream.Stream;
      
      /**
       * Demonstrate potential for deadlock on a {@link ReentrantLock} when there is both a synchronized and
       * non-synchronized path to that lock, which can allow a virtual thread to hold the lock, but
       * other pinned waiters to consume all the available workers. 
      */
      public class VirtualThreadReentrantLockDeadlock {
      
          public static void main(String[] args) {
              final boolean shouldPin = args.length == 0 ||Boolean.parseBoolean(args[0]);
              final ReentrantLock lock = new ReentrantLock(true); // With faireness to ensure that the unpinned thread is next in line
      
              lock.lock();
          
              Runnable takeLock = () -> {
                  try {
                      System.out.println(Thread.currentThread() + " waiting for lock");
                      lock.lock();
                      System.out.println(Thread.currentThread() + " took lock");
                  } finally {
                      lock.unlock();
                      System.out.println(Thread.currentThread() + " released lock");
                  }
              };
      
              Thread unpinnedThread = Thread.ofVirtual().name("unpinned").start(takeLock);
      
              List<Thread> pinnedThreads = IntStream.range(0, Runtime.getRuntime().availableProcessors())
          .mapToObj(i -> Thread.ofVirtual().name("pinning-" + i).start(() -> {
                      if (shouldPin) {
                          synchronized (new Object()) {
                              takeLock.run();
                          }
                      } else {
                          takeLock.run();
                      }
                  })).toList();
          
              lock.unlock();
          
              Stream.concat(Stream.of(unpinnedThread), pinnedThreads.stream()).forEach(thread -> {
                  try {
                      if (!thread.join(Duration.ofSeconds(3))) {
                          throw new RuntimeException("Deadlock detected");                    
                      }
                  } catch (InterruptedException e) {
                      throw new RuntimeException(e);
                  }
              });
          }
      
      }
      ```
          
      ```
      (base) saveole@saveoledeMacBook-Pro src % java VirtualThreadReentrantLockDeadlock.java
      VirtualThread[#25,unpinned]/runnable@ForkJoinPool-1-worker-1 waiting for lock
      VirtualThread[#28,pinning-0]/runnable@ForkJoinPool-1-worker-1 waiting for lock
      VirtualThread[#30,pinning-2]/runnable@ForkJoinPool-1-worker-3 waiting for lock
      VirtualThread[#29,pinning-1]/runnable@ForkJoinPool-1-worker-2 waiting for lock
      VirtualThread[#33,pinning-5]/runnable@ForkJoinPool-1-worker-6 waiting for lock
      VirtualThread[#31,pinning-3]/runnable@ForkJoinPool-1-worker-4 waiting for lock
      VirtualThread[#35,pinning-7]/runnable@ForkJoinPool-1-worker-9 waiting for lock
      VirtualThread[#32,pinning-4]/runnable@ForkJoinPool-1-worker-5 waiting for lock
      VirtualThread[#34,pinning-6]/runnable@ForkJoinPool-1-worker-7 waiting for lock
      VirtualThread[#36,pinning-8]/runnable@ForkJoinPool-1-worker-8 waiting for lock
      VirtualThread[#38,pinning-9]/runnable@ForkJoinPool-1-worker-10 waiting for lock
      Exception in thread "main" java.lang.RuntimeException: Deadlock detected
      at VirtualThreadReentrantLockDeadlock.lambda$main$3(VirtualThreadReentrantLockDeadlock.java:49)
      at java.base/java.util.stream.Streams$StreamBuilderImpl.forEachRemaining(Streams.java:411)
      at java.base/java.util.stream.Streams$ConcatSpliterator.forEachRemaining(Streams.java:734)
      at java.base/java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:762)
      at VirtualThreadReentrantLockDeadlock.main(VirtualThreadReentrantLockDeadlock.java:46)
      
      ```
      
    • Reproducing a Java 21 virtual threads deadlock scenario with TLA+

      ​对 Java 21 虚拟线程 + synchronized 造成死锁的解释文章,值得一看。

  • Spring Framework 7.0 - preview 版本

GitHub

Video

Book

  • NaN
This post is licensed under CC BY 4.0 by the author.