不寻常的 Java:StackTrace 扩展了 Throwable

浮生若梦 -
不寻常的 Java:StackTrace 扩展了 Throwable

在 Java 中你可以做一些你很少看到的事情,通常是因为它没有用处。但是,Java 中有一些不寻常的东西可能会非常有用。

Chronicle Software在其低级库中使用了许多不同的常用模式,大多数开发人员通常不会遇到。

其中之一是扩展 Throwable 但不是错误或异常的类。

StackTrace 扩展了 Throwable

public class EgMain {
   static class MyCloseable implements Closeable {
       protected transient volatile StackTrace closedHere;

       @Override
       public void close() {
           closedHere = new StackTrace("Closed here"); // line 13
       }

       public void useThis() {
           if (closedHere != null)
               throw new IllegalStateException("Closed", closedHere);
       }
   }

   public static void main(String[] args) throws InterruptedException {      
 MyCloseable mc = new MyCloseable(); // line 27
       Thread t = new Thread(mc::close, "closer");
       t.start();
       t.join();
       mc.useThis();
   }
}

一些重要的旁注先让开

是的,我确实在我的 IDE 中使用了比例字体。我在 Windows 上使用 Verdana,我很容易习惯并且不想回去。
这不是我期望被抛出的课程。检查直接扩展 Throwable 的类,就像 Exception 一样,因此编译器将帮助您执行此操作。
Throwable 的堆栈跟踪是在创建 Throwable 时确定的,而不是在抛出它的位置。通常,这是同一行,但并非必须如此。不必抛出 Throwable 即可获得堆栈跟踪。
堆栈跟踪元素对象在需要时才会创建。相反,元数据被添加到对象本身以减少开销,并且在首次使用时填充 StackTraceElements 数组。
但是,让我们更详细地看一下这个类。该类将记录它创建位置的堆栈跟踪和创建它的线程。稍后您应该会看到这有什么用处。

它还可以用于保存另一个正在运行的线程的堆栈跟踪。仅当线程到达安全点时才会获取另一个线程的堆栈跟踪,这可能是在您尝试获取它之后的一段时间。这是由于 JVM 停止线程,并且通常 JVM 等待停止每个线程,因此它可以检查您尝试捕获的线程的堆栈。

即它有很高的开销,但可能非常有用。

StackTrace 作为延迟异常
我们不希望抛出这个 Throwable,但它可以记录稍后可能抛出的异常的原因。

为什么资源被关闭

public class CreatedMain {
   static class MyResource implements Closeable {
       private final transient StackTrace createdHere = new StackTrace("Created here");
       volatile transient boolean closed;

       @Override
       public void close() throws IOException {
           closed = true;
       }

       @Override
       protected void finalize() throws Throwable {
           super.finalize();
           if (!closed)
               Logger.getAnonymousLogger().log(Level.WARNING, "Resource discarded but not closed", createdHere);
       }
   }

   public static void main(String[] args) throws InterruptedException {
       new MyResource(); // line 27
       System.gc();
       Thread.sleep(1000);
   }
}

运行时产生以下异常:
image.png

学习知识

通常您会看到 IllegalStateException 以及您的代码尝试使用已关闭资源的位置,但这并不能告诉您为什么在没有其他信息的情况下关闭它。

由于 StackTrace 是 Throwable,您可以将其作为后续异常或错误的原因。

您可以看到关闭资源的线程,因此您知道它发生在另一个线程中,并且您可以看到它关闭原因的堆栈跟踪。这有助于快速诊断过早关闭资源的难以发现的问题。

哪个资源被丢弃了?
长寿命的 Closeable 对象可能有一个复杂的生命周期,并且确保它们在需要时关闭可能难以追踪,并且可能导致资源泄漏。当 GC 释放对象时,某些资源不会被清理,例如 RandomAccessFile 对象在 GC 上被清理,除非您关闭它,否则它所代表的文件不会关闭,从而导致文件句柄的潜在资源泄漏。

public class JitteryMain implements Runnable {
   volatile long loopStartMS = Long.MIN_VALUE;
   volatile boolean running = true;

   @Override
   public void run() {
       while (running) {
           loopStartMS = System.currentTimeMillis();
           doWork();
           loopStartMS = Long.MIN_VALUE;
       }
   }

   private void doWork() {
       int loops = new Random().nextInt(100);
       for (int i = 0; i < loops; i++)
           pause(1); // line 24
   }

   static void pause(int ms) {
       try {
           Thread.sleep(ms); // line 29
       } catch (InterruptedException e) {
           throw new AssertionError(e); // shouldn't happen
       }
   }

   public static void main(String[] args) {
       final JitteryMain jittery = new JitteryMain();
       Thread thread = new Thread(jittery, "jitter");
       thread.setDaemon(true);
       thread.start();

       // monitor loop
       long endMS = System.currentTimeMillis() + 1_000;
       while (endMS > System.currentTimeMillis()) {
           long busyMS = System.currentTimeMillis() - jittery.loopStartMS;
           if (busyMS > 100) {
               Logger.getAnonymousLogger()
                       .log(Level.INFO, "Thread spent longer than expected here, was " + busyMS + " ms.",
                               StackTrace.forThread(thread));
           }
           pause(50);
       }
       jittery.running = false;
   }
}

打印以下内容,您可以再次看到很容易在 IDE 中导航堆栈。

image.png

您可能想知道为什么在这种情况下会发生这种情况。最可能的原因是Thread.sleep(time)睡眠时间最短,而不是最长,并且在 Windows 上睡眠 1 毫秒实际上相当一致地需要大约 1.9 毫秒。

检测单线程资源何时在线程间并发访问

package net.openhft.chronicle.core;

public class ConcurrentUsageMain {
   static class SingleThreadedResource {
       private StackTrace usedHere;
       private Thread usedByThread;

       public void use() {
           checkMultithreadedAccess();
           // BLAH
       }

       private void checkMultithreadedAccess() {
           if (usedHere == null || usedByThread == null) {
               usedHere = new StackTrace("First used here");
               usedByThread = Thread.currentThread();
           } else if (Thread.currentThread() != usedByThread) {
               throw new IllegalStateException("Used two threads " + Thread.currentThread() + " and " + usedByThread, usedHere);
           }
       }
   }

   public static void main(String[] args) throws InterruptedException {
       SingleThreadedResource str = new SingleThreadedResource();
       final Thread thread = new Thread(() -> str.use(), "Resource user"); // line 25
       thread.start();
       thread.join();

       str.use(); // line 29
   }
}

打印以下内容:

image.png

您可以看到该资源已被两个线程及其名称使用,但是,您还可以看到它们在堆栈中用于确定可能原因的位置。

关闭此跟踪
创建 StackTrace 对线程和可能的 JVM 有重大影响。但是,使用系统属性等控制标志很容易将其关闭并替换为空 值。

使用null 不需要太多特殊处理,因为记录器会忽略一个为null的 Throwable ,您可以为 Exception 提供一个null 原因,这与不提供一个原因相同。

结论
虽然有一个直接扩展 Throwable 的类令人惊讶,但它是允许的,并且对于提供有关资源生命周期的其他信息或添加可以在生产中运行的简单监控也非常有用。

如果本文对你有帮助,别忘记给我个3连 ,点赞,转发,评论,,咱们下期见。
收藏 等于白嫖,点赞才是真情。

学习更多JAVA知识与技巧,关注与私信博主

特别申明:本文内容来源网络,版权归原作者所有,如有侵权请立即与我们联系(cy198701067573@163.com),我们将及时处理。

Tags 标签

java数据库算法git小程序

扩展阅读

加个好友,技术交流

1628738909466805.jpg