本文共 1983 字,大约阅读时间需要 6 分钟。
这篇文章尽量将每个区域的作用,以及特点描述清楚,直到他们会在什么时候用到,关于其相关的概念,暂不做展开分析
java内存结构由以下几个部分组成
程序计数器
堆
本地方法栈
虚拟机栈(也叫java栈)
方法区
接下来就主要介绍每个区的是干什么的,以及有什么作用
程序计数器
用来保存当前线程执行的虚拟机字节码的指令地址,通过改变字节码计数器的值,来获取下一条需要执行的字节码指令,分支,循环,跳转,异常处理,线程恢复都依赖于这个计数器来完成,多线程情况下,为了线程切换后能恢复到正确的执行位置,每个线程都拥有一个程序计数器。
如果当前线程执行的是一个本地方法,那么计数器当中保存的是undefined
每个线程对应一个程序计数器,随着线程的创建而创建,线程的结束而销毁,唯一一个不会出现outOfMemoryError的区域
Java虚拟机栈
java虚拟机栈也是和线程相关联的,每创建一个线程就对应一个java虚拟机栈,每个java虚拟机栈当中对应多个栈帧,每调用一个方法就会往栈当中压入一个栈帧,栈帧是用来存储方法中涉及到的数据信息,每个方法调用到执行结束,就是一个栈帧入站到出站的过程。
java虚拟机栈顶中永远是当前正在执行的方法,如果在这个栈帧中调用另外一个方法,与之对应的栈帧也会被压入栈内,变为当前活动的栈帧,方法执行结束之后,将栈帧移除栈中,将方法的返回值作为当前活跃的栈帧的操作数,如果没返回值,新活动的栈帧当中操作数不会发生变化,
每个栈帧当中存储的有:局部变量表,操作数栈,方法连接,方法返回地址。 局部方法表用来存储方法当中的局部变量,在编译时期,就会确定大小,在执行的时候只需分配大小即可,方法运行过程中,局部方法表大小不会发生变化
虚拟机栈会有两种类型错误,StackOverFlowError和OutOfMemoryError,StackOverFlowError,如果java虚拟机栈的大小不允许动态扩展,当线程请求栈的深度超过java虚拟机栈的最大深度时,就会抛出异常。OutOfMemoryError是,如果允许动态扩展,当线程请求栈时,内存用完了,就会抛出OutOfMemoryError错误了
本地方法栈
本地方法栈(native method stack)和java虚拟机栈类似,不过是为native方法服务的,
本地方法栈,在执行本地方法时,也会把当前执行的方法作为栈帧压入栈中,同样也会抛出StackOverFlowError和OutOfMemoryError错误。
堆
是java存储对象的地方,是jvm当中最大的一块地方,也是垃圾回收的主要场所,堆对于所有线程来说都是共享的,进一步可以分为新生代(Eden区 from Survior和to Survior)和老年代, 在虚拟机启动时创建
堆的大小是可以动态分配的,当线程请求分配内存,但是堆已经满了时,且内存无法扩展时,就会抛出OutOfMemoryError
堆中分配内存有两个实现方式
1、指针碰撞法
前提是堆中内存都是连续的,已经使用的内存和未使用的内存在不同侧,通过一个指针作为临界点,当需要分配内存时,只需要把指正往空闲的一端移动需要分配内存大小的长度即可
2、空闲列表法
堆当中的数据并不是完整的,已经分配的和未分配的都交互存在的,JVM当中会维护一个列表,来记录维护的空闲内存信息,当分配操作发生的时候,分配一块内存给对象实例,然后更新空闲列表数据。在多线程下,需要考虑并发问题。
解决办法
a:采用CAS保证数据更新的原子性
b:将内存分配的行为按照线程划分,每个线程在堆中分配一块大的区域,叫做本地线程分配缓冲(Local Thread Alloctation Buffer, LTAB)
方法区
方法区和堆一样,是可以被线程共享的一块区域,主要是用来存储已经被虚拟机加载类信息,常量,静态变量,及时编译器编译后的代码。
关于方法区的垃圾回收:方法区主要是来回收常量池和类的卸载,一般来说方法区的数据需要长期存在,属于堆的逻辑分区,按照堆当中划分的方式,方法区会被称作是“老年代”。方法区的实现是比较宽松的,可以固定大小,也可以动态扩展,也可以不垃圾回收。同样的,当内存分配不能被满足时,也会抛出OutOfMemoryError错误。
--------------------------------------------------------------------
最后,需要了解下运行时常量池,即方法区当中的一部分,
运行时常量池
主要是用来存放编译时各种常量,包括字面值和符号引用,当然在运行期间也可以往常量池当中增加新的常量,比如String.intern()
以上就是java内存结构中的各个模块的分析,如有问题,欢迎指正~
参考: