最近一直想学习一下jvm相关知识,经朋友介绍,阅读了周志明老师的《深入理解java虚拟机》,以下内容全部来自这边书籍的内容
一、Java运行时数据区
Java 虚拟机在执行java程序的过程中会把它所管理的内存划分为若干个不同的数据区域,根据《java虚拟机规范》的规定,Java虚拟机所管理的内存将包括以下几个区域,如下图:
1.1程序计数器
线程私有的一块比较小的内存区域,各条线程之间计数器互不影响,独立存储。 它可以看作当前线程所执行的字节码指示器,处理分支,循环,跳转,异常处理,线程恢复等功能。不会出现任何OutOfMemoryError。
1.2Java虚拟机栈
Java虚拟机栈也是一块线程私有的一块区域,它的生命周期与 线程相同,每个方法被执行的时候Java虚拟机栈都会同步创建一个栈帧用于存储局部变量表,操作数栈,动态连接,方法出口等信息,每一个方法被调用直至执行完毕的过程,都对应着一个栈帧在虚拟机中从入栈到出栈的过程。
1.2.1局部变量表
局部变量表是一组存储变量值的存储空间,用于存放方法参数和方法内部定义的局部变量,在Java编译时期就确定了该方法所需分配的局部变量表的最大容量。局部变量表的容量以变量槽为最小单位,存放编译期可知的各种Java虚拟机基本数据类型(boolean,byte,char,short,int,float(前面的为32位),long,double(后面两个为64位)),对象引用(reference,它·并不等同于本身,可能指向对象起始地址的引用指针,也可能指向一个代表对象的句柄或者对象的相关位置),returnAddress类型。如果线程请求的栈深度大于虚拟机的所允许的深度,将抛出StackOverError 异常。同时也有可能发生OutOfMemoryError异常
1.2.2 操作数栈
操作数栈是一个先入先出栈,在方法执行的过程中,会有各种字节码指令往操作数栈中写入和提取内容,也就是出栈和入栈操作。比如在做算术运算时通过将运算涉及的操作数栈压入栈顶后调用运算指令来进行。又譬如在调用其他方法时是通过操作数栈来进行方法参数的传递。
1.2.3动态连接
每一个栈帧都包含一个指向运行时常量池中栈帧所属方法的引用。方法在运行期间将符号引用转为直接引用,这部分就是动态连接。
1.2.4 方法返回地址(方法出口)
当一个方法执行后,有两种退出方式:一种是正常调用完成,另一种是异常调用完成。第一种指的是执行引擎遇到任意一个方法返回字节指令,正常退出。另一种是在方法执行过程中遇到了异常,且异常没有得到妥善处理。
1.3本地方法栈
本地方法栈与虚拟机栈所发挥的作用是非常相似的,其区别只是虚拟机栈为虚拟机执行Java方法服务,而本地方法栈则为虚拟机使用到的本地方法服务。
1.4Java堆
Java堆是虚拟机所管理的内存中最大的一块,是被所有线程共享的一块内存区域,虚拟机启动时创建。该内存区域唯一目的就是存放对象实例。
Java堆是垃圾收集器管理的内存区域,因此也被称为GC堆,从回收内存的角度看,由于现代垃圾收集器大部分都是基于分代收集器的理论设计的,所以Java 堆分为 新生代和老年代,更为细致的新生代又可以分为Eden 区,Form Survivor 区,To Surivor 区 (其中比例为8:1:1后续讲解)。从内存分配的角度看,所有线程共享的Java堆中可以划分出多个线程私有的分配缓冲区(TLAB),来提升对象分配时的效率,无论从什么角度,无论如何划分,都不会改变java 堆中存储内容的共性,无论是哪个区域,存储的都只能是对象的实例,将Java堆细分的目的只是为了更好的回收内存,或者更快的分配内存。
如果Java堆中没有内存完成实例分配,并且堆也无法再扩展时,Java虚拟机将会抛出OutOfMemoryError异常。(通过参数-Xmx和Xms可以设定大小)
1.5方法区
方法区和Java堆一样,是各个线程共享的内存区域,它用于存储被虚拟机加载的类型信息,常量,静态变量,即时编译器编译后的代码缓存等数据。
1.6运行时常量池
运行时常量池是方法区的一部分,class文件中除了有类的版本,字段,方法,接口等描述信息外,还有一项信息是常量池表,用于存放编译期生成的各种字面量与符号引用。一般来讲,除了保存Class文件中的描述的符号引用外,还会把有符号引用翻译出来的直接引用也存储在运行时常量池中。既然常量词是方法区的一部分,自然受到方法区的限制,当常量池无法在申请到内存时会抛出OutOfMemoryError。
1.7 永久代和元空间
永久代的移除:
在Java 6中:方法区中包含的数据,除了JIT(Just In Time)编译生成的代码存放在native memory的CodeCache区域,其他都存放在永久代。
在Java 7中:符号引用(Symbolic References)转移到了native memory;字面量(Literal)转移到了java heap;类的静态变量(class statics)转移到了java heap。
在Java 8中:永久代被彻底移除,取而代之的是另一块与堆不相连的本地内存——元空间(Metaspace),‑XX:MaxPermSize 参数失去了意义,取而代之的是-XX:MaxMetaspaceSize。
移除永久代的主要原因:
- 为了HotSpot与JRockit的融合。HotSpot所属于Sun公司,JRockit所属于BEA公司,两者分别与2008和2009年被Oracle收购,Oracle选择将两个优秀的虚拟机融合到一起,主要应该是以HotSpot为主,这个融合在JDK1.8完成。
- 永久代大小不容易确定,PermSize指定太小容易造成永久代OOM。
Metaspace(元空间):
- 元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存(native memory)。
- 元空间主要用来储存类的元数据,元数据包括类、字段、方法定义及其他信息。类和它的元数据的生命周期是和它的类加载器的生命周期一致的。也就是说,只要类的类加载器是存活的,在Metaspace中的类元数据也是存活的,不能被释放。