二、运行时JVM结构组成及作用
二、运行时JVM结构组成及作用
程序计数器
是否共享:否,线程私有,每个线程有1个独立的程序计数器!
所处位置:线程私有的内部区域
生命周期:与线程绑定
主要作用:
当前线程执行字节码的行号指示器! 指哪打哪!
虚拟机的概念模型里(仅是概念模型,各种虚拟机可能会通过一些更高效的方式去实现),字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
存储内容:
线程正在执行一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Natvie方法,这个计数器值则为空(Undefined)。
为什么设计每个线程一个计数器?
JVM多线程之间切换时,如果只有1个计数器,也会产生并发问题;所以,为了让线程切换后也能执行正确的程序,每个线程设计1个程序计数器!
异常:此区域不抛出异常
虚拟机栈
是否共享:否,线程私有,每个线程1个栈区间
生命周期:与线程生命周期绑定
主要作用:
1)线程中java方法执行的逻辑模型;
每个java方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
执行该方法,就创建一个栈帧压栈;执行完栈帧出栈;
2)存放java方法的基本数据类型,引用数据类型
局部变量表存放基本数据类型,局部变量;
如(boolean,byte,char,short,int,float,long,double)、对象引用(reference)和returnAddress类型。对象引用可能是指向对象堆内存的指针;
异常:
1)如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;
2)如果虚拟机栈可以动态扩展(当前大部分的Java虚拟机都可动态扩展,只不过Java虚拟机规范中也允许固定长度的虚拟机栈),当扩展时无法申请到足够的内存时会抛出OutOfMemoryError异常。
本地方法栈
主要作用:
为JVM执行native方法!除了这个,其他都和虚拟机栈相同!
是否共享:否,线程私有,每个线程1个栈区间
生命周期:与线程生命周期绑定
异常:
同虚拟机栈
Java堆
是否共享:是,所有线程共享的内存。
主要作用:分配/存放对象实例,数组实例;
堆内存结构:堆内存=年轻代+老年代
年轻代Eden:含有Thread Local Allocation Buffer
没有直接设置老年代的参数,但是可以设置堆空间大小和新生代空间大小两个参数来间接控制。
老年代空间大小=堆空间大小-年轻代大空间大小
具体参考jvm参数设置。
是否有垃圾回收:是,垃圾回收的主要场所
回收目标:没有引用的对象
要求:
1)java堆可扩展,扩展存放增加的对象实例!
2)不需要连续内存空间
异常:
堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。
年轻代
1)年轻代作用:存放所有new新创建的对象
2)年轻代结构
年轻代内存又被分成三部分,Eden空间、From Survivor空间、To Survivor空间,默认情况下年轻代按照8:1:1的比例来分配
Eden:8
From Survivor:1
To Survivor:1
部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor区(两个中的一个),当这个Survivor区满时,此区的存活对象将被复制到另外一个Survivor区,当这个Survivor去也满了的时候,从第一个Survivor区复制过来的并且此时还存活的对象,将被复制“年老区(Tenured)”。需要注意,Survivor的两个区是对称的,没先后关系,所以同一个区中可能同时存在从Eden复制过来 对象,和从前一个Survivor复制过来的对象,而复制到年老区的只有从第一个Survivor去过来的对象。而且,Survivor区总有一个是空的。同时,根据程序需要,Survivor区是可以配置为多个的(多于两个),这样可以增加对象在年轻代中的存在时间,减少被放到年老代的可能。
年老代
在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。
方法区
是否共享:是,所有线程共享
主要作用:存储类元信息(类的数据结构),常量,静态变量,即时编译器编译后的代码数据。又叫类信息常量池!
是否有垃圾回收:可选,一般在不在方法区执行垃圾回收,没啥效果!
回收目标:针对常量池的回收和对类类型的卸载
要求:
1) 可扩展
2) 不需要连续内存空间
异常:
当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常
永久代Permanent Generation
用于存放静态文件,类常量信息池,如Java类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。持久代大小通过-XX:MaxPermSize=<N>进行设置。
JDK8移动永久代到Java堆
http://www.sczyh30.com/posts/Java/jvm-metaspace/
永久代是类的元数据,从JDK 8 开始把类的元数据放到本地java堆内存(native heap)中,这一块区域就叫 Metaspace,中文名叫元空间。
运行时常量池
方法区的一部分,
主要作用:用于存放编译期类生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。当java代码被编译成字节码(class)文件时,会将字面量(文本字符串,声明为final的常量值)、符号引用存放在class文件的常量池中。这部分内容将在类加载后进入方法区的运行时常量池中存放。
Class文件中除了有类的版本、 字段、 方法、 接口等描述信息外,还有一项信息就是常量池(Constant Pool Table),
堆外内存-硬件内存
NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。 这样能在一些场景中显著提高性能,因为避免了在Java堆和Native堆(堆外内存)中来回复制数据。
jvm内存归类
执行逻辑:
Java执行逻辑:栈区,程序计数器
Native执行逻辑:local栈,
数据存储:堆区
类元数据:方法区(类常量池)
JVM内存结构主要有三大块:堆内存、方法区和栈。
1)堆内存是JVM中最大的一块由年轻代和老年代组成,而年轻代内存又被分成三部分,Eden空间、From Survivor空间、To Survivor空间,默认情况下年轻代按照8:1:1的比例来分配;
2)方法区存储类的元信息(类的数据结构)、常量、静态变量等数据,是线程共享的区域,为与Java堆区分,方法区还有一个别名Non-Heap(非堆);
3)栈又分为java虚拟机栈和本地方法栈主要用于方法的执行。