JVM内存模型
之前看过很多关于jvm内存模型的文章,什么有堆栈,方法区,程序计数器等划分,也有文章说主存和工作内存的模型。
这里我觉得无论是jvm内存区域的划分,还是主存工作内存的使用,都是属于jvm内存模型内的知识。基于自己的理解,简单总结一下,单纯只是个人总结,不一定是正确的,假如看到有错误的地方可以帮忙指出。
一、jvm运行期的内存区域划分:
上述图可以比较直观的看出jvm运行期,哪些内存区域属于共享的,哪些属于线程私有的。下面简单的介绍每个内存区域:
1.线程私有的内存区域:
(1)java 栈(jvm stack)
主要存放一个个栈帧,每个栈帧对应着线程执行每个方法相关的数据(局部变量表,操作栈,动态链接,方法出口等信息),每一个方法执行完了,对应的栈帧就会从java栈出栈。当栈的深度达到jvm所允许的最大深度,就会抛出 stackoverflowerror
的错误,当扩展无法申请到足够的内存则会抛出 outofmemoryerror
的错误。
(2)程序计数器(PC register)
很小的内存空间,因为jvm底层执行程序是执行字节码指令,而程序计数器就记录着当前指令的执行位置。随着指令执行而变化,从而获取下一个需要执行的指令(可实现分支,循环,跳转,异常)。
(3)本地方法栈(native method stack)
与java栈存储内容很相似,只是java栈是服务于java方法(字节码),而本地方法栈是服务于native方法。同样也会抛出上面提到的异常错误。
2.线程共享的内存区域:
(1)堆(heap)
这一块主要是存放对象数据,是jvm内存中最大的一块。包含了新生代和年老代,也是GC(垃圾回收)主要收集的区域。(GC算法会在后面的文章里提到),当内存申请超过了jvm定义的内存大小,则会报outofmemory的错误。
(2.1)本地方法区(Method Area)
主要存放的是类信息,常量,静态变量等数据,在jdk1.8之前,hotspot里是将方法区当做永久代来使用,之后废除了永久代,这里会在另外一篇文章里解释。
(2.2)运行时常量池(Runtime Constant Pool)
属于方法区的一部分,用于存放编译期生成的各种字面量和符号引用,如字符串,final变量,类名和方法名常量等(这里需要注意常量池的常量的存储大小)
二、主存和工作内存:
与内存区域划分不在同一个层面上,jvm中的所有变量都是存放在主存中,所有线程在获取这些变量的时候,首先需要将变量拷贝一份到自己的工作内存中,然后再在工作内存对这些变量进行操作。不同线程无法访问彼此的工作内存中的变量。
因此假如A线程想要修改某个变量test1,首先将线程的工作内存中的变量赋值,然后再将变量传递到主存上去。
这样的模型,假如是线程操作私有的变量是没有问题的,假如是线程操作共享的变量,可能就会引起操作结果有误。比如当线程A将共享变量test2拷贝到A的工作内存,这个时候B线程也拷贝了test2到B的工作内存,当A操作完test2经过flush将值刷到主存后,B线程操作完test2同步到主存的时候,会将A的操作覆盖掉。
这样的问题就是同步问题。同步问题可以通过synchronized锁或者volatile来实现。(如何实现同步将会在另外的文章里讲述)
后续有补充,会持续更新。