直接进入正题,运行时数据区是jvm中一个重要的组成部分,也是开发人员最关注的一个部分。java不同于C、C++,他们是开发人员掌控所有对象的生命周期,从创建到销毁都是由开发人员控制,而java则是自动内存管理,开发人员往往只需要创建对象即可,不需要关注对象是如何、何时被销毁的,不需要关注并不是它不需要销毁对象,而是交给了jvm自动进行垃圾回收,通常我们称之为GC。
通常jvm管理的这部分内存可以划分为方法区、堆、虚拟机栈、程序计数器、本地方法栈。其中方法区和堆是线程共享区域,而虚拟机栈、程序计数器和本地方法栈是线程私有区域。
通常每一个版本的JDK发布,都会伴随这两个规范发布:java语言规范和java虚拟机规范,这些在官方文档都可以查看。
堆
其中在java虚拟机规范中规定,在java中所有创建的对象和数组都应该在堆上分配。说白了堆上存放的就是对象和数组,自jdk7以后,java中字符串常量池也被放到了java堆中,原因是因为oracle在收购BEA获得了JRockit虚拟机将其中的元空间整合到了Hotspot虚拟机上,为了兼容而将字符串常量池移到了堆空间上。jdk1.7之前(不包含)字符串常量池应该是在方法区中。
堆中可能会抛出OutOfMemoryError,这个异常表示在创建新的对象时堆空间不足而抛出的异常,通常来说在抛出该异常的时候都会先进行一次Full GC。
堆空间是开发以及运维人员最为重视的一块内存空间,因为在程序运行的时候无时无刻不在创建和销毁对象,也是抛出OutOfMemoryError最多的一块区域
方法区
方法区也成为非堆,同堆一样也是线程共享的一块区域。该区域存放类型信息,域信息、方法信息、JIT代码缓存以及运行时常量池等信息。该区域在jdk1.8以前被称为永久代,在jdk1.8的时候被称为元空间,所谓元空间就是收购BEA之后合并JRockit后从中获得的。
方法区是一块在java虚拟机规范中要求比较宽泛的一块区域,它可以由虚拟机自行决定是否实现方法区的垃圾回收。事实上方法区的垃圾回收也是极难实现的,其中运行时常量池倒是还好,只要没有地方引用该常量就可以进行回收了,但是类型卸载的条件确实极难满足,类型的卸载需要同时实现以下三个条件:
- 该类的所有实例已经被回收
- 该类的类加载器已经被回收
- 该类的Class对象已被回收
方法区也是java虚拟机规范中规定允许抛出OutOfMemoryError异常的地方,比如动态代理使用较多的应用产生了大量的代理类就有可能造成空间溢出进而抛出OutOfMemoryError异常。
虚拟机栈
虚拟机栈是线程私有的一块区域,也就是说,每一个线程被创建,都会有一个虚拟机栈被创建。虚拟机栈中存放栈帧(Frame),一个栈帧代表一个方法,一个方法的调用表示方法入栈,方法结束表示方法出栈,也就是说虚拟机栈遵循先进后出FILO的原则。
java虚拟机规范中规定虚拟机栈允许抛出OutOfMemoryError和StackOverflowerError。java虚拟机规范中允许java虚拟机栈可以动态扩容,如果在需要扩容时无法申请到足够的内存则抛出OutOfMemoryError。如果栈帧数大于java虚拟机栈的最大深度将会抛出StackOverflowerError异常。
本地方法栈
本地方法栈同虚拟机栈一样,只是虚拟机栈调用的是java的方法,而本地方法栈调用的是本地方法,所谓本地方法就是编写java所用的C或C++的方法。
程序计数器
程序计数器也叫pc寄存器,他是唯一一个在运行时数据去没有规定抛出任何方法的一块内存区域,原因是这块区域所占空间极小,它只是存放每条线程执行代码下一个的行号。java在多线程条件下总会争夺CPU资源而造成线程切换(造成线程切换的原因不止这个)如此看来程序计数器实现最简单最有效的办法就是线程私有化,让每一条线程都拥有一个程序计数器即可。这里要注意的是,java虚拟机在执行本地方法的时候在pc寄存器里边对应的是空值(undefined)
到这里简单的jvm的运行时数据区已经简单介绍完了,后续再详细记录他们中的每一个部分。