Java虚拟机的内存结构

咱们都知道虚拟机的内存划分了多个区域,并非一张大饼。那么为何要划分为多块区域呢,直接搞一块区域,全部用到内存的地方都往这块区域里扔不就好了,岂不痛快。是的,若是不进行区域划分,扔的时候确实痛快,可用的时候再去找怎么办呢,这就引入了第一个问题,分类管理,相似于衣柜,系统磁盘等等,为了方便查找,咱们会进行分区分类。另外若是不进行分区,内存用尽了怎么办呢?这里就引入了内存划分的第二个缘由,就是为了方便内存的回收。若是不分,回收内存须要所有内存扫描,那就慢死了,内存根据不一样的使用功能分红不一样的区域,那么内存回收也就能够根据每一个区域的特定进行回收,好比像栈内存中的栈帧,随着方法的执行栈帧进栈,方法执行完毕就出栈了,而对于像堆内存的回收就须要使用经典的回收算法来进行回收了,因此看起来分类这么麻烦,实际上是大有好处的。java

提到虚拟机的内存结构,可能首先想起来的就是堆栈。对象分配到堆上,栈上用来分配对象的引用以及一些基本数据类型相关的值。可是·虚拟机的内存结构远比此要复杂的多。除了咱们所认识的(尚未认识彻底)的堆栈之外,还有程序计数器,本地方法栈和方法区。咱们平时所说的栈内存,通常是指的栈内存中的局部变量表。下面是官方所给的虚拟机的内存结构图
hjKrsEcCSHCZBGXsccyCzjGwQkk4K3ys.jpg算法

从图中能够看到有5大内存区域,按照是否被线程所共享可分为两部分,一部分是线程独占区域,包括Java栈,本地方法栈和程序计数器。还有一部分是被线程所共享的,包括方法区和堆。什么是线程共享和线程独占呢,很是好理解,咱们知道每个Java进行都会有多个线程同时运行,那么线程共享区的这片区域就是被全部线程一块儿使用的,无论有多少个线程,这片空间始终就这一个。而线程的独占区,是每一个线程都有这么一分内存空间,每一个线程的这片空间都是独有的,有多少个线程就有多少个这么个空间。上图的区域的大小并不表明实际内存区域的大小,实际运行过程当中,内存区域的大小也是能够动态调整的。下面来具体说说每个区域的主要功能。安全

程序计数器,咱们在写代码的过程当中,开发工具通常都会给咱们标注行号方便查看和阅读代码。那么在程序在运行过程当中也有一个相似的行号方便虚拟机的执行,就是程序计数器,在c语言中,咱们知道会有一个goto语句,其实就是跳转到了指定的行,这个行号就是程序计数器。存储的就是程序下一条所执行的指令。这部分区域是线程所独享的区域,咱们知道线程是一个顺序执行流,每一个线程都有本身的执行顺序,若是全部线程共用一个程序计数器,那么程序执行确定就会出乱子。为了保证每一个线程的执行顺序,因此程序计数器是被单个线程所独显的。程序计数器这块内存区域是惟一一个在jvm规范中没有规定内存溢出的。数据结构

java虚拟机栈,java虚拟机栈是程序运行的动态区域,每一个方法的执行都伴随着栈帧的入栈和出栈。 栈帧也叫过程活动记录,是编译器用来实现过程/函数调用的一种数据结构。栈帧中包括了局部变量表,操做数栈,方法返回地址以及额外的一些附加信息,在编译过程当中,局部变量表的大小已经肯定,操做数栈深度也已经肯定,所以栈帧在运行的过程当中须要分配多大的内存是固定的,不受运行时影响。对于没有逃逸的对象也会在栈上分配内存,对象的大小其实在运行时也是肯定的,所以即便出现了栈上内存分配,也不会致使栈帧改变大小。jvm

一个线程中,可能调用链会很长,不少方法都同时处于执行状态。对于执行引擎来说,活动线程中,只有栈顶的栈帧是最有效的,称为当前栈帧,这个栈帧所关联的方法称为当前方法。执行引擎所运行的字节码指令仅对当前栈帧进行操做。函数

Ft5rk58GfiJxcdcCzGeAt8fjkFPkMRdf.png

局部变量表:咱们平时所说的栈内存通常就是指栈内存中的局部变量表。这里主要是存储变量所用。对于基本数据类型直接存储其值,对于引用数据类型则存储其地址。局部变量表的最小存储单位是Slot,每一个Slot都能存放一个boolean、byte、char、short、int、float、reference或returnAddress类型的数据。工具

既然前面提到了数据类型,在此顺便说一下,一个Slot能够存放一个32位之内的数据类型,Java中占用32位之内的数据类型有boolean、byte、char、short、int、float、reference和returnAddress八种类型。前面六种不须要多解释,你们都认识,然后面的reference是对象的引用。虚拟机规范既没有说明它的长度,也没有明确指出这个引用应有怎样的结构,可是通常来讲,虚拟机实现至少都应当能今后引用中直接或间接地查找到对象在Java堆中的起始地址索引和方法区中的对象类型数据。而returnAddress是为字节码指令jsr、jsr_w和ret服务的,它指向了一条字节码指令的地址。性能

对于64位的数据类型,虚拟机会以高位在前的方式为其分配两个连续的Slot空间。Java语言中明确规定的64位的数据类型只有long和double两种(reference类型则多是32位也多是64位)。值得一提的是,这里把long和double数据类型读写分割为两次32读写的作法相似。不过,因为局部变量表创建在线程的堆栈上,是线程私有的数据,不管读写两个连续的Slot是不是原子操做,都不会引发数据安全问题。开发工具

操做数栈是一个后入先出(Last In First Out, LIFO)栈。同局部变量表同样,操做数栈的最大深度也在编译的时候被写入到字节码文件中,关于字节码文件,后面我会具体的来描述。操做数栈的每个元素能够是任意的Java数据类型,包括long和double。32位数据类型所占的栈容量为1,64位数据类型所占的栈容量为2。在方法执行的任什么时候候,操做数栈的深度都不会超过在max_stacks数据项中设定的最大值。spa

当一个方法刚刚开始执行的时候,这个方法的操做数栈是空的,在方法的执行过程当中,会有各类字节码指令向操做数栈中写入和提取内容,也就是入栈出栈操做。例如,在作算术运算的时候是经过操做数栈来进行的,又或者在调用其余方法的时候是经过操做数栈来进行参数传递的。

举个例子,整数加法的字节码指令iadd在运行的时候要求操做数栈中最接近栈顶的两个元素已经存入了两个int型的数值,当执行这个指令时,会将这两个int值和并相加,而后将相加的结果入栈。

操做数栈中元素的数据类型必须与字节码指令的序列严格匹配,在编译程序代码的时候,编译器要严格保证这一点,在类校验阶段的数据流分析中还要再次验证这一点。再以上面的iadd指令为例,这个指令用于整型数加法,它在执行时,最接近栈顶的两个元素的数据类型必须为int型,不能出现一个long和一个float使用iadd命令相加的状况。

本地方法栈 与虚拟机栈所发挥的做用是很是类似的,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。虚拟机规范中对本地方法栈中的方法使用的语言、使用方式与数据结构并无强制规定,所以具体的虚拟机能够自由实现它。甚至有的虚拟机(譬如Sun HotSpot虚拟机)直接就把本地方法栈和虚拟机栈合二为一。与虚拟机栈同样,本地方法栈区域也会抛出StackOverflowError和OutOfMemoryError异常。

方法区常常会被人称之为永久代,但这俩并非一个概念。首先永久代的概念仅仅在HotSpot虚拟机中存在,不幸的是,在jdk8中,Hotspot去掉了永久代这一说法,使用了Native Memory,也就是Metaspace空间。那么方法区是干吗的呢?咱们能够这么理解,咱们要运行Java代码,首先须要编译,而后才能运行。在运行的过程当中,咱们知道首先须要加载字节码文件。也就是说要把字节码文件加载到内存中。好了,问题就来了,字节码文件放到内存中的什么地方呢,就是方法区中。固然除了编译后的字节码以外,方法区中还会存放常量,静态变量以及及时编译器编译后的代码等数据。

堆,通常来说堆内存是Java虚拟机中最大的一块内存区域,同方法区同样,是被全部线程所共享的区域。此区域所存在的惟一目的就存放对象的实例(对象实例并不必定所有在堆中建立)。堆内存是垃圾收集器主要光顾的区域,通常来说根据使用的垃圾收集器的不一样,堆中还会划分为一些区域,好比新生代和老年代。新生代还能够再划分为Eden,Survivor等区域。另外为了性能和安全性的角度,在堆中还会为线程划分单独的区域,称之为线程分配缓冲区。更细致的划分是为了让垃圾收集器可以更高效的工做,提升垃圾收集的效率。

若是想要了解更多的关于虚拟机的内容,欢迎观看由我录制的<深刻理解Java虚拟机>这套视频教程。

相关文章
相关标签/搜索