深入理解JVM第三版笔记(4)-运行时数据区域

运行时数据区域

程序计数器

可以看作是当前线程所执行的字节码的行号指示器,字节码解释器就是通过改变这个计数器的值来选取下一条要执行的字节码指令,通过这机制可以实现分支,循环,跳转,异常处理,线程恢复等.

虚拟机栈

为线程私有的,它的生命周期与线程相同.它描述的是Java方法执行的内存模型,每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧用于存储局部变量表,操作数栈,动态链接,方法出口等.

局部变量表存放了编译期可知的各种基本数据类型(boolean,char,byte,short,int,long,float,double),引用类型(reference类型,可以是指针或者句柄),和returnAddress类型.这些数据类型在局部变量表中以局部变量槽表示(slot),其中double和long占用2个slot,其他的占用1个,进入一个方法时,这个方法需要在栈帧占用多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小.这里的大小指的是slot的数量,不同虚拟机中一个slot占用32bit或者64bit完全由虚拟机的实现自行决定.

本地方法栈

与虚拟机栈发挥的作用类似,区别是虚拟机栈为虚拟机执行的Java方法服务,本地方法栈为虚拟机使用的本地方法服务.

Java堆

一般来说,堆是虚拟机所管理的内存中最大的一块,几乎所有的对象实例都在这里分配内存.

从分配内存的角度看,所有线程共享的对中可以划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB)以提升对象分配时的效率,但无论怎样划分,都不会改变Java堆中存储内容的共性,将堆细分的目的是为了更好地会回收内存或者更好的分配内存.

方法区

用于存储已加载的类型信息,常量,静态变量,即时编译器编译后的代码缓存等数据.

永久代是JDK8以前HotSpot方法区的实现,二者并不是等价的,其他优秀的JVM并不存在永久代如JRockit,J9等,为了吸收其他VM的优秀特性并达成统一,从JDK6开始逐步放弃永久代,JDK7移除了永久代中的字符串常量池,静态变量,JDK8完全移除,改用与JRockit和J9一样在本地内存中实现的元空间(Meta space)来代替.

这个区域的垃圾回收的确较少出现,回收的主要是针对常量池和卸载类型,但这块区域的回收又是必要的,很多严重的Bug就是由于未对这块区域完全回收导致的.

运行时常量池

它是方法区的一部分,Class文件除了有类的版本,字段,方法,接口等描述信息外,还有一项信息就是常量池表,用于存放编译器生成的各种字面量和符号引用,这些内容将在类加载后存放到方法区的运行时常量池中.

一般来说除了保存Class文件的符号引用外,还会把符号引用翻译出来的直接引用也存储到运行时常量池中.

另一个相对于Class文件常量池来说重要的特性是具备动态性,也就是说并非预置在Class文件常量池中的内容才会进入方法区运行时常量池,运行期间也可以将新的常量放入池中,运用的比较多的是String的intern()方法.

常量池包括:

  1. 字面量(字符串,final int型值)

  2. 符号引用,包括:

    • 类和接口的全限定名
    • 字段名称和描述符
    • 方法名称和描述符

直接内存

直接内存并不是运行时数据区的一部分,JDK1.4中新加入了NIO,引用了一种基于通道与缓冲区的I/O方式,它可以使用Native函数直接分配堆外内存,然后通过DirectByteBuffer对象作为这块内存的引用来操作,在一些场景能显著提升性能,因为避免了Java堆和Native堆中的来回复制数据.

直接内存的分配不会受Java堆的大小限制,但是内存会受到物理内存或Swap分页大小和处理器寻址空间的限制,如果忽略限制直接内存,使得各个内存区域大小总和超过物理内存限制,传统而导致动态扩展时出现OOM异常.

posted @ 2020-04-16 20:43  柿子君  阅读(158)  评论(0)    收藏  举报