内存模型简介
JVM运行时数据区(RunTime Data Areas)分为了堆(Heap Area),方法区(Method Area),本地方法栈(Native Method Stack),栈(Stack Area),程序计数器(PC Registers)。
详解
运行时数据区的模块分两大类,一类是被线程私有的,还有一类是线程共同持有的,线程公有部分也就是线程问题的出现原因,不过这一章节就不对这方面展开描述了。
线程私有:本地方法栈,栈,程序计数器
线程公有:堆,方法区
栈(Stack Area)
作用概括:用于执行程序中的方法
每个方法在执行时都会创建一个栈帧,栈帧内包括了局部变量表,操作数栈,动态链接,方法出口,栈空间大小是可设定的,线程请求的栈深度不够会报StackOverflowError异,栈动态扩展的容量不够会报OutOfMemoryError异常。
局部表量表:储存方法中的基础类型数据,以及在逃逸分析打开的情况下,也会保存不会发生逃逸的对象
操作数栈:进行数据的加减乘除等操作时,会将数据加载如操作数栈,操作后,再放回局部变量表中
动态链接:存放逃逸分析后逃逸的对象或者过大的对象的内存地址
方法出口:指向调用方法的地方,在方法执行完后回到原点继续向下执行
本地方法栈(Native Method Stack)
作用概括:用于执行程序中的native方法,其他与栈相同
程序计数器(PC Registers)
程序计数器是一块较小的内存区,可以看做是当前线程所执行的字节码的行号指示器,如果线程正在执行一个JAVA方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址,如果正在执行的是Native方法,这个计数器值为空(Undefined),此内存区域是唯一一个在JAVA虚拟机规范中没有规定任何OutOfMemoryError的区域
注:这里有问题是计数器值为空,程序怎么往下执行 参考C++理解是:当线程中调用native方法的时候,则重新启动一个新的线程,那么新的线程的计数器为空则不会影响当前线程的计数器,相互独立。
方法区(Method Area)
作用概括:主要用于存储类的信息、常量池、方法数据、方法代码等。方法区逻辑上属于堆的一部分,但是为了与堆进行区分,通常又叫非堆。
方法区很多人会叫他永久代(PermGen Space),其实这个叫法源于甲骨文对JVM的实现HotSpot,JDK1.8之前对于方法区的落地实现,为什么说是1.8以前的原因是因为在1.8版本将具体实现改为了元空间(MetaSpace)
元空间和永久代主要的区别在于,永久代是在堆内的一块空间,因为储存内容的特殊性,就很容易造成内存溢出,元空间不在JVM中,而是直接使用了本地内存,从而避免了内存溢出问题。在更新到1.8后,对于方法区储存内容也进行了一定的转移:
1.字面量 (interned strings)转移到 Java heap
2.类的静态变量(class statics)转移到Java heap
3.符号引用(Symbols) 转移到 Native heap
堆(Heap Area)
作用概括:堆内主要储存执行过程中的对象数据
堆空间分配:堆内还分为年轻代和老年代两大空间,年轻代还分为Eden区和survivor1,survivor2三片区域,年轻代和老年代的内存默认占比是1:2,年轻代三片区域的占比是(Eden)8:(Survivor1) 1:(Survivor2)1
老年代晋升策略:具体的还是要看GC篇
1.age小于15
2.大对象
3.survivor区装不下自动晋升