Java虚拟机在执行Java程序的过程当中会把它所管理的内存划分为若干个不一样的数据区域。这些区域的用途各不相同,同时也依据着各自的执行规则,独立的建立和销毁数据。java
虚拟机内存的划分,如图所示:数组
线程之间互相独立的区域有:数据结构
虚拟机栈 、本地方法栈、程序计数器多线程
线程能够共享数据的区域:布局
方法区 、堆性能
每一个区域的做用分别以下:spa
程序计数器 Program Counter Register:线程
众所周知,虚拟机处理多线程时,是经过轮流的切换线程,来获取cpu的执行机会的。在虚拟机执行程序的过程当中,当线程执行到某一位置时,虚拟机将cpu的执行机会出让给了其余线程,此时原有线程的执行(防盗链接:本文首发自http://www.cnblogs.com/jilodream/ )位置须要被记录下来,而新获得执行机会的线程,又须要提供上次执行的位置,以此来保证程序中的多个线程能够持续的并行的执行下去。对象
程序计数器的做用就是将各个线程下次所执行的(字节码)行号(准确来讲是指令的地址)记录下来,以保证其下次执行时能够正确的执行。blog
根据程序计数器的做用,咱们能够知道:
一、每一个线程都在这个区域中都应该拥有一个只为本身提供服务的程序计数器,它们之间是独立存储,互不影响的存在。
二、咱们还能够知道,程序计数器只记录字节码的行号,所以当线程执行本地方法(Native method)时,计数器的值是空。
三、程序计数器所耗费的内存空间很是小,所以这个区域是不会抛出OutOfMemoryError错误的。
虚拟机栈 VM Stack:
线程想要正常的运行下去,单靠程序计数器来记录行号是远远不止的。线程还须要拥有本身的运行空间,在这个空间中,虚拟机能够保存方法的执行顺序、方法的内部局部变量,方法在运算时,所须要的内存空间等。
在数据结构中,栈的特性最知足方法的进入返回的结构的。而这块区域的主(防盗链接:本文首发自http://www.cnblogs.com/jilodream/ )要做用就是线程在执行java方法时所须要记录的数据。所以咱们将这块区域称之为虚拟机栈。可是要记住这里与咱们在工做中一般指的栈并不等同,这个我会在后边介绍。
虚拟机栈的结构以下:
而对于每个栈帧内部的划分又是这样的:
每一部分的做用以下:
(1)局部变量表:每个方法均可以定义一个只属于本身的局部变量,当这个方法运行结束后,这个局部变量的生命周期也就宣告结束。因此每个方法都应该拥有一个块属于本身的内存区域用来保存方法内部定义的局部变量。这块区域就是局部变量表,咱们日常工做中所指的栈,实际上指的是虚拟机栈中的栈帧中的局部变量表。
(2)操做数栈:每一个方法的内部均可以计算数据,而计算数据势必须要拥有一块内存区域,为虚拟机用来进行数值计算。所以在栈帧中,就须要有一块区域专门为当前方法计算数据使用,它就是操做数栈。
在每进行一次完整的计算以后,栈中的数据都已经出栈,因此操做数栈的空间在一个方法内部是能够反复使用的。因此虚拟机在分配内存大小时,只分配当前方法,单次完整计算所须要的最大内存空间给当前栈帧,以减小内存的消耗。
同时为了增长运行效率,减小数据的不断复制,在大部分虚拟机的实现中,将当前方法的局部变量表和上层方法的操做数栈的内存造成部分重叠,从而减小参数的不断复制而引发的性能消费。(防盗链接:本文首发自http://www.cnblogs.com/jilodream/ )
(3)动态链接:
虚拟机在执行方法时有两种形式被用来肯定执行指令所对应的方法,
第一种是类加载时,能够直接肯定要执行的方法,譬如静态方法,私有方法,final方法等。这种形式叫作静态解析。
第二种是在真正运行时,根据对象的真实引用来判断当前真正要执行的方法。这种形式称之为动态链接。
在字节码文件中,都存在一个常量池,在这个常量池中保存有大量的符号引用,这个符号引用是每个方法的间接引用。在字节码指令的中,使用的是这个符号引用。可是在运行时阶段,确定须要调用到要执行方法在内存中真实的地址。这就须要将间接引用转化成直接引用。而这里的“动态链接”就是为了保证在运行时阶段,方法能够正确的找到要调用的方法,每一个栈帧将本身在运行时常量池中所对应的真实地址记录的位置。
这里须要注意的是,在栈帧中的动态链接和查找符号引用为真实引用中的动态链接,是两个概念。前者表示的是一个区域,后者表示的是一种查找方式。
(4)返回地址:
退出当前方法的方式有两种,第一种是遇到返回指令时,正常的退出当前方法。另外一种形式是遇到没有捕获而被抛出的异常。不管何种返回形式,在方法退出后,栈帧的顶端都应是当前退出方法的上层方法。同时上层方法的执行状态也须要根据当前的返回结果从新调整。因此每一个栈帧能够利用“返回地址”这块区域帮助上层方法恢复状态。
(5)附加信息:对于虚拟机规范中没有申明的,拥有指定存放位置的信息能够由各个虚拟机本身决定,放置到这个区域中。
本地方法栈 Native Stack
在虚拟机中,不但运行java方法,还会运行本地方法,也就是常见的Native 关键字修饰的方法。在虚拟机栈中,会为每一个线程独立的开辟一个专门运行java语言(更准确的说应该是字节码)的方法(防盗链接:本文首发自http://www.cnblogs.com/jilodream/ )栈,可是对于本地方法,则是使用另外的一块内存区域来保存线程的调用状态,这块区域就是本地方法栈。他的做用跟虚拟机栈基本类似,其区别就是一个为java方法服务,一个为Native发光法服务。在虚拟机规范中,对于本地方法栈中的结构、方法的语言、方式,都没有强制规定,各个虚拟机能够自由的实现它。
Java堆 Java Heap
咱们日常所说的,在堆中建立一个实例,指的就是这个堆。这是虚拟机所管理的内存中最大的一块。在虚拟机中,几乎全部的实例以及数组所分配的内存空间都会被放置在这个堆中。
因为java堆是对象实例的的主要存放位置,所以虚拟机的垃圾回收机制的主要工做区域。
根据Java的内存回收机制,咱们能够将堆的大小和内容划分红以下的形式:
根据java堆的特性,咱们也能够知道,这块区域是一块线程共享的区域。同时咱们也能够看出来,这块区域,所可使用在物理上非连续的内存,只要在逻辑上保持连续便可。
方法区 Method Area
方法区的主要做用是保存类信息、常量、静态变量以及即时编译后的代码等数据。这个区域中的数据仍然会被GC的代回收所涉及到。咱们日常所说的永久代,指的就是这个区域。
尽管这个区域也被称之为永久代,可是当数据进入这个区域中,仍然可能会被回收。这个区域的回收目标主要是常量池的回收,以及类型的卸载。
运行时常量池 Runtime Constant Pool
这块区域属于方法区的中的一块子区域。
在Class文件中,除了有类版本、字段、方法、接口等,还有一个信息区(防盗链接:本文首发自http://www.cnblogs.com/jilodream/ )域是常量池。常量池中的数据将会在类被加载后,存在到运行时常量池中。
而类文件中的常量池主要包括各类字面量和符号引用。符号引用在讲解栈帧时,有所涉及。
字面量能够理解为java语言中的常量,如字符串、final修饰的变量等。
符号引用则是指如下三种固定信息:
(1)类和接口的全限定名称
(2)字段的名称和描述符
(3)方法的名称和描述符
java语言在编译成Class文件后,并无关于方法和字段在内存中最终布局的信息。因此当虚拟机使用这些变量或方法时,须要先从常量池中,找到这些数据对应的符号引用,而后在方法的栈帧中的动态链接区域中找到其对应的内存真实位置。
在平常工做中,咱们常常会遇到两种内存溢出的错误:
一、OutOfMemoryError
二、StackOverflowError
OutOfMemoryError指的是一个区域中,因为数据的不断增长,致使区域没法再从物理内存总申请到更大的空间,或者是区域所申请的空间已经到达虚拟机运行参数所给该区域设定的最大值,那么就会抛出这个错误。
StackOverflowError则指的是内存中的栈结构在不断的入栈,最终致使栈的深度超过了虚拟机所容许的栈深度时,所抛出的错误。