在 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);
}
}
运行时产生以下异样:
学习常识
通常您会看到 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 中导航堆栈。
您可能想晓得为什么在这种状况下会产生这种状况。最可能的起因是 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}
}
打印以下内容:
您能够看到该资源已被两个线程及其名称应用,然而,您还能够看到它们在堆栈中用于确定可能起因的地位。
敞开此跟踪
创立 StackTrace 对线程和可能的 JVM 有重大影响。然而,应用零碎属性等管制标记很容易将其敞开并替换为空 值。
应用 null 不须要太多非凡解决,因为记录器会疏忽一个为 null 的 Throwable,您能够为 Exception 提供一个 null 起因,这与不提供一个起因雷同。
论断
尽管有一个间接扩大 Throwable 的类令人诧异,但它是容许的,并且对于提供无关资源生命周期的其余信息或增加能够在生产中运行的简略监控也十分有用。
如果本文对你有帮忙,别忘记给我个 3 连,点赞,转发,评论,,咱们下期见。
珍藏 等于白嫖,点赞才是真情。
学习更多 JAVA 常识与技巧,关注与私信博主