JVM中stack和程序寄存器pc信息获取和分析

本文介绍了如何使用jstack工具获取Java线程的堆栈信息,包括Java帧和本地帧。内容涉及jstack命令的用法,解析了Java方法和本地方法在程序寄存器PC中的表示,强调程序计数器作为字节码行号指示器的角色。文章指出,执行Java方法时,PC存储字节码指令地址,而执行本地方法时,PC值为空。

一、官方解读:

jstack prints Java stack traces of Java threads for a given Java process or core file or a remote debug server.
For each Java frame, the full class name, method name, 'bci' (byte code index) and line number, if available, are printed.
With the -m option, jstack prints both Java and native frames of all threads along with the 'pc' (program counter).
采用jstack默认命令,仅输出Java frame(Java帧),对于每一个java frame,如果可用,完整类名、方法名、字节码索引(bci)和行号都会被输出;
采用附带-m参数,jstack会将所有线程的java frame和native frame信息都打印出来,并且附带PC信息输出

二、获取命令:

jstack <pid> => 

"ContainerBackgroundProcessor[StandardEngine[Tomcat]]" #18 daemon prio=5 os_prio=0 tid=0x00007fb028f26000 nid=0xc58 waiting on condition [0x00007fafc0376000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
	at java.lang.Thread.sleep(Native Method)
	at org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor.run(ContainerBase.java:1357)
	at java.lang.Thread.run(Thread.java:745)

jstack -m <pid> =>

>>>Interpreted Frame

----------------- 3160 -----------------
0x00007fb02e6ced12	__pthread_cond_timedwait + 0x132
0x00007fb02d819d63	_ZN2os5sleepEP6Threadlb + 0x283
0x00007fb02d61b8d2	JVM_Sleep + 0x3b2
0x00007fb019b67c31	<Unknown compiled code>
0x00007fb01900798d	* org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor.run() bci:46 line:1357 (Interpreted frame)
0x00007fb0190079d2	* java.lang.Thread.run() bci:11 line:745 (Interpreted frame)
0x00007fb0190004e7	<StubRoutines>
0x00007fb02d588616	_ZN9JavaCalls11call_helperEP9JavaValueP12methodHandleP17JavaCallArgumentsP6Thread + 0x1056
0x00007fb02d588b21	_ZN9JavaCalls12call_virtualEP9JavaValue11KlassHandleP6SymbolS4_P17JavaCallArgumentsP6Thread + 0x321
0x00007fb02d588fc7	_ZN9JavaCalls12call_virtualEP9JavaValue6Handle11KlassHandleP6SymbolS5_P6Thread + 0x47
0x00007fb02d61fd80	_ZL12thread_entryP10JavaThreadP6Thread + 0xa0
0x00007fb02d965dcf	_ZN10JavaThread17thread_main_innerEv + 0xdf
0x00007fb02d965efc	_ZN10JavaThread3runEv + 0x11c
0x00007fb02d8199d8	_ZL10java_startP6Thread + 0x108

>>>Compiled Frame

----------------- 3263 -----------------
0x00007fb02e6ced12	__pthread_cond_timedwait + 0x132
0x00007fb02d80464f	_ZN13ObjectMonitor4waitElbP6Thread + 0x27f
0x00007fb02d623e65	JVM_MonitorWait + 0x1a5
0x00007fb01a3b2a68	<Unknown compiled code>
0x00007fb01addd9bc	* java.util.TimerThread.mainLoop() bci:201 line:552 (Compiled frame)
* java.util.TimerThread.run() bci:1 line:505 (Interpreted frame)
0x00007fb0190004e7	<StubRoutines>
0x00007fb02d588616	_ZN9JavaCalls11call_helperEP9JavaValueP12methodHandleP17JavaCallArgumentsP6Thread + 0x1056
0x00007fb02d588b21	_ZN9JavaCalls12call_virtualEP9JavaValue11KlassHandleP6SymbolS4_P17JavaCallArgumentsP6Thread + 0x321
0x00007fb02d588fc7	_ZN9JavaCalls12call_virtualEP9JavaValue6Handle11KlassHandleP6SymbolS5_P6Thread + 0x47
0x00007fb02d61fd80	_ZL12thread_entryP10JavaThreadP6Thread + 0xa0
0x00007fb02d965dcf	_ZN10JavaThread17thread_main_innerEv + 0xdf
0x00007fb02d965efc	_ZN10JavaThread3runEv + 0x11c
0x00007fb02d8199d8	_ZL10java_startP6Thread + 0x108

 3160 ---> nid=0xc58

三、比对分析:

1> jstack -m命令采用mixed模式,会将java frame和native frame都打印出来,
比对jstack不带-m和带-m的参数,可以看到:
>>>>所记录线程号:16进制表示的nid号同10进制的数字是一致的;
>>>>java method方法在程序寄存器PC中体现出的是BCI,即ByteCodeIndex,字节码索引或者字节码执行的行号
>>>>native methohd通过16进制的数字偏移量来表示
>>>>程序寄存器PC中不存放native method的地址信息吗?=>【是】
>>>>jstack -m命令会输出程序寄存器PC中的信息;(那程序寄存器PC存的是什么就知道了)
>>>>从Interpreted frame和Compiled frame标识出是对应到Java method的,本质是bci标识;
>>>>注意标【*】为PC中存储的java frame bci和line


四、小结
定义:程序计数器是一块较小的内存空间,可以看作是当前线程所执行的字节码行号指示器,也可称之为PC寄存器;
字节码解释器工作时通过改变程序计数器的值来选取下一条需要执行的字节码指令,
分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
=>执行native本地方法时,程序计数器的值为空(Undefined)
【特性】
>>>在任何确定的时刻,一个处理器(对于多核处理器是一个内核)都只会执行一条线程中的指令;
>>>每条线程都有一个独立的线程计数器(各条线程之间的计数器互不影响,独立存储);
>>>Java方法:则这个计数器记录的是正在执行的虚拟机字节码指令的地址
>>>Native方法:计数器值为空(Undefined),是这样吗?=>【是】PC属于JVM内存范畴,不记录本地方法执行位置;
  ===>执行java方法,计数器记录虚拟机字节码指令的地址(BCI);
  ===>执行native方法,计数器为空(undefined);
>>>此内存区域是唯一在Java虚拟机规范中没有规定任何OutOfMemoryError的区域;
>>>PC寄存器是对物理PC寄存器的一种抽象模拟,线程私有,生命周期与线程的生命周期保持一致

【注意】程序寄存器PC分Java PC和 machine PC的,一般我们讲的是Java PC(因为这个属于JVM 内存模型范围中的),但是在jstack -m时,会将machine PC的本地方法执行的记录号也输出来的;

简言之:Java PC存储的是BCI(相对值)和要执行的字节码的行号(绝对值),当BCI=0时,无line号;

【Tip:样例stack frame】

【GroupA】
"http-nio-9605-exec-28" #10027 daemon prio=5 os_prio=0 tid=0x00007faf50001800 nid=0x1476 waiting on condition [0x00007fafb25b7000]
   java.lang.Thread.State: WAITING (parking)
	at sun.misc.Unsafe.park(Native Method)
	- parking to wait for  <0x00000000e1b31770> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
	at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
	at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
	at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:103)
	at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:31)
	at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)

----------------- 5238 -----------------
0x00007fb02e6ce965	__pthread_cond_wait + 0xc5
0x00007fb02d98c1e5	Unsafe_Park + 0xf5
0x00007fb0196891aa	<Unknown compiled code>
***************************************************
【GroupB】
"NioBlockingSelector.BlockPoller-1" #30 daemon prio=5 os_prio=0 tid=0x00007fb02b0c1000 nid=0xc71 runnable [0x00007fafb26b8000]
   java.lang.Thread.State: RUNNABLE
	at sun.nio.ch.EPollArrayWrapper.epollWait(Native Method)
	at sun.nio.ch.EPollArrayWrapper.poll(EPollArrayWrapper.java:269)
	at sun.nio.ch.EPollSelectorImpl.doSelect(EPollSelectorImpl.java:93)
	at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:86)
	- locked <0x00000000e1b334c8> (a sun.nio.ch.Util$2)
	- locked <0x00000000e1b334b8> (a java.util.Collections$UnmodifiableSet)
	- locked <0x00000000e1b33390> (a sun.nio.ch.EPollSelectorImpl)
	at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:97)
	at org.apache.tomcat.util.net.NioBlockingSelector$BlockPoller.run(NioBlockingSelector.java:298)

----------------- 3185 -----------------
0x00007fb02dfda483	__GI_epoll_wait + 0x33
0x00007fb01921af32	<Unknown compiled code>
**************************************
【GroupC】
"spring.cloud.inetutils" #23 daemon prio=5 os_prio=0 tid=0x00007fb02a423000 nid=0xc63 waiting on condition [0x00007fafb3a06000]
   java.lang.Thread.State: WAITING (parking)
	at sun.misc.Unsafe.park(Native Method)
	- parking to wait for  <0x00000000e15324d8> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
	at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
	at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
	at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)


----------------- 3171 -----------------
0x00007fb02e6ce965	__pthread_cond_wait + 0xc5
0x00007fb02d98c1e5	Unsafe_Park + 0xf5
0x00007fb0190156d4	* sun.misc.Unsafe.park(boolean, long) bci:0 (Interpreted frame)
0x00007fb01900798d	* java.util.concurrent.locks.LockSupport.park(java.lang.Object) bci:14 line:175 (Interpreted frame)
0x00007fb01900798d	* java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await() bci:42 line:2039 (Interpreted frame)
0x00007fb0190079d2	* java.util.concurrent.LinkedBlockingQueue.take() bci:29 line:442 (Interpreted frame)
0x00007fb0190077e4	* java.util.concurrent.ThreadPoolExecutor.getTask() bci:149 line:1067 (Interpreted frame)
0x00007fb019007710	* java.util.concurrent.ThreadPoolExecutor.runWorker(java.util.concurrent.ThreadPoolExecutor$Worker) bci:26 line:1127 (Interpreted frame)
0x00007fb01900798d	* java.util.concurrent.ThreadPoolExecutor$Worker.run() bci:5 line:617 (Interpreted frame)
0x00007fb0190079d2	* java.lang.Thread.run() bci:11 line:745 (Interpreted frame)
0x00007fb0190004e7	<StubRoutines>
0x00007fb02d588616	_ZN9JavaCalls11call_helperEP9JavaValueP12methodHandleP17JavaCallArgumentsP6Thread + 0x1056
0x00007fb02d588b21	_ZN9JavaCalls12call_virtualEP9JavaValue11KlassHandleP6SymbolS4_P17JavaCallArgumentsP6Thread + 0x321
0x00007fb02d588fc7	_ZN9JavaCalls12call_virtualEP9JavaValue6Handle11KlassHandleP6SymbolS5_P6Thread + 0x47
0x00007fb02d61fd80	_ZL12thread_entryP10JavaThreadP6Thread + 0xa0
0x00007fb02d965dcf	_ZN10JavaThread17thread_main_innerEv + 0xdf
0x00007fb02d965efc	_ZN10JavaThread3runEv + 0x11c
0x00007fb02d8199d8	_ZL10java_startP6Thread + 0x108
*************************************

 

<think>我们正在讨论堆栈、寄存器内存管理的概念及其在开发中的注意事项。根据引用内容,我们可以总结如下: 1. 堆栈(Stack): - 自动管理,由操作系统分配释放。 - 后进先出(LIFO)结构,连续内存块,访问速度快。 - 用于函数调用(局部变量、参数、返回地址等)。 - 大小有限,需注意栈溢出。 2. 寄存器(Register): - CPU内部的高速存储单元。 - 包括通用寄存器(如EAX, EBX等)专用寄存器(如堆栈指针SP, 基址指针BP, 程序计数器PC等)。 - 在上下文切换(如任务切换)时,需要保存恢复寄存器状态(如引用[2]引用[4]中的示例)。 3. 内存管理: - 包括栈(自动管理)堆(手动管理,使用new/delete或malloc/free)。 - 堆容易产生内存碎片,效率较低,且可能引发用户态核心态的切换(引用[1])。 - 推荐尽量使用栈而不是堆。 开发注意事项: - 栈溢出:避免在栈上分配过大的对象(如大数组),递归深度不宜过深。 - 指针安全:不要返回指向栈内存的指针(函数返回后栈内存失效)。 - 多线程:每个线程有独立的栈,注意线程局部存储的使用。 - 调用约定:跨语言调用时需统一调用约定(如参数传递顺序、栈清理责任)。 - 栈对齐:某些指令(如SSE)要求栈对齐(如16字节对齐)。 - 上下文切换:在操作系统中,任务切换时需要保存恢复寄存器状态(如引用[2]引用[4]中的代码示例)。 - 性能优化:减少栈内存操作,使用寄存器存储局部变量(register关键字),内联小函数,尾递归优化等。 此外,引用[5]提到虚拟机有基于栈基于寄存器的两种,Lua是基于寄存器的虚拟机,而.NET是基于栈的。这说明了不同虚拟机在实现上的差异。 下面我们按照要求,结构化地组织回答。</think>### 堆栈、寄存器内存管理的核心概念与开发注意事项 #### 一、堆栈(Stack)的核心特性 1. **自动内存管理** - 由操作系统自动分配/释放,遵循 LIFO(后进先出)原则[^3] - 函数调用时自动分配空间用于存储: - 局部变量(如 `int a = 10;`) - 函数参数 - 返回地址 - 寄存器备份(如 EBP) 2. **性能优势** - 连续内存块访问,硬件直接支持(CPU 指令操作栈寄存器) - 分配仅需调整栈指针:$ \text{SP} \leftarrow \text{SP} - n $[^3] - 比堆分配快 $10 \sim 100$ 倍[^1] 3. **容量限制** - 默认大小有限(通常 1MB~8MB) - 栈溢出风险:递归深度过大或超大局部变量 #### 二、寄存器(Register)的关键作用 1. **核心功能寄存器** | 寄存器类型 | x86 示例 | ARM 示例 | 功能 | |------------|----------|----------|------| | 栈指针 | RSP/ESP | SP | 指向当前栈顶 | | 程序计数器 | RIP/EIP | PC | 存储下条指令地址 | | 基址指针 | RBP/EBP | FP | 标记当前栈帧基址 | 2. **上下文切换机制** RTOS 任务切换时需完整保存/恢复寄存器状态[^2][^4]: ```armasm ; ARM 任务切换示例 PendSV_Handler: MRS R0, PSP ; 获取当前任务堆栈指针 STMFD R0!, {R4-R11} ; 保存寄存器到栈 MSR PSP, R0 ; 更新堆栈指针 BX LR ; 返回新任务 ``` 3. **性能优化关键** - 高频数据优先存寄存器(`register` 关键字提示编译器) - ARM 架构要求堆栈 8 字节对齐提升访问效率[^4] #### 三、内存管理对比 | 特性 | 堆 (Heap) | 栈 (Stack) | |--------------|-------------------------|--------------------------| | **管理方式** | 手动分配 (malloc/new) | 自动管理 | | **速度** | 慢(需系统调用) | 极快(硬件直接支持) | | **碎片问题** | 易产生内存碎片[^1] | 无碎片(连续分配) | | **容量** | 受系统内存限制 | 固定大小(默认 1-8MB) | | **典型用途** | 大型对象/动态数据结构 | 函数调用/局部变量 | > 💡 **开发准则**:优先使用栈存储,仅在必要时使用堆分配[^1] #### 四、开发关键注意事项 1. **栈安全防护** - 避免栈溢出: ```c // 危险:可能超出栈容量 void risky_func() { char buffer[1024 * 1024]; // 1MB 栈分配 // ... } ``` - 递归函数设置深度限制:`#define MAX_DEPTH 100` 2. **寄存器安全操作** - ARM 架构需严格区分 MSP(主栈指针) PSP(进程栈指针)[^4] - 中断处理中必须保存所有被修改的寄存器: ```armasm ; 正确保存示例 PUSHALL ; 保存全部寄存器 ; ... ; 中断处理代码 POPALL ; 恢复寄存器 ``` 3. **指针生命周期管理** ```c // 错误:返回失效栈指针 int* create_array() { int arr[10]; return arr; // 函数返回后arr内存被回收 } // 正确:使用堆分配 int* safe_array() { return malloc(10 * sizeof(int)); } ``` 4. **多线程环境要点** - 每个线程拥有独立栈空间 - 共享堆数据需同步机制: ```c // 线程局部存储声明 _Thread_local int per_thread_counter; ``` 5. **架构适配策略** | 架构 | 栈对齐要求 | 寄存器保存规则 | |------------|------------|----------------------| | x86-64 | 16字节 | 调用者保存 RCX/RDX | | ARMv7 | 8字节 | 被调用者保存 R4-R11 | | RISC-V | 16字节 | 调用者保存 a0-a7 | #### 五、性能优化技巧 1. **寄存器优先策略** - 将热点变量保留在寄存器中: ```c register int counter; // 提示编译器优先寄存器分配 ``` 2. **栈内存布局优化** - 高频访问变量靠近栈顶减少 cache miss - 冷数据置于栈帧底部 3. **函数调用优化** - 内联小型函数消除调用开销: ```c inline int square(int x) { return x * x; } ``` - 尾递归转换循环: ```c // 优化前递归 int factorial(int n) { if (n <= 1) return 1; return n * factorial(n-1); // 非尾递归 } // 优化后尾递归 int factorial_tail(int n, int acc=1) { if (n <= 1) return acc; return factorial_tail(n-1, acc*n); // 可转循环 } ``` > 🔍 **调试技巧**:使用 `backtrace()` 获取栈回溯信息,编译时添加 `-fno-omit-frame-pointer` 保留帧指针[^4] --- ### 相关问题 1. 如何检测预防栈溢出攻击(Stack Smashing)? 2. 实时系统中如何精确计算每个任务的栈大小? 3. x86 与 ARM 架构在中断处理时保存寄存器有哪些关键差异? 4. 基于寄存器的虚拟机(如 Lua)基于栈的虚拟机(如 JVM)在内存管理上有何本质区别[^5]? 5. 使用 `alloca()` 进行动态栈分配的优缺点是什么?
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值