广告
返回顶部
首页 > 资讯 > 精选 >线程崩溃不会导致JVM崩溃的原因是什么
  • 112
分享到

线程崩溃不会导致JVM崩溃的原因是什么

2023-07-02 10:07:26 112人浏览 八月长安
摘要

本文小编为大家详细介绍“线程崩溃不会导致JVM崩溃的原因是什么”,内容详细,步骤清晰,细节处理妥当,希望这篇“线程崩溃不会导致JVM崩溃的原因是什么”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。线程崩溃,进程一定

本文小编为大家详细介绍“线程崩溃不会导致JVM崩溃的原因是什么”,内容详细,步骤清晰,细节处理妥当,希望这篇“线程崩溃不会导致JVM崩溃的原因是什么”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。

线程崩溃,进程一定会崩溃吗

一般来说如果线程是因为非法访问内存引起的崩溃,那么进程肯定会崩溃,为什么系统要让进程崩溃呢,这主要是因为在进程中,各个线程的地址空间是共享的,既然是共享,那么某个线程对地址的非法访问就会导致内存的不确定性,进而可能会影响到其他线程,这种操作是危险的,操作系统会认为这很可能导致一系列严重的后果,于是干脆让整个进程崩溃

线程崩溃不会导致JVM崩溃的原因是什么

线程共享代码段,数据段,地址空间,文件

非法访问内存有以下几种情况,我们以 C 语言举例来看看

针对只读内存写入数据

#include <stdio.h>#include <stdlib.h>int main() {   char *s = "hello world"; s[1] = 'H'; // 向只读内存写入数据,崩溃}

针对没有权限的内存非法操作

#include <stdio.h>#include <stdlib.h>int main() {   int *p = (int *)0xC0000fff; *p = 10; // 针对进程没有写权限的内核空间写入数据,崩溃}

在 32 位虚拟地址空间中,p 指向的是内核空间,显然不具有写入权限,所以上述赋值操作会导致崩溃

访问了不存在的内存,比如

#include <stdio.h>#include <stdlib.h>int main() {   int *a = NULL;   *a = 1;     // 访问了不存在的内存}

以上错误都是访问内存时的错误,所以统一会报 Segment Fault 错误(即段错误),这些都会导致进程崩溃

进程是如何崩溃的-信号机制简介

那么线程崩溃后,进程是如何崩溃的呢,这背后的机制到底是怎样的,答案是信号,大家想想要干掉一个正在运行的进程是不是经常用 kill -9 pid 这样的命令,这里的 kill 其实就是给指定 pid 发送终止信号的意思,其中的 9 就是信号,其实信号有很多类型的,在 linux 中可以通过 kill -l查看所有可用的信号

线程崩溃不会导致JVM崩溃的原因是什么

当然了发 kill 信号必须具有一定的权限,否则任意进程都可以通过发信号来终止其他进程,那显然是不合理的,实际上 kill 执行的是系统调用,将控制权转移给了内核(操作系统),由内核来给指定的进程发送信号

那么发个信号进程怎么就崩溃了呢,这背后的原理到底是怎样的?

其背后的机制如下

  • CPU 执行正常的进程指令

  • 调用 kill 系统调用向进程发送信号

  • 进程收到操作系统发的信号,CPU 暂停当前程序运行,并将控制权转交给操作系统

  • 调用 kill 系统调用向进程发送信号(假设为 11,即 SIGSEGV,一般非法访问内存报的都是这个错误)

  • 操作系统根据情况执行相应的信号处理程序(函数),一般执行完信号处理程序逻辑后会让进程退出

注意上面的第五步,如果进程没有注册自己的信号处理函数,那么操作系统会执行默认的信号处理程序(一般最后会让进程退出),但如果注册了,则会执行自己的信号处理函数,这样的话就给了进程一个垂死挣扎的机会,它收到 kill 信号后,可以调用 exit() 来退出,但也可以使用 sigsetjmp,siglongjmp 这两个函数来恢复进程的执行

// 自定义信号处理函数示例#include <stdio.h>#include <signal.h>#include <stdlib.h>// 自定义信号处理函数,处理自定义逻辑后再调用 exit 退出void sigHandler(int sig) {  printf("Signal %d catched!\n", sig);  exit(sig);}int main(void) {  signal(SIGSEGV, sigHandler);  int *p = (int *)0xC0000fff;  *p = 10; // 针对不属于进程的内核空间写入数据,崩溃}// 以上结果输出: Signal 11 catched!

如代码所示:注册信号处理函数后,当收到 SIGSEGV 信号后,先执行相关的逻辑再退出

另外当进程接收信号之后也可以不定义自己的信号处理函数,而是选择忽略信号,如下

#include <stdio.h>#include <signal.h>#include <stdlib.h>int main(void) {  // 忽略信号  signal(SIGSEGV, SIG_IGN);  // 产生一个 SIGSEGV 信号  raise(SIGSEGV);  printf("正常结束");}

也就是说虽然给进程发送了 kill 信号,但如果进程自己定义了信号处理函数或者无视信号就有机会逃出生天,当然了 kill -9 命令例外,不管进程是否定义了信号处理函数,都会马上被干掉

说到这大家是否想起了一道经典面试题:如何让正在运行的 Java 工程的优雅停机,通过上面的介绍大家不难发现,其实是 JVM 自己定义了信号处理函数,这样当发送 kill pid 命令(默认会传 15 也就是 SIGTERM)后,JVM 就可以在信号处理函数中执行一些资源清理之后再调用 exit 退出。这种场景显然不能用 kill -9,不然一下把进程干掉了资源就来不及清除了

为什么线程崩溃不会导致 JVM 进程崩溃

现在我们再来看看开头这个问题,相信你多少会心中有数,想想看在 Java 中有哪些是觉见的由于非法访问内存而产生的 Exception 或 error 呢,常见的是大家熟悉的 StackoverflowError 或者 NPE(NullPointerException),NPE 我们都了解,属于是访问了不存在的内存

但为什么栈溢出(Stackoverflow)也属于非法访问内存呢,这得简单聊一下进程的虚拟空间,也就是前面提到的共享地址空间

现代操作系统为了保护进程之间不受影响,所以使用了虚拟地址空间来隔离进程,进程的寻址都是针对虚拟地址,每个进程的虚拟空间都是一样的,而线程会共用进程的地址空间,以 32 位虚拟空间,进程的虚拟空间分布如下

线程崩溃不会导致JVM崩溃的原因是什么

那么 stackoverflow 是怎么发生的呢,进程每调用一个函数,都会分配一个栈桢,然后在栈桢里会分配函数里定义的各种局部变量,假设现在调用了一个无限递归的函数,那就会持续分配栈帧,但 stack 的大小是有限的(Linux 中默认为 8 M,可以通过 ulimit -a 查看),如果无限递归显然很快栈就会分配完了,此时再调用函数试图分配超出栈的大小内存,就会发生段错误,也就是 stackoverflowError

线程崩溃不会导致JVM崩溃的原因是什么

好了,现在我们知道了 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,为了便于说明,也方便大家查阅,我把其中关于信号处理的关键流程整理了下(忽略其中的次要代码)

线程崩溃不会导致JVM崩溃的原因是什么

可以看到,在启动 JVM 的时候,也设置了信号处理函数,收到 SIGSEGV,SIGPIPE 等信号后最终会调用 JVM_handle_linux_signal 这个自定义信号处理函数,再来看下这个函数的主要逻辑

JVM_handle_linux_signal(int sig,                        siginfo_t* info,                        void* ucVoid,                        int abort_if_unrecognized) {   // Must do this before SignalHandlerMark, if crash protection installed we will longjmp away  // 这段代码里会调用 siglongjmp,主要做线程恢复之用  os::ThreadCrashProtection::check_crash_protection(sig, t);  if (info != NULL && uc != NULL && thread != NULL) {    pc = (address) os::Linux::ucontext_get_pc(uc);    // Handle ALL stack overflow variations here    if (sig == SIGSEGV) {      // Si_addr may not be valid due to a bug in the linux-ppc64 kernel (see      // comment below). Use get_stack_bang_address instead of si_addr.      address addr = ((NativeInstruction*)pc)->get_stack_bang_address(uc);      // 判断是否栈溢出了      if (addr < thread->stack_base() &&          addr >= thread->stack_base() - thread->stack_size()) {        if (thread->thread_state() == _thread_in_Java) {            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) {    // save all thread context in case we need to restore it    if (thread != NULL) thread->set_saved_exception_pc(pc);    uc->uc_mcontext.gregs[REG_PC] = (greg_t)stub;    // 返回 true 代表 JVM 进程不会退出    return true;  }  VMError err(t, sig, pc, info, ucVoid);  // 生成 hs_err_pid_xxx.log 文件并退出  err.report_and_die();  ShouldNotReachHere();  return true; // Mute compiler}

从以上代码我们可以知道以下信息

  • 发生 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 这样的方式指定),这个文件记录了虚拟机崩溃的重要原因,所以也可以说,虚拟机是否崩溃只要看它是否会产生此崩溃日志文件

读到这里,这篇“线程崩溃不会导致JVM崩溃的原因是什么”文章已经介绍完毕,想要掌握这篇文章的知识点还需要大家自己动手实践使用过才能领会,如果想了解更多相关内容的文章,欢迎关注编程网精选频道。

--结束END--

本文标题: 线程崩溃不会导致JVM崩溃的原因是什么

本文链接: https://www.lsjlt.com/news/341168.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 线程崩溃不会导致JVM崩溃的原因是什么
    本文小编为大家详细介绍“线程崩溃不会导致JVM崩溃的原因是什么”,内容详细,步骤清晰,细节处理妥当,希望这篇“线程崩溃不会导致JVM崩溃的原因是什么”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。线程崩溃,进程一定...
    99+
    2023-07-02
  • 线程崩溃不会导致 JVM 崩溃的原因解析
    目录线程崩溃,进程一定会崩溃吗进程是如何崩溃的-信号机制简介为什么线程崩溃不会导致 JVM 进程崩溃openJDK 源码解析总结参考文章网上看到一个很有意思的据说是美团的面试题:为什...
    99+
    2022-11-13
  • Linux内核崩溃崩溃的原因是什么
    今天就跟大家聊聊有关Linux内核崩溃崩溃的原因是什么,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。 原理Linux内核发送崩溃时,kdump会生成一个内核转储文件vmco...
    99+
    2023-06-15
  • EXCEPTION_ACCESS_VIOLATION 崩溃的可能原因是什么
    EXCEPTION_ACCESS_VIOLATION 异常通常是由程序尝试访问未分配或受保护的内存区域引起的。可能的原因包括:1. ...
    99+
    2023-09-27
    崩溃
  • APP崩溃的主要原因是什么
    这篇文章跟大家分析一下“APP崩溃的主要原因是什么”。内容详细易懂,对“APP崩溃的主要原因是什么”感兴趣的朋友可以跟着小编的思路慢慢深入来阅读一下,希望阅读后能够对大家有所帮助。下面跟着小编一起深入学习“APP崩溃的主要原因是什么”的知识...
    99+
    2023-06-04
  • 服务器系统崩溃的原因是什么
    服务器系统崩溃的原因:1、网站服务器的网站访问量突出暴增,超出服务器承受能力导致;2、网站服务器自身硬件配置空间不足导致;3、网站服务器的连接数量超载导致;4、网站服务器遭受黑客入侵或网络恶意攻击破坏导致。具体内容如下:访问峰值或请求超过服...
    99+
    2022-10-18
  • App崩溃的6个常见原因是什么
    这篇文章主要为大家分析了App崩溃的6个常见原因是什么的相关知识点,内容详细易懂,操作细节合理,具有一定参考价值。如果感兴趣的话,不妨跟着跟随小编一起来看看,下面跟着小编一起深入学习“App崩溃的6个常见原因是什么”的知识吧。人们讨厌应用程...
    99+
    2023-06-04
  • win10游戏崩溃的原因及解决方法是什么
    Win10游戏崩溃的原因可能有很多,包括以下几点:1. 硬件问题:游戏过于占用系统资源,导致硬件性能不足而崩溃。解决方法:升级硬件,...
    99+
    2023-08-30
    win10
  • 香港阿里云服务器崩溃原因是什么引起的呢
    定期更新操作系统,确保系统的安全性; 配置更加强大的服务器硬件,如增加内存、处理器等; 增加服务器的网络带宽,确保网络的顺畅; 确保服务器的网络连接稳定,如设置正确的网络参数、优化网络速度等; 定期检查服务器的硬件配置,及时更换老旧的硬件...
    99+
    2023-10-27
    阿里 香港 原因
  • MySQL数据库崩溃的常见原因和解决方法是什么
    这篇“MySQL数据库崩溃的常见原因和解决方法是什么”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“MySQL数据库崩溃的常见...
    99+
    2023-07-05
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作