当我们递归调用Java方法时,很可能会出现StackOverflowError,我们会认为此时栈内存溢出了,那么这个栈内存溢出虚拟机是如何检测的呢?
是累加分配的内存与栈大小进行比较,还是有更好的方式呢?
更多问答 >>
-
2024-06-17 09:16
-
2024-06-06 11:06
-
2023-10-25 00:22
-
每日一问 | targetSdkVersion 有什么神奇的魔力?
2023-05-24 17:30 -
每日一问 | Android 模块化依赖中的资源冲突该如何规避?
2023-05-24 17:30 -
每日一问 | Android 默认开启硬件加速与设置hardwareAccelerated是一回事吗?
2023-05-24 17:30 -
每日一问 | 瘦身属性?对android:extractNativeLibs属性的探讨
2022-12-07 14:43 -
每日一问 | App在运行状态,可以动态安装apk,并且不重新启动吗?
2022-11-20 12:46
洋神,你真的好久没搞每日一问了啊……
本回答介绍的是 Android 上 ART 的处理方式。想要回答这个问题,首先我们需要知道几个预备知识:
首先可以来看看解释执行的情况,随便点开一个函数:
http://aospxref.com/android-13.0.0_r3/xref/art/runtime/interpreter/interpreter.cc?fi=EnterInterpreterFromInvoke#EnterInterpreterFromInvoke第一眼就能看见一个 if,如果这个 if 的条件成立就会抛出 StackOverflowError。__builtin_frame_address 是 gcc 内置的获取方法调用信息的函数,这里获取的是“当前函数的栈帧地址”,如果小于某个值就会认为发生栈溢出,抛出 StackOverflowError。为什么是小于?因为栈是向下增长的。接着看看函数被编译后是怎么检查的。用
cmd package compile -m everything -f 包名
这条命令强制编译,然后把 oat 拿出来,用 oatdump 反汇编看看。随便点开一个函数(因为我刚好在一台 arm 机子上用了 oatdump ,直接复制过来了,所以这里是 Thumb-2 指令集):随便看几个函数,你会发现基本上每个函数的开头都有这几条固定指令。为什么?
这两条指令里,第一条把 sp 寄存器也就是所谓的栈帧指针减去 8192,赋值给了 r12 寄存器;第二条读取了从 r12 寄存器指向的内存的值,赋值给 r12 寄存器。由于栈向下增长,这里 sp - 8192 指向的内存应该没有被实际分配所以无意义,检查后续指令也会发现这个读取到的值根本没有用到,为什么要多此一举呢?其实这里就是在检查栈有没有溢出。栈是一段连续的内存空间,提前使用 mmap 分配,当栈(快要)溢出的时候,sp - 8192 这个内存地址就会超出这段提前分配的内存空间,接下来尝试访问这个地址指向的内存就会触发段错误,从而导致程序中断执行跳转到 art 提前设置好的 signal handler 中。art 的 signal handler 会检测出这种情况,并抛出 StackOverflowError。http://aospxref.com/android-13.0.0_r3/xref/art/runtime/arch/arm64/fault_handler_arm64.cc#142哈哈,主要有的没人回答,我就一直等啊等就变懒了~~ 后面只要有回答,当周我就准备下一问~
至于为什么是8192,ART虚拟机为每个Java线程保存了2个页大小的reserved页用于栈溢出后的异常收集,为了reserved的页不被占用,所以每个函数开始前,就sp-0x2000; ...查看更多
至于为什么是8192,ART虚拟机为每个Java线程保存了2个页大小的reserved页用于栈溢出后的异常收集,为了reserved的页不被占用,所以每个函数开始前,就sp-0x2000;
每天一个面试小技巧
是这里面提到的是编译执行模式下的栈溢出检测,那解释执行模式下的栈溢出检测是怎么样的呢?
解释执行是手动 if 来检测的,可以参考第一段列出来链接,点进去看代码
这里的8192是一个经验值,一般情况下,栈的大小为8KB,所以这里减去8192是为了获取栈底的地址。如果当前函数的栈帧地址小于栈底的地址,就说明栈已经溢出了,需要抛出StackOverflowErro ...查看更多
这里的8192是一个经验值,一般情况下,栈的大小为8KB,所以这里减去8192是为了获取栈底的地址。如果当前函数的栈帧地址小于栈底的地址,就说明栈已经溢出了,需要抛出StackOverflowError
楼上回答挺好的,这里再补充JNI场景,当调用一个JNI方法,比如获取methodid 调用,其实就会调用ArtMethod::Invoke方法,这里面在判断是否解释运行之前,也会判断是否触达了当前线程的栈低。其他ArtMethod场景也会判断
gpt:
Java线程栈的栈溢出(StackOverflowError)是通过递归调用或方法调用链过长而导致的。Java虚拟机在执行方法时,会为每个线程分配一定大小的栈空间用于存储方法调用和局部变量等信息。当方法调用嵌套或递归调用过多,栈空间被耗尽时,就会抛出栈溢出异常。Java虚拟机在运行时会检测线程栈的溢出情况,一旦检测到栈溢出,就会抛出StackOverflowError异常。虚拟机通过不断检查栈空间的使用情况,例如当前栈帧的大小、栈帧的数量等来进行检测。当栈空间超过了其分配的限制,就会触发栈溢出异常。
栈溢出检测通常是由虚拟机的运行时系统负责完成的。它可能依赖于操作系统提供的一些底层机制,例如页面异常、内存访问异常等。当检测到栈溢出时,虚拟机会抛出异常并终止当前线程的执行。
值得注意的是,栈溢出异常是一种严重的错误,通常很难恢复或处理。因此,在编写代码时,应避免出现过多的递归调用或方法调用链过长,以防止栈溢出异常的发生。