前言
启动一个Java程序,本质上是运转某个Java类的main办法。咱们写一个死循环程序,跑起来,然后运转jvisualvm
进行调查
能够看到这个Java进程中,一共有11个线程,其中10个守护线程,1个用户线程。咱们main办法中的代码,就跑在一个名为main
的线程中。当Java进程中跑着的一切线程都是守护线程时,JVM就会退出线程池原理。
在单线程的场景下,如果代码运转到某个方位时抛出了反常,会看到控制台打印出反常的仓库信息。
但在jvm面试题多线程的场景下,子线程中发生的反常,不一定就能及时的将反常信息打印出来。
我曾经在作业中遇到过一次,采用Compljava怎么读etableFuture.runAsync
异步处理耗时使命时,使命处理进程中出现反常,然而日志中没有任何关于反常的信息。
时隔许久,从头温习了线程中的反常处理机制,加深字节码了对线程作业jvm原理原理的了解,特此记录。
线程的反常处理机制
咱们开源软件知道,Java程序的运转,是先经由javac
将Java源代码编译成class字节码文件,然后由JVM加载并解析cljavascriptass文件,随后从主类的main办法开始履行。
当一个线程在运转进程中抛出了未捕获线程池的七个参数反常时,会由JVM调用这个线程目标上的dispatchUncaughtExce开源矿工ption
办法,进行反常处理。
// Thread类中
private void dispatchUncaughtException(Throwable e) {
getUncaughtExceptionHandler().uncaughtException(this, e);
}
源码很好了解,先获取一个UncaughtExceptionHandl线程池原理er
反常处理器,然后经过调用这个反常处理器的uncaughtException
办法来对反常进行处理。(下文用缩写ueh
来表明UncaughtExceptionHandler
)
uehjavascript
是个 啥呢?其实便是定义在Thread
内部的一个接口,用作反常处理。
@FunctionalInterface
public interface UncaughtExceptionHandler {
/**
* Method invoked when the given thread terminates due to the
* given uncaught exception.
* <p>Any exception thrown by this method will be ignored by the
* Java Virtual Machine.
* @param t the thread
* @param e the exception
*/
void uncaughtException(Thread t, Throwable e);
}
再来看下Thread
目标中的getUncaughtExceptionHandler
办法
public UncaughtExceptionHandler getUncaughtExceptionHandler() {
return uncaughtExceptionHandler != null ?
uncaughtExceptionHandler : group;
}
先java面试题检查当时字节码文件这个线程池原理Thread
目标是否有设置字节码文件扩展名自定义的ueh
目标,若有,则由其对反jvm垃圾回收机制常进行处理,不然,由当时Thread
目标所属的线程组(ThreadGroupjvm是什么
)进行反常处理。咱们点开源码开源节流,简略发线程池的工作原理现ThreJVMadGroup
类本身完成了Thread.UncaughtExceptionHandler
接口,也便是jvm内存模型说ThreadGroup
本身便是个反常处理器。
public class ThreadGroup implements Thread.UncaughtExceptionHandler {
private final ThreadGroup parent;
....
}
假设咱们在main
办法中抛出一个反常,若没有对majava培训in
线程设置自定义的ueh
目标,则交由main
线程所属的ThreadGroup
来处理反常。咱们看下Threjava环境变量配置adGroup
是怎开源矿工样处理反常的:
public void uncaughtException(Thread t, Throwable e) {
if (parent != null) {
parent.uncaughtException(t, e);
} else {
Thread.UncaughtExceptionHandler ueh =
Thread.getDefaultUncaughtExceptionHandler();
if (ueh != null) {
ueh.uncaughtException(t, e);
} else if (!(e instanceof ThreadDeath)) {
System.err.print("Exception in thread ""
+ t.getName() + "" ");
e.printStackTrace(System.err);
}
}
}
这部分源码也比较简略。首先是检查当时ThreadGroup
是jvm内存模型否具有父级的Thjava怎么读readGr开源代码网站githuboup
,若有,则调用父级ThreadGroup
进行反常处理。不然,调用静态办法Thread.get开源软件DefaultUncaughtExceptionHandler()
获取一个默许的ueh
目标。
若默许的ue线程池面试题h
目标不为jvm优化空,则由这个开源阅读默许的ueh
目标进行反常处理;不然,当反常不是ThreadDeath
时,直接将当时线程的姓名,和反常的仓库信息,经过标准错误输出(System.err
)打印到控制台。
咱们随便运转一个main
办法,看一下线程的开源阅读情况
能够看到,main
线程归于一个同样名为main
的ThreadGroup
,而这个main
的ThreadGroup
,其父级ThreadGroup
名为sjvm垃圾回收机制ystem
,而这个system
的ThreadGroupjava编译器
,没有父级了,它便是根Th开源中国readGroup
。
由此可知,main
线程中抛出的未捕获反常,终究会交由名为system
的ThreadGroup
进行反常处理,而由于没有设置默许的ueh
目标,反常信息会经过Sjava培训ystem.err
输出到控制台。
接下字节码文件扩展名来,咱们经过最朴素的办法(new
一个Thr开源节流ead
),在main
线程中创立一个子线程,在子线程中编写能抛出反常的代码,进jvm是什么行调查
public static void main(String[] args) {
Thread thread = new Thread(() -> {
System.out.println(3 / 0);
});
thread.start();
}
子线程中的反字节码是什么意思常信息被打印到了控制台。反常处理的流程便是咱们上面描绘的那样。
小结
所以,正常来说,如果没有对某个线程设置特定的ueh
目标;也没有调用静态办法Thread.setDefaultUncaughtExceptio字节码是什么意思nHandler
设置线程池的使用大局默许的ueh
目标。那么,在任意一个线程的运转进程中抛出未捕获反常时,反常信息都会被输出到控制台(当反常是ThreadDeath
时则不会进行jvm是什么意思输出,但一般来说,反常都不是ThreadDeath
,不过这个细节要留意下)。
如何设置自定义的ueh
目标来进行反常处理?依据上面的剖析可知,有2种办法
- 对某一个
Thread
目标,调用其setUncaughtExceptionHandler
办法,设置一个ueh
目标。留意这个ueh
目标只对这个线程起作用 - 调用静jvm调优态办法
Thread.setDefaultUncaughtExceptionHandler()
设置一个大局默许的ueh
目标。这样设置的ueh
目标会对一切线程起作用
当然,由于ThreadGroup
本身能够充任ueh
,所java怎么读以其实还能java环境变量配置够字节码完成一个ThreadGroup
子类,重写其uncaughtEx字节码是什么意思ception
办法进行反常处理。
若一个线程没有进行任何设置,当在这个线程内抛出反常后,默许会将线程名称和反常仓jvm是什么意思库,经过Systejava培训m.err
进行输出。
线程的反常处理机制,用一个流程图表明如下
线程池字节码场景下的反常处理
在实践的开发中,咱们经常会运用线程池来进行多线程的管理和控制,而不是经过new
来手动创立java编译器Thread
目标。
对于Java中的线程池ThreadPoolExecutjvm是什么意思or
,咱们知道,一般来说有两种办法,能够向线程池提交使命:
execute
submi线程池有哪几种t
其中execute
办法没有回来值,咱们经java怎么读过exejvm优化cute
提交的使命,只需求提交该使命给线程池履行,开源节流是什么意思而不需求获取使命的履行成果。
而submit
办法,会回来一个Future
目字节码是什么意思标,咱们经过submit
提交的使命,能够开源代码网站github经过这个Future
目标,拿到使命的履行成果。
咱们分别测验如下代码
public static void main(String[] args) {
ExecutorService threadPool = Executors.newSingleThreadExecutor();
threadPool.execute(() -> {
System.out.println(3 / 0);
});
}
public static void main(String[] args) {
ExecutorService threadPool = Executors.newSingleThreadExecutor();
threadPool.submit(() -> {
System.out.println(3 / 0);
});
}
简略得到如下成果:
经过execute
办法提交的使命,反常信息被打印到控制台;经过submit
办法提交的使命,没有出现反常信息。
咱们略微跟一下Thjvm是什么readPoolExecujava怎么读tor
的源码,当运用execute
办法提交使命时,在runWorker
办法中,会履行到下图红java环境变量配置框的部分
在上面的代码履行结束后,由于反常线程池创建被throw
了出来,所以会由JVM捕捉到,并调用当时子线程的dispatjvm原理chUncaughtException
办法进行处理,依据上面的剖析,终究反常仓库会被打印到控制台。
多扯几句别的。
上面跟源码时,留意到W开源是什么意思orker
是ThreadPoolExecutor
的一个内部类,也便是说,每个Worker
都会隐式的持有ThreadPoolExecutor
目标的引证jvm内存(内部类的相关原理请自行jvm调优补课)。字节码文件每个Worker
在运转时(在不同的子线程中运转)都能够对T字节码是什么意思hreadPoolExecutor
目标(一般来说这开源中国个目标是在main
线程中被维护)中的属性进行拜访和修正。Wjava环境变量配置orker
完成了Runnable
接口,并java编译器且其run
办法实践是调用的ThreadPoolExecutor
上的runWorker
办法。在新建一个WJVMorker
时,会创立一java怎么读个新的Thread
目标,并jvm性能调优把当时Worker
的引证传递给这个Threjavaeead
目标,随后调用这个Thread
目标的start开源矿工
办法,则开始在这个Thread
中(子线程中)运转这个Work开源代码网站githuber
。
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
ThreadPoolExecutor
中的addWorker
办法
再次跟源码时,加深了对ThreadPoolExecutorJava
和Worker
体系的了解和java面试题知道。
它jvm是什么意思们之间有一种嵌套依赖的联系。每个Worker
里持有一个Thread
目标,这个Threajvm调优d
目标又是以这个jvm性能调优Worker
目标作为Runnable
,而Worker
又是ThreadPoolEjvm是什么意思xecutor
的内部类,这意味着每个Worker
目标都会隐式的持有其所属的ThreadPoolExecutor
目标的引证。每个Worker
的run
办法, 都跑在子线程中,但是这些Worker
跑在子线程中时,能够对ThreadPoolExecutor
目标的属性进行拜访和修正(每个线程池的工作原理Worker
的run
办法都是调用的runWorker
,所以runWorker
办法是跑在子线程中的,这个办法中会对线程池的状况进行拜访和修正,比方当时子线程运转进程中抛出反常时,会从ThreadPoolExecutor
中移除当时Worker
,并开源节流启一个新的Worjavascriptker
)。而一般来说,ThreadPoolExecutor
目标的引证,咱们一般是在主线程字节码中进行维护的。
反正便是这中心其实有点骚东西,没那么简略。需求多跟几次源码,多自己打断点进行debug,debug进程中能够经过IDEA的Evaluate Expression
功用实时调查当时办法履行时所在的线程环境(Thread.开源众包currentThread
)。
扯得有点远了,现在回到正题。上面说了调用ThreadPoolExecutor
中的execute
办法提交使命,子线程中出现反常时,反常会被抛出,打印在控制台,并且当时Worker
会被线程池收回,并重启一个新的Worker
作为代替。
那么,调用submit
时,反常为何就没有被打印到控制台呢?
咱们看一下源码
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
return new FutureTask<T>(runnable, value);
}
经过调用submit
提交的使命,被包装了成了一个FutureTask
目标,随后会将这个Fujvm是什么tureTask
目标,经过execjvm调优ute
办法提交给线程池,并回来FutureTask
目标给主线程的调用者。
也便是说,submit
办法实践做了这几件事
- 将提交的
Runnable
,包装成FutureTask
- 调用
execute
办法提交这个FutureTask
(实践还是经过execute
提交的使命) - 将
FutureTask
作为回来值,回来给主线程的调用者
要害就开源众包在于FutureTask
,咱们来看一下
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
// Executors中
public static <T> Callable<T> callable(Runnable task, T result) {
if (task == null)
throw new NullPointerException();
return new RunnableAdapter<T>(task, result);
}
static final class RunnableAdapter<T> implements Callable<T> {
final Runnable task;
final T result;
RunnableAdapter(Runnable task, T result) {
this.task = task;
this.result = result;
}
public T call() {
task.run();
return result;
}
}
经过submit
办法传入的Runnable
,经过一个适配器RunnableAdapter
转化为了Callable
目标,并终究包装成为一个FutureTask
目标。这个FutureTask
,又完成了Runnabl开源阅读e
和Future
接口
所以咱们看下FutureTask
的run
办法(由于终究是将包装后的FutureTask
提交给线程池履行,所以终究会履行FutureTask
的run
办法)
protected void setException(Throwable t) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = t;
UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
finishCompletion();
}
}
能够看到,反常信息仅仅被简略的设置到了FutureTask
的outcome
字段上。并没有往外抛,所以这儿其实相当于把jvm性能调优反常给生吞了,catch
块中捕捉到开源阅读app下载安装反常后,既没有打印反常的仓库,也没有把反常持续往外throw
。所以咱们无法在控制台看到反常信息,在实践的项目中,此种场景下的反常信息也不会被输出到日志文件。这一点要特别留意,会加大问题的排查难度。
那么,为什么要这样处理呢?
由于咱们经过submit
提交使命时,会拿到一个Future
目标
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
咱们能够在稍后,经过Future
目标,来获悉使命的履行情况,包含使命是否成功履jvm面试题行结束,使命履行后回来的成果是什么,履行进程中是否出现反常。
所以,经过submit
提交的使命,实践会把使命的各种状况信息,都封装在FutureTask
目标中。当最后调用FutureTjava模拟器ask
目标上的get
办法,测验获取使命履行成果时,才能够看到反常信息被打印出来。
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}
private V report(int s) throws ExecutionException {
Object x = outcome;
if (s == NORMAL)
return (V)x;
if (s >= CANCELLED)
throw new CancellationException();
throw new ExecutionException((Throwable)x); // 反常会经过这一句被抛出来
}
小结
- 经过
ThreadPoolExecutor
的execute
办法提交的使命,出现反常后,反常会在子java是什么意思线程中被抛出,线程池的使用并被JVM捕获,并调用子线程的d线程池原理ispatchUncaughtException
办法,进行反常处理,若子线程没有任何特别设置,则反常仓库会被输出到System.err
,即反常会被打印到控制台上。并且会从线程池中移除当时Worker
,并另启一个新的Worker
作为字节码文件扩展名代替。 - 经过
ThreadPoolExecutor
的submit
办法提交的使命,使命会先被包装成FutureTask
目标,出现反常后,反常会被生吞,并暂存到FutureTask
目标中,作为使命履行成果的一部分。反常信息不会被打印jvm性能调优,该子线程池原理线程也不会被线程池移除(由于反常在子线程开源节流中被吞了,没有抛出来)。在调用FutureTask
上的g开源众包et
办法时(此时一般是在主线程中了),反常才会被抛出,触发主线程的反常处理,并输出到System.err
其他
其他的线程池场景。比方
- 运用
ScheduledThreadPoolExecutor
完成推迟使命或者守时使命(开源阅读周期使命)开源软件,javaee剖析进程也是相似。这儿给字节码是什么意思个简略定论,当调用scheduleAtFixedRate
办法履行一个周期使命时(使命会被包装成Fujvm调优tureJVMTask
(实践是ScheduledFutureTask
,是JVMFutureTask
的子类)),若周期使命中出现反常,反常会被生吞,反常信息不会被打印,线程不会被收回线程池创建,但是周期使命履行这一次jvm是什么意思后就不会持续履行了。ScheduledThreadPoolExec字节码utor
继java面试题承了ThreadPoolExec开源是什么意思utor
,所以其也是复用了ThreadPoolExecutor
的那一套逻辑。 - 运用
CompletableFuture
的runAsync
提交使命,底层是经过Fork线程池的工作原理JoinPool
线程开源阅读app下载安装池jvm内存进行履行,使命会被包装成AsyncRu开源矿工n
,且会回来一个CompletableFu线程池的七个参数tu线程池有哪几种re
给主线程开源阅读。当使命出现反常时,处java是什么意思理办法和ThreadPooljava语言Executor
的submit
相似,反常仓库不会被打印。只有在CompletableFujvm性能调优ture
上调用get
办法测验获取成果开源中国时,反常才会被打印。