色哟哟视频在线观看-色哟哟视频在线-色哟哟欧美15最新在线-色哟哟免费在线观看-国产l精品国产亚洲区在线观看-国产l精品国产亚洲区久久

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

為什么在JVM中線程崩潰不會導致JVM進程崩潰呢?

jf_ro2CN3Fa ? 來源:碼海 ? 2023-01-09 10:39 ? 次閱讀

線程崩潰,進程一定會崩潰嗎

一般來說如果線程是因為非法訪問內存引起的崩潰,那么進程肯定會崩潰,為什么系統要讓進程崩潰呢,這主要是因為在進程中,各個線程的地址空間是共享的 ,既然是共享,那么某個線程對地址的非法訪問就會導致內存的不確定性,進而可能會影響到其他線程,這種操作是危險的,操作系統會認為這很可能導致一系列嚴重的后果,于是干脆讓整個進程崩潰

a54dbe82-7dff-11ed-8abf-dac502259ad0.jpg

線程共享代碼段,數據段,地址空間,文件

非法訪問內存有以下幾種情況,我們以 C 語言舉例來看看

針對只讀內存寫入數據

#include
#include

intmain(){
char*s="helloworld";
//向只讀內存寫入數據,崩潰
s[1]='H';
}

訪問了進程沒有權限訪問的地址空間(比如內核空間)

#include
#include

intmain(){
int*p=(int*)0xC0000fff;
//針對進程的內核空間寫入數據,崩潰
*p=10;
}

在 32 位虛擬地址空間中,p 指向的是內核空間,顯然不具有寫入權限,所以上述賦值操作會導致崩潰

訪問了不存在的內存,比如

#include
#include

intmain(){
int*a=NULL;
*a=1;
}

以上錯誤都是訪問內存時的錯誤,所以統一會報 Segment Fault 錯誤(即段錯誤),這些都會導致進程崩潰

基于 Spring Boot + MyBatis Plus + Vue & Element 實現的后臺管理系統 + 用戶小程序,支持 RBAC 動態權限、多租戶、數據權限、工作流、三方登錄、支付、短信、商城等功能

進程是如何崩潰的-信號機制簡介

那么線程崩潰后,進程是如何崩潰的呢,這背后的機制到底是怎樣的,答案是信號 ,大家想想要干掉一個正在運行的進程是不是經常用 kill -9 pid 這樣的命令,這里的 kill 其實就是給指定 pid 發送終止信號的意思,其中的 9 就是信號,其實信號有很多類型的,在 Linux 中可以通過 kill -l查看所有可用的信號

a57668a0-7dff-11ed-8abf-dac502259ad0.jpg

當然了發 kill 信號必須具有一定的權限,否則任意進程都可以通過發信號來終止其他進程,那顯然是不合理的,實際上 kill 執行的是系統調用,將控制權轉移給了內核(操作系統),由內核來給指定的進程發送信號

那么發個信號進程怎么就崩潰了呢,這背后的原理到底是怎樣的?

其背后的機制如下

CPU 執行正常的進程指令

調用 kill 系統調用向進程發送信號

進程收到操作系統發的信號,CPU 暫停當前程序運行,并將控制權轉交給操作系統

調用 kill 系統調用向進程發送信號(假設為 11,即 SIGSEGV,一般非法訪問內存報的都是這個錯誤)

操作系統根據情況執行相應的信號處理程序(函數),一般執行完信號處理程序邏輯后會讓進程退出

注意上面的第五步,如果進程沒有注冊自己的信號處理函數,那么操作系統會執行默認的信號處理程序(一般最后會讓進程退出),但如果注冊了,則會執行自己的信號處理函數,這樣的話就給了進程一個垂死掙扎的機會,它收到 kill 信號后,可以調用 exit() 來退出,但也可以使用 sigsetjmp,siglongjmp 這兩個函數來恢復進程的執行

//自定義信號處理函數示例

#include
#include
#include
//自定義信號處理函數,處理自定義邏輯后再調用exit退出
voidsigHandler(intsig){
printf("Signal%dcatched!
",sig);
exit(sig);
}
intmain(void){
signal(SIGSEGV,sigHandler);
int*p=(int*)0xC0000fff;
*p=10;//針對不屬于進程的內核空間寫入數據,崩潰
}

//以上結果輸出:Signal11catched!

如代碼所示 :注冊信號處理函數后,當收到 SIGSEGV 信號后,先執行相關的邏輯再退出

另外當進程接收信號之后也可以不定義自己的信號處理函數,而是選擇忽略信號,如下

#include
#include
#include

intmain(void){
//忽略信號
signal(SIGSEGV,SIG_IGN);

//產生一個SIGSEGV信號
raise(SIGSEGV);

printf("正常結束");
}

也就是說雖然給進程發送了 kill 信號,但如果進程自己定義了信號處理函數或者無視信號就有機會逃出生天,當然了 kill -9 命令例外,不管進程是否定義了信號處理函數,都會馬上被干掉

說到這大家是否想起了一道經典面試題:如何讓正在運行的 Java 工程的優雅停機,通過上面的介紹大家不難發現,其實是 JVM 自己定義了信號處理函數,這樣當發送 kill pid 命令(默認會傳 15 也就是 SIGTERM)后,JVM 就可以在信號處理函數中執行一些資源清理之后再調用 exit 退出。這種場景顯然不能用 kill -9,不然一下把進程干掉了資源就來不及清除了

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 實現的后臺管理系統 + 用戶小程序,支持 RBAC 動態權限、多租戶、數據權限、工作流、三方登錄、支付、短信、商城等功能

為什么線程崩潰不會導致 JVM 進程崩潰

現在我們再來看看開頭這個問題,相信你多少會心中有數,想想看在 Java 中有哪些是常見的由于非法訪問內存而產生的 Exception 或 error 呢,常見的是大家熟悉的 StackoverflowError 或者 NPE(NullPointerException),NPE 我們都了解,屬于是訪問了不存在的內存

但為什么棧溢出(Stackoverflow)也屬于非法訪問內存呢,這得簡單聊一下進程的虛擬空間,也就是前面提到的共享地址空間

現代操作系統為了保護進程之間不受影響,所以使用了虛擬地址空間來隔離進程,進程的尋址都是針對虛擬地址,每個進程的虛擬空間都是一樣的,而線程會共用進程的地址空間,以 32 位虛擬空間,進程的虛擬空間分布如下

a59d99d4-7dff-11ed-8abf-dac502259ad0.jpg

那么 stackoverflow 是怎么發生的呢,進程每調用一個函數,都會分配一個棧楨,然后在棧楨里會分配函數里定義的各種局部變量,假設現在調用了一個無限遞歸的函數,那就會持續分配棧幀,但 stack 的大小是有限的(Linux 中默認為 8 M,可以通過 ulimit -a 查看),如果無限遞歸很快棧就會分配完了,此時再調用函數試圖分配超出棧的大小內存,就會發生段錯誤,也就是 stackoverflowError

a5bb1176-7dff-11ed-8abf-dac502259ad0.jpg

好了,現在我們知道了 StackoverflowError 怎么產生的,那問題來了,既然 StackoverflowError 或者 NPE 都屬于非法訪問內存, JVM 為什么不會崩潰呢,有了上一節的鋪墊,相信你不難回答,其實就是因為 JVM 自定義了自己的信號處理函數,攔截了 SIGSEGV 信號,針對這兩者不讓它們崩潰,怎么證明這個推測呢,我們來看下 JVM 的源碼來一探究竟

openJDK 源碼解析

HotSpot 虛擬機目前使用范圍最廣的 Java 虛擬機,據 R 大所述, Oracle JDK 與 OpenJDK 里的 JVM 都是 HotSpot VM,從源碼層面說,兩者基本上是同一個東西,OpenJDK 是開源的,所以我們主要研究下 Java 8 的 OpenJDK 即可,地址如下:https://github.com/AdoptOpenJDK/openjdk-jdk8u,有興趣的可以下載來看看

我們只要研究 Linux 下的 JVM,為了便于說明,也方便大家查閱,我把其中關于信號處理的關鍵流程整理了下(忽略其中的次要代碼)

a5d9c6fc-7dff-11ed-8abf-dac502259ad0.jpg

可以看到,在啟動 JVM 的時候,也設置了信號處理函數,收到 SIGSEGV,SIGPIPE 等信號后最終會調用 JVM_handle_linux_signal 這個自定義信號處理函數,再來看下這個函數的主要邏輯

JVM_handle_linux_signal(intsig,
siginfo_t*info,
void*ucVoid,
intabort_if_unrecognized){

//MustdothisbeforeSignalHandlerMark,ifcrashprotectioninstalledwewilllongjmpaway
//這段代碼里會調用siglongjmp,主要做線程恢復之用
os::check_crash_protection(sig,t);

if(info!=NULL&&uc!=NULL&&thread!=NULL){
pc=(address)os::ucontext_get_pc(uc);

//HandleALLstackoverflowvariationshere
if(sig==SIGSEGV){
//Si_addrmaynotbevalidduetoabuginthelinux-ppc64kernel(see
//commentbelow).Useget_stack_bang_addressinsteadofsi_addr.
addressaddr=((NativeInstruction*)pc)->get_stack_bang_address(uc);

//判斷是否棧溢出了
if(addrstack_base()&&
addr>=thread->stack_base()-thread->stack_size()){
if(thread->thread_state()==_thread_in_Java){//針對棧溢出JVM的內部處理
stub=SharedRuntime::continuation_for_implicit_exception(thread,pc,SharedRuntime::STACK_OVERFLOW);
}
}
}
}

if(sig==SIGSEGV&&
!MacroAssembler::needs_explicit_null_check((intptr_t)info->si_addr)){
//此處會做空指針檢查
stub=SharedRuntime::continuation_for_implicit_exception(thread,pc,SharedRuntime::IMPLICIT_NULL);
}


//如果是棧溢出或者空指針最終會返回true,不會走最后的report_and_die,所以JVM不會退出
if(stub!=NULL){
//saveallthreadcontextincaseweneedtorestoreit
if(thread!=NULL)thread->set_saved_exception_pc(pc);

uc->uc_mcontext.gregs[REG_PC]=(greg_t)stub;
//返回true代表JVM進程不會退出
returntrue;
}

VMErrorerr(t,sig,pc,info,ucVoid);
//生成hs_err_pid_xxx.log文件并退出
err.report_and_die();

ShouldNotReachHere();
returntrue;//Mutecompiler

}

從以上代碼(注意看加粗的紅線字體部分)我們可以知道以下信息

發生 stackoverflow 還有空指針錯誤,確實都發送了 SIGSEGV,只是虛擬機不選擇退出,而是自己內部作了額外的處理,其實是恢復了線程的執行,并拋出 StackoverflowError 和 NPE,這就是為什么 JVM 不會崩潰且我們能捕獲這兩個錯誤/異常的原因

如果針對 SIGSEGV 等信號,在以上的函數中 JVM 沒有做額外的處理,那么最終會走到 report_and_die 這個方法,這個方法主要做的事情是生成 hs_err_pid_xxx.log crash 文件(記錄了一些堆棧信息或錯誤),然后退出

至此我相信大家明白了為什么發生了 StackoverflowError 和 NPE 這兩個非法訪問內存的錯誤,JVM 卻沒有崩潰。原因其實就是虛擬機內部定義了信號處理函數,而在信號處理函數中對這兩者做了額外的處理以讓 JVM 不崩潰,另一方面也可以看出如果 JVM 不對信號做額外的處理,最后會自己退出并產生 crash 文件 hs_err_pid_xxx.log(可以通過 -XX:ErrorFile=/var/log/hs_err.log 這樣的方式指定),這個文件記錄了虛擬機崩潰的重要原因,所以也可以說,虛擬機是否崩潰只要看它是否會產生此崩潰日志文件

總結

正常情況下,操作系統為了保證系統安全,所以針對非法內存訪問會發送一個 SIGSEGV 信號,而操作系統一般會調用默認的信號處理函數(一般會讓相關的進程崩潰),但如果進程覺得"罪不致死",那么它也可以選擇自定義一個信號處理函數,這樣的話它就可以做一些自定義的邏輯,比如記錄 crash 信息等有意義的事,回過頭來看為什么虛擬機會針對 StackoverflowError 和 NullPointerException 做額外處理讓線程恢復呢,針對 stackoverflow 其實它采用了一種棧回溯的方法保證線程可以一直執行下去,而捕獲空指針錯誤主要是這個錯誤實在太普遍了,為了這一個很常見的錯誤而讓 JVM 崩潰那線上的 JVM 要宕機多少次,所以出于工程健壯性的考慮,與其直接讓 JVM 崩潰倒不如讓線程起死回生,并且將這兩個錯誤/異常拋給用戶來處理。

主線程異常會導致 JVM 退出?

a6069862-7dff-11ed-8abf-dac502259ad0.jpg

有讀者讀完前面部分的文章后,問出了上面這個問題。

他認為如果 JVM 中的主線程異常沒有被捕獲,JVM 還是會崩潰,那么這個說法是否正確呢,我們做個試驗看看結果是否是他說的這樣

publicclassTest{
publicstaticvoidmain(String[]args){
TestThreadtestThread=newTestThread();
TestThread.start();
Integerp=null;
//這里會導致空指針異常
if(p.equals(2)){
System.out.println("hahaha");
}
}
}

classTestThreadextendsThread{
@Override
publicvoidrun(){
while(true){
System.out.println("test");
}
}
}

試驗很簡單,首先啟動一個線程,在這個線程里搞一個 while true 不斷打印, 然后在主線程中制造一個空指針異常,不捕獲,然后看是否會一直打印 test

結果是會不斷打印 test,說明主線程崩潰,JVM 并沒有崩潰 ,這是怎么回事, JVM 又會在什么情況下完全退出呢?

其實在 Java 中并沒有所謂主線程的概念,只是我們習慣把啟動的線程作為主線程而已,所有線程其實都是平等的,不管什么線程崩潰都不會影響到其它線程的執行,注意我們這里說的線程崩潰是指由于未 catch 住 JVM 拋出的虛擬機錯誤(VirtualMachineError)而導致的崩潰,虛擬機錯誤包括 InternalError,OutOfMemoryError,StackOverflowError,UnknownError 這四大子類

a6186286-7dff-11ed-8abf-dac502259ad0.jpg

JVM 拋出這些錯誤其實是一種防止整個進程崩潰的自我防護機制,這些錯誤其實是 JVM 內部定義了信號處理函數處理后拋出的,JVM 認為這些錯誤"罪不致死",所以選擇恢復線程再給這些線程拋錯誤(就算線程不 catch 這些錯誤也不會崩潰)的方式來避免自身崩潰,但如果線程觸發了一些其他的非法訪問內存的錯誤,JVM 則會認為這些錯誤很嚴重,從而選擇退出,比如下面這種非法訪問內存的錯誤就會被認為是致命錯誤,JVM 就不會向上層拋錯誤,而會直接選擇退出

Fieldf=Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafeunsafe=(Unsafe)f.get(null);
unsafe.putAddress(0,0);

回過頭來看,除了這些致命性錯誤導致的 JVM 崩潰,還有哪些情況會導致 JVM 退出呢,在 javadoc 上說得很清楚

a63ac682-7dff-11ed-8abf-dac502259ad0.jpg

The Java Virtual Machine exits when the only threads running are all daemon threads

也就是說只有在 JVM 的所有線程都是守護線程(daemon thread)的時候才會完全退出,什么是守護線程?守護線程其實是為其他線程服務的線程,比如垃圾回收線程就是典型的守護線程,既然是為其他線程服務的,那么一旦其他線程都不存在了,守護線程也沒有存在的意義了,于是 JVM 也就退出了,守護線程通常是 JVM 運行時幫我們創建好的,當然我們也可以自己設置,以開頭的代碼為例,在創建完 TestThread 后,調用 testThread.setDaemon(true) 方法即可將線程轉為守護線程,然后再啟動,這樣在主線程退出后,JVM 就會退出了,大家可以試試

Java 線程模型簡介

我們可以看看 Java 的線程模型,這樣大家對 JVM 的線程調度也會有一個更全面的認識,我們可以先從源碼角度看看,啟動一個 Thread 到底在 JVM 內部發生了什么,啟動源碼代碼在 Thread#start 方法中

publicclassThread{

publicsynchronizedvoidstart(){
...
start0();
...
}
privatenativevoidstart0();
}

可以看到最終會調用 start0 這個 native 方法,我們去下載一下 openJDK(地址:https://github.com/AdoptOpenJDK/openjdk-jdk8u) 來看看這個方法對應的邏輯

a65ed004-7dff-11ed-8abf-dac502259ad0.jpg

可以看到 start0 對應的是 JVM_startThread 這個方法,我們主要觀察在 Linux 下的線程啟動情況,一路追蹤下去

//jvm.cpp
JVM_ENTRY(void,JVM_StartThread(JNIEnv*env,jobjectjthread))
native_thread=newJavaThread(&thread_entry,sz);

//thread.cpp
JavaThread::JavaThread(ThreadFunctionentry_point,size_tstack_sz)
{
os::create_thread(this,thr_type,stack_sz);
}

//os_linux.cpp
boolos::create_thread(Thread*thread,ThreadTypethr_type,size_tstack_size){
intret=pthread_create(&tid,&attr,(void*(*)(void*))java_start,thread);
}

可以看到最終是通過調用 pthread_create 來啟動線程的,這個方法是一個 C 函數庫實現的創建 native thread 的接口,是一個系統調用,由此可見 pthread_create 最終會創建一個 native thread,這個線程也叫內核線程 ,操作系統只能調度內核線程,于是我們知道了在 Java 中,Java 線程和內核線程是一對一的關系,Java 線程調度實際上是通過操作系統調度實現的,這種一對一的線程也叫 NPTL(Native POSIX Thread Library) 模型,如下

a68acf9c-7dff-11ed-8abf-dac502259ad0.jpg

NPTL線程模型

那么這個內核線程在內核中又是怎么表示的呢, 其實在 Linux 中不管是進程還是線程都是通過一個 task_struct 的結構體來表示的, 這個結構體定義了進程需要的虛擬地址,文件描述符,寄存器,信號等資源

早期沒有線程的概念,所以每次啟動一個進程都需要調用 fork 創建進程,這個 fork 干的事其實就是 copy 父進程對應的 task_struct 的多數字段(pid 等除外),這在性能上顯然是無法接受的。于是線程的概念被提出來了,線程除了有自己的棧和寄存器外,其他像虛擬地址,文件描述符等資源都可以共享

a6c49ca4-7dff-11ed-8abf-dac502259ad0.jpg

線程共享代碼段,數據段,地址空間,文件等資源

于是針對線程,我們就可以指定在創建 task_struct 時,采用共享 而不是復制字段的方式。其實不管是創建進程(fork)還是創建線程(pthread_create)最終都會通過調用 clone() 的形式來創建 task_struct,只不過 pthread_create 在調用 clone 時,指定了如下幾個共享參數

clone(CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND,0);

畫外音 :CLONE_VM 共享頁表,CLONE_FS 共享文件系統信息,CLONE_FILES 共享文件句柄,CLONE_SIGHAND 共享信號

通過共享而不是復制資源的形式極大地加快了線程的創建,另外線程的調度開銷也會更小,比如在(同一進程內)線程間切換的時候由于共享了虛擬地址空間,TLB 不會被刷新從而導致內存訪問低效的問題

提到這相信你已經明白了教科書上的一句話:進程是資源分配的最小單元,而線程是程序執行和調度的最小單位。在 Linux 中進程分配資源后,線程通過共享資源的方式來被調度得以提升線程的執行效率

由此可見,在 Linux 中所有的進程/線程都是用的 task_struct,它們之間其實是平等的 ,那怎么表示這些線程屬于同一個進程的概念呢,畢竟線程之間也是要通信的,一組線程以及它們所共同引用的一組資源就是一個進程。, 它們還必須被視為一個整體。

task_struct 中引入了線程組的概念,如果線程都是由同一個進程(即我們說的主線程)產生的, 那么它們的 tgid(線程組id) 是一樣的,如果是主線程,則 pid = tgid,如果是主線程創建的線程,則這些線程的 tgid 會與主線程的 tgid 一致,

那么在 LInux 中進程,進程內的線程之間是如何通信或者管理的呢,其實 NPTL 是一種實現了 POSIX Thread 的標準 ,所以我們只需要看 POSIX Thread 的標準即可,以下列出了 POSIX Thread 的主要標準:

查看進程列表的時候, 相關的一組 task_struct 應當被展現為列表中的一個節點(即進程內如果有多個線程,展示進程列表 ps -ef 時只會展示主線程,如果要查看線程的話可以用 ps -T)

發送給這個進程的信號(對應 kill 系統調用), 將被對應的這一組 task_struct 所共享, 并且被其中的任意一個”線程”處理

發送給某個線程的信號(對應 pthread_kill), 將只被對應的一個 task_struct 接收, 并且由它自己來處理

當進程被停止或繼續時(對應 SIGSTOP/SIGCONT 信號), 對應的這一組 task_struct 狀態將改變

當進程收到一個致命信號(比如由于段錯誤收到 SIGSEGV 信號), 對應的這一組 task_struct 將全部退出

畫外音 : POSIX 即可移植操作系統接口(Portable Operating System Interface of UNIX,縮寫為 POSIX ),是一種接口規范,如果系統都遵循這個標準,可以做到源碼級的遷移,這就類似 Java 中的針對接口編程

這樣就能很好地滿足進程退出線程也退出,或者線程間通信等要求了

NPTL 模型的缺點

NPTL 是一種非常高效的模型,研究表明 NPTL 能夠成功地在 IA-32 平臺上在兩秒內生成 100,000 個線程,而 2.6 之前未采用 NPTL 的內核則需耗費 15 分鐘左右,看起來 NPTL 確實很好地滿足了我們的需求,但針對內核線程來調度其實還是有以下問題

不管是進程還是線程,每次阻塞、切換都需要陷入系統調用(system call),系統調用開銷其實挺大的,包括上下文切換(寄存器切換),特權模式切換等,而且還得先讓 CPU 跑操作系統的調度程序,然后再由調度程序決定該跑哪一個進程(線程)

不管是進程還是線程,都屬于搶占式調度(高優先級線進程優先被調度),由于搶占式調度執行順序無法確定的特點,使用線程時需要非常小心地處理同步問題

線程雖然更輕量級,但這只是相對于進程而言,實際上使用線程所消耗的資源依然很大,比如在 linux 上,一個線程默認的棧大小是1M,創建幾萬個線程就吃不消了

協程

NPTL 模型其實已經足夠優秀了,上述問題本質上其實還是因為線程還是太“重”所致,那能否再在線程上抽出一個更輕量級的執行單元(可被 CPU 調度和分派的基本單位)呢,答案是肯定的,在線程之上我們可以再抽象出一個協程(coroutine)的概念,就像進程是由線程來調度的,同樣線程也可以細化成一個個的協程來調度

a6db1ff6-7dff-11ed-8abf-dac502259ad0.jpg

針對以上問題,協程都做了非常好的處理

協程的調度處于用戶態,也就沒有了系統調用這些開銷

協程不屬于搶占式調度,而是協作式調度,如何調度,在什么時間讓出執行權給其它協程是由用戶自己決定的,這樣的話同步的問題也基本不存在,可以認為協程是無鎖的,所以性能很高

我們可以認為線程的執行是由一個個協程組成的,協程是更輕量的存在,內存使用大約只有線程的十分之一甚至是幾十分之一,它是使用棧內存按需使用的,所以創建百萬級的協程是非常輕松的事

協程是怎么做到上述這些的呢

協程(coroutine)可以分為兩個角度來看,一個是 routine 即執行單元,一個是 co 即 cooperative 協作,也就是說線程可以依次順序執行各個協程,但協程與線程不同之處在于,如果某個協程(假設為 A)內碰到了 IO 等阻塞事件,可以主動讓出自己的調度權,即掛起(suspend),轉而執行其他協程,等 IO 事件準備好了,再來調度協程 A

a6f459f8-7dff-11ed-8abf-dac502259ad0.jpg

這就好比我在看電視的時候碰到廣告,那我可以先去倒杯水,等廣告播完了再回來繼續看電視。而如果是函數,那你必須看完廣告再去倒水,顯然協程的效率更高。那么協程之間是怎么協作的呢,我們可以在兩個協程之間碰到 IO 等阻塞事件時隨時將自己掛起(yield),然后喚醒(resume)對方以讓對方執行,想象一下如果協程中有挺多 IO 等阻塞事件時,那這種協作調度是非常方便的

a7159da2-7dff-11ed-8abf-dac502259ad0.jpg兩個協程之間的“協作”

不像函數必須執行完才能返回,協程可以在執行流中的任意位置 由用戶決定掛起和喚醒,無疑協程是更方便的

a72c21c6-7dff-11ed-8abf-dac502259ad0.jpg

函數與協程的區別

更重要的一點是不像線程的掛起和喚醒等調度必須通過系統調用來讓內核調度器來調度,協程的掛起和喚醒完全是由用戶決定的 ,而且這個調度是在用戶態,幾乎沒有開銷!

前面我們一直提到一般我們在協程中碰到 IO 等阻塞事件時才會掛起并喚醒其他協程,所以可知協程非常適合 IO 密集型的應用 ,如果是計算密集型其實用線程反而更加合適

為什么 Go 語言這么最近這么火,一個很重要的原因就是因為因為它天生支持協程,可以輕而易舉地創建成千上萬個協程,而如果是創建線程的話,創建幾百個估計就夠嗆了,不過比較遺憾的是 Java 原生并不支持協程,只能通過一些第三方庫如 Quasar 來實現,2018 年 OpenJDK 官方創建了一個 loom 項目來推進協程的官方支持工作

總結

從進程,到線程再到協程,可知我們一直在想辦法讓執行單元變得更輕量級,一開始只有進程的概念,但是進程的創建在 Linux 下需要調用 fork 全部復制一遍資源,雖然后來引入了寫時復制的概念,但進程的創建開銷依然很大,于是提出了更輕量級的線程,在 Linux 中線程與進程其實都是用 task_struct 表示的,只是線程采用了共享資源的方式來創建,極大了提升了 task_struct 的創建與調度效率,但人們發現,線程的阻塞,喚醒都要通過系統調用陷入內核態才能被調度程度調度,如果線程頻繁切換,開銷無疑是很大的,于是人們提出了協程的概念,協程是根據棧內存按需求分配的,所需開銷是線程的幾十分之一,非常的輕量,而且調度是在用戶態,并且它是協作式調度,可以很方便的掛起恢復其他協程的執行,在此期間,線程是不會被掛起的,所以無論是創建還是調度開銷都很小,目前 Java 官方還不支持,不過支持協程應該是大勢所趨,未來我們可以期待一下。






審核編輯:劉清

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • cpu
    cpu
    +關注

    關注

    68

    文章

    10879

    瀏覽量

    212189
  • JVM
    JVM
    +關注

    關注

    0

    文章

    158

    瀏覽量

    12238
  • openjdk
    +關注

    關注

    0

    文章

    8

    瀏覽量

    2322

原文標題:美團一面:為什么線程崩潰崩潰不會導致 JVM 崩潰

文章出處:【微信號:芋道源碼,微信公眾號:芋道源碼】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    Jvm的整體結構和特點

    的代碼等數據。  堆區  所有線程共享的一塊內存區域,虛擬機啟動時被創建用來存放對象實例?! ?b class='flag-5'>JVM?! 】梢詤⒖剂私鈼5臄祿Y構,存放Java方法執行的內存模型,Java開發中,一個功能實現需要
    發表于 01-05 17:23

    看看基于JDK中自帶JVM工具的用法

    用的手段;輕松解決開發:由于經驗不足,程序出現重大BUG導致JVM異常,進而引起系列的連鎖反應,這種不會絕地反彈,只有一地雞毛;解決常規的JVM
    發表于 11-16 15:30

    Jvm工作原理學習筆記

    JVM實例對應了一個獨立運行的java程序它是進程級別 a) 啟動。啟動一個Java程序時,一個JVM實例就產生了,任何一個擁有public static void main(String
    發表于 04-03 11:03 ?5次下載

    如何解決JVM中一個極小概率發生的bug

    到 CMS 代碼存在 bug,導致 JVM 弱內存模型的平臺上 Crash。分析過程中,涉及到 CMS 垃圾回收原理、內存屏障、對象頭、以及 ParNew 并行回收算法中多個
    的頭像 發表于 08-23 17:35 ?3442次閱讀

    如何解決JVM解釋器導致應用崩潰的bug

    編者按:筆者遇到一個非常典型的問題,應用在 X86 正常運行, AArch64 上 JVM 就會崩潰。這個典型的 JVM 內部問題。筆者通過分析最終定位到是由于
    的頭像 發表于 08-27 09:58 ?2453次閱讀
    如何解決<b class='flag-5'>JVM</b>解釋器<b class='flag-5'>導致</b>應用<b class='flag-5'>崩潰</b>的bug

    OOM會導致JVM虛擬機退出嗎

    OutOfMemoryError (OOM)。 這種錯誤是 Error 的一個子類,通常表示某種無法恢復的問題。 回到主題,先說下結論: OutOfMemoryError 本身不會直接導致JVM退出,但由于其
    的頭像 發表于 09-30 10:14 ?809次閱讀

    jvm的dump太大了怎么分析

    分析大型JVM dump文件可能會遇到的一些挑戰。首先,JVM dump文件通常非常大,可能幾百MB或幾個GB。這是因為它們包含了JVM的完整內存快照,包括堆和棧的所有對象和線程信息。
    的頭像 發表于 12-05 11:01 ?2732次閱讀

    jvm內存模型和內存結構

    JVM(Java虛擬機)是Java程序的運行平臺,它負責將Java程序轉換成機器碼并在計算機上執行。JVM中,內存模型和內存結構是兩個重要的概念,本文將詳細介紹它們。 一、JVM內存
    的頭像 發表于 12-05 11:08 ?946次閱讀

    什么場景需要jvm調優

    ,如果JVM的性能不夠優越,可能會導致應用程序的性能下降甚至崩潰。此時需要對JVM進行調優,以提高應用程序的并發處理能力。例如,調整線程池的
    的頭像 發表于 12-05 11:14 ?1450次閱讀

    jvm調優參數

    JVM(Java虛擬機)是Java程序的運行環境,它負責解釋Java字節碼并執行相應的指令。為了提高應用程序的性能和穩定性,我們可以調優JVM的參數。 JVM調優主要涉及到堆內存、垃圾收集器、
    的頭像 發表于 12-05 11:29 ?653次閱讀

    jvm參數的設置和jvm調優

    JVM(Java虛擬機)參數的設置和調優對于提高Java應用程序的性能和穩定性非常重要。本文中,我們將詳細介紹JVM參數的設置和調優方法。 一、JVM參數的設置 內存參數: -Xms
    的頭像 發表于 12-05 11:36 ?1591次閱讀

    jvm調優工具有哪些

    、基于GUI的監控和故障排查工具,提供了對JVM各種資源的可視化監控和分析,例如CPU使用率、內存使用情況、線程狀態等??梢酝ㄟ^JMX(Java Management Extensions)來連接和監控
    的頭像 發表于 12-05 11:44 ?1096次閱讀

    jvm哪些區域會發生oom

    of Memory,OOM),本文將詳細介紹 JVM 內容可能發生 OOM 的區域。OOM 是指應用程序申請分配內存時,沒有足夠的內存供其使用,導致程序無法正常執行。 堆(Heap)區域: 堆是
    的頭像 發表于 12-05 11:51 ?1423次閱讀

    weblogic jvm參數配置

    WebLogic中,JVM參數配置是非常重要的,它可以對應用程序的性能和穩定性產生直接影響。JVM參數通過調整Java虛擬機的運行時行為,可以優化內存管理、垃圾回收以及線程管理等方面
    的頭像 發表于 12-05 14:31 ?1472次閱讀

    eclipse設置jvm內存大小

    內存大小,并對其背后的原理進行解釋。 JVM(Java虛擬機)是Java程序的運行環境,它負責將Java字節碼翻譯成機器碼,以便在不同的平臺上執行。JVM使用內存來存儲運行時對象和執行過程中的臨時數據。如果JVM的內存不足,就會
    的頭像 發表于 12-06 11:43 ?1917次閱讀
    主站蜘蛛池模板: 免费毛片播放| 国产日韩亚洲精品视频| 国产亚洲综合视频| 欧美色图天堂网| 坠落的丝袜美人妻| 国产在线伊人| 天堂Av亚洲欧美日韩国产综合| 99视频偷窥在线精品国自产拍| 久久精品国产欧美日韩99热| 性女传奇快播| 动漫美女搞鸡| 欧美一夜爽爽爽爽爽爽| 18av 在线| 久久精品无码成人国产毛| 亚洲精品乱码8久久久久久日本| 国产99r视频精品免费观看| 青娱乐国产精品视频| 99麻豆精品国产人妻无码| 久久久黄色片| 一级特黄视频| 精品视频中文字幕| 亚洲精品视频区| 国产在线精品亚洲第1页| 小SAO货叫大声点妓女| 国产精品久久久久久影院| 善良的小峓子2在钱免费中文字| 岛国片免费看| 三级黄色在线| 芳草地社区在线视频| 日本真人啪啪试看30秒| JEALOUSVUE成熟老师APP| 女仆翻身大作战| 99re久久免费热在线视频手机| 狂野欧美性猛XXXX乱大交| 中文字幕欧美一区| 天美传媒MV高清免费看| 国产一区二区精品视频| 秋霞网站一级一片| 欧美高清videos 360p| 中文字幕成人免费高清在线| 久久艹伊人|