介绍下Java内存区域(运行时数据区)
Java 虚拟机在执行 Java 程序的过程中会把它管理的内存划分成若干个不同的数据区域。JDK 1.8 和之前的版本略有不同。
下图是 JDK 1.8 对JVM做的改动,把方法区的具体实现—-元空间已到了本地内存中。
各线程共享的:堆、方法区(元空间)、直接内存;
各线程私有的:程序计数器、虚拟机栈、本地方法栈;
1️⃣ 程序计数器
它是个什么?
程序计数器是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。字节码解释器工作时通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等功能都需要依赖这个计数器来完成。
它有什么用?
1、字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制;
2、在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了;
补充:
- 每个线程内都有一个程序计数器,因为每个线程各自执行的指令地址是不一样的。当多线程并发执行时,CPU中的线程调度器会给每个线程分配一个时间片,当某个线程在时间片内仍没执行完,那程序计数器就得记录当前线程下一次被分配时间片执行时的下一条jvm指令的执行地址。
有什么需要注意的点?
- 线程私有;
- 不会存在内存溢出(⚠ 程序计数器是JVM中唯一一个不会出现
OutOfMemoryError
的内存区域)- 它的生命周期随着线程的创建而创建,随着线程的结束而死亡;
- Java中把CPU中的寄存器当作了“程序计数器”,因为寄存器是CPU中读取速度最快的单元;
2️⃣ 虚拟机栈
它是个什么?
每个线程运行时所需要的内存,称为“虚拟机栈”。所有的 Java 方法调用都是通过栈来实现的(也需要和其他运行时数据区域比如程序计数器配合)。
每一个线程运行时需要给每一个线程划分一块内存空间,那虚拟机栈就是每一个线程运行时所需要的内存空间。一个线程就有一个虚拟机栈,多个线程就有多个栈。
方法调用的数据需要通过栈进行传递,每一次方法调用都会有一个对应的栈帧被压入栈中,每一个方法调用结束后,都会有一个栈帧被弹出。
每个栈由一个个栈帧(Frame)组成,对应着每次方法调用时所占用的内存。每个栈帧内部都拥有:局部变量表、操作数栈、动态链接、方法返回地址。栈顶部的,即正在被执行的方法,叫活动栈帧。
它有什么用?
栈用来执行所有方法,并返回结果。
什么情况下会栈内存溢出?
- 栈帧过多导致栈内存溢出。如递归调用,没有递归出口,当线程请求栈的深度超过当前 Java 虚拟机栈的最大深度的时候,就抛出
StackOverFlowError
错误; - 栈帧过大导致栈内存溢出。如果虚拟机在动态扩展栈时无法申请到足够的内存空间,则抛出
OutOfMemoryError
异常;
有什么需要注意的点?
- 一个方法被调用时所占用的内存就是一个栈帧,方法中有方法参数,局部变量,返回地址等这些信息都是要占用内存的。所以每个方法执行时,需要预先把内存分配好。
有没有试过线程诊断?(Linux环境下)
# 可以实时监测后台进程对CPU和内存的占用情况,top命令可以得到进程编号,但不能定位这个进程中哪个线程有问题
top
# ps命令进一步可以查看该进程下,所有线程对CPU的占用情况,看是哪一个线程占用过高(注意:输出的线程编号是十进制的)
ps H -eo pid,tid,%cpu | grep 进程id
# jstack命令可以得到该进程下的所有线程运行信息(注意:输出的线程中有个参数是nid,值就是线程编号,16进制的,把上面10进制换算成16进制)
jstack 进程id # 而且,可以定位到Java哪个类中第几行代码有问题
上面的三步法,对于常见的死循环、死锁等问题,都可以排查出来。
其他面试问题?
Q:垃圾回收是否涉及栈内存?
- 不会。因为栈内存无非就是一次次方法调用所产生的栈帧内存,而栈帧内存在每一次方法调用结束后,都会被弹出栈,即会自动被回收掉,所以根本不需要垃圾回收来管理我们的栈内存。
- 垃圾回收只是去回收堆内存中无用的对象。
Q:栈内存分配越大越好吗?
- 栈内存划得越大,反而会让线程数变少。因为内存条物理内存大小是一定的,栈内存越大,那线程数就越少;
- 一般栈内存划得大,只是为了进行多次的递归调用。一般采用系统默认大小就可以了,Linux下是1024kB,即1M大小;
- 使用
-Xss size
可以指定栈内存的大小,如-Xss256k
(中间没有空格),把栈内存设为256k;Q:方法内的局部变量是否线程安全?
- 如果方法内局部变量没有逃离方法的作用访问,它是线程安全的;
- 如果是局部变量引用了对象,并逃离方法的作用范围,需要考虑线程安全;
3️⃣ 本地方法栈
它是个什么?
和虚拟机栈的作用非常相似,虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。
JVM在调用本地方法时,需要给这些本地方法提供的内存空间。本地方法不是由java代码编写的,因为java有时不能直接跟OS底层打交道,所以就需要用C或C++语言编写的本地方法来与OS底层的API打交道。java代码可以通过本地方法接口调用OS底层的功能。那这些本地方法运行时使用到的内存就是本地方法栈。
它有什么用?
本地方法被执行的时候,在本地方法栈也会创建一个栈帧,用于存放该本地方法的局部变量表、操作数栈、动态链接、出口信息。
方法执行完毕后相应的栈帧也会出栈并释放内存空间,也会出现 StackOverFlowError
和 OutOfMemoryError
两种错误。
4️⃣ 堆(
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/281464.html
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/281464.html