目录
一. 程序计数器(Program Counter Register)
什么是JVM运行时数据区?
我们都知道,当java文件编译成class文件后,需要交给JVM去运行,那么肯定是会存在一个数据运行区,我们的写的方法中的变量包括方法本身,都是在运行时数据区中被操作的,下面我们先来看看运行时数据区的数据组成图。
一. 程序计数器(Program Counter Register)
在JVM中,也可以这么理解,程序计数器是指当前线程执行时的字节码指令地址,相当于行号。
为什么需要记录这个地址呢? 因为我们的线程在运行过程中不是持续能够抢占到资源的,在多线程的情况下,线程之间通过轮流切换时间片来完成需要执行的任务,所以程序计数器的作用就是,当线程再次获得资源时,将执行当前记录的字节码对应的地址。
二,虚拟机栈(VM Stack)
存储当前线程执行运行方法时所需要的数据,指令和返回地址等,如下图:
一个方法一个栈帧,对于我们熟悉的递归方法,将会创建n个方法, 如果异常,将会遇到StackOverFlowError。JVM的栈可以动态扩展(例如我们熟知的ArryList, 便是可支持扩展的),但是在尝试扩展时无法申请到足够的内存则抛出OutOfMemoryError异常。
局部变量表的详解,可查看:https://blog.csdn.net/qq_15037231/article/details/96462457
cd到类对应的class文件路径下,反编译命令 javap -v TestJVM > 0726.txt,可查看结果如下:
class文件中包含对应的字节码,可以查看文章https://blog.csdn.net/Ginny_2017/article/details/97371018。
三,本地方法栈(Native Method Stack)
本地方法栈和虚拟机栈相似,区别就是虚拟机为虚拟机栈执行Java服务(字节码服务),而本地方法栈为虚拟机使用到的Native 方法服务。本地方法栈中使用的语言,使用方式,数据结构没有强制要求。在HotSpot虚拟机实现中是把本地方法栈和虚拟机栈合二为一的,同理它也会抛出StackOverflowError和OOM异常。
四,堆(Heap)
堆是JVM里最大的一块内存区域,被所有线程共享,在虚拟机启动时创建,此区域的目的就是存放对象实例和数组,几乎所有的对象实例都在这分配(随着JIT的发展已经不是那么绝对了)的.java堆是垃圾收集管理的主要区域,由于现在收集器基本都采用分代收集方法,所以Java的堆中还可以分为新生代,老年代,永久代.1.8之后取消了永久代;其中新生代又划分为Eden空间,From Survivor空间,To Survivor空间。比如堆的分配内存为无论怎么划分都是为了更好的回收,分配,利用内存。
1. 新生代与老年代的比例1:2, 而新生代中Eden:From:To = 8:1:1
2. from区和to区会随着每一次的gc而调换位置,同时当新生代的eden空间不足时,如果该对象无法通过gc释放,将会被移动到老年代中,因为老年代对新生代存在担保机制, 关于担保机制的条件,可参照文章:https://blog.csdn.net/qq_36187285/article/details/82144696
下图为1.8后的内存模型:
五,方法区(Method Area)
方法区也是一个线程共享的区域,存储已被虚拟机加载的类信息,常量(final),静态变量(static),JIT(即时编译器)编译后的代码等数据。
Java虚拟机规范把方法区描述为堆的一个逻辑部分,其实堆和方法区可以看成数据部分;虚拟机栈和程序计数器可以看成指令部分;方法区存储一些不会变更的数据,之前热点上使用GC分代收集管理方法区,所以方法区也被称为永久代(本质上两者不等价),但是现在已经使用Native Memory来代替永久代了。
虚拟机对方法区规范非常宽松,除了和Java的堆一样不需要连续的内存和可以选择固定大小意外,还可以选择不实现垃圾回收。垃圾回收行为在这个区域比较少见但还是有必要的,主要是针对常量池回收和类型的卸载。
5.1运行时常量池
运行时常量池是方法区的一部分,用于存放编译期生成的各种字面量和符号引用,运行时常量池相对于类常量池另外一个特性就是具备动态性,运行期间可能将新的常量放入池中。
*************************************************************************************
public static void main(String[] args) {
List<String> list = new ArrayList<>();
int i = 0;
while (true) {
list.add(String.valueOf(i++).intern());
}
}
*************************************************************************************
实际上对于以上代码,在JDK6、JDK7、JDK8运行结果均不一样。原因就在于字符串常量池在JDK6的时候还是存放在方法区(永久代)所以它会抛出OutOfMemoryError:Permanent Space;而JDK7后则将字符串常量池移到了Java堆中,上面的代码不会抛出OOM,若将堆内存改为20M则会抛出OutOfMemoryError:Java heap space;至于JDK8则是纯粹取消了方法区这个概念,取而代之的是”元空间(Metaspace)“,所以在JDK8中虚拟机参数”-XX:MaxPermSize”也就没有了任何意义,取代它的是”-XX:MetaspaceSize“和”-XX:MaxMetaspaceSize”等。
参考自:https://www.cnblogs.com/yulinfeng/p/7153391.html
参考资料:《深入浅出JVM》