JVM总结以内存区域

Java虚拟机在执行Java程序的过程当中会把它所管理的内存划分为若干个不一样的数据区域。这些区域都有各自的用途,有的区域是线程共享的,有的区域是线程隔离的。以下图:java

程序计数器

程序计数器(Program Counter Register)是一块较小的内存空间,它能够看作是当前线程所执行的字节码的行号指示器。字节码解释器工做时就是经过这个计数器的值来选取下一条须要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都须要依赖这个计数器来完成。缓存

程序计数器是线程私有的内存区域,保证线程切换后能恢复到正确的执行位置。spa

执行Java方法的时候,这个计数器记录的是正在执行的虚拟机字节码的指令的地址;执行native方法的时候,计数器的值为空(null)。线程

Java虚拟机栈

Java虚拟机栈(Java Virtual Machine Stacks)描述的是Java方法执行的内存模型:每一个方法在执行的同时都会建立一个栈帧(Stack Frame)用于存储局部变量表、操做数栈、动态连接、方法出口灯信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。3d

Java虚拟机栈也是线程私有的内存区域,生命周期和线程相同。orm

栈帧对象

在活动线程中,只有虚拟机栈顶的栈帧才是有效的,称为当前栈帧(Current Stack Frame),这个栈帧所关联的方法称为当前方法(Current Method)。执行引用所运行的全部字节码指令都只针对当前栈帧进行操做。栈帧的概念结构以下图所示:blog

  • 局部变量表:局部变量表是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。在Java程序编译为Class文件时,就在方法表的Code属性的max_locals数据项中肯定了该方法须要分配的最大局部变量表的容量。
  • 操做数栈:操做数栈也常被称为操做栈,它是一个后入先出栈。同局部变量表同样,操做数栈的最大深度也是编译的时候被写入到方法表的Code属性的max_stacks数据项中。操做数栈的每个元素能够是任意Java数据类型,包括long和double。32位数据类型所占的栈容量为1,64位数据类型所占的栈容量为2。栈容量的单位为“字宽”,对于32位虚拟机来讲,一个”字宽“占4个字节,对于64位虚拟机来讲,一个”字宽“占8个字节。
  • 动态连接:每一个栈帧都包含一个指向运行时常量池中该栈帧所属性方法的引用,持有这个引用是为了支持方法调用过程当中的动态链接。在Class文件的常量池中存有大量的符号引用,字节码中的方法调用指令就以常量池中指向方法的符号引用为参数。这些符号引用一部分会在类加载阶段或第一次使用的时候转化为直接引用,这种转化称为静态解析。另一部分将在每一次的运行期期间转化为直接引用,这部分称为动态链接。
  • 返回地址:在方法退出以前,都须要返回到方法被调用的位置,程序才能继续执行,方法返回时可能须要在栈帧中保存一些信息,用来帮助恢复它的上层方法的执行状态。通常来讲,方法正常退出时,调用者PC计数器的值就能够做为返回地址,栈帧中极可能会保存这个计数器值。而方法异常退出时,返回地址是要经过异常处理器来肯定的,栈帧中通常不会保存这部分信息。方法退出的过程实际上等同于把当前栈帧出栈,所以退出时可能执行的操做有:恢复上层方法的局部变量表和操做数栈,把返回值(若是有的话)压入调用都栈帧的操做数栈中,调用PC计数器的值以指向方法调用指令后面的一条指令等。

java虚拟机栈能够抛出以下异常:生命周期

  • StackOverflowError:线程请求的栈深度大于JVM容许的深度,抛出该异常;
  • OutOfMemoryError:若是虚拟机栈能够动态扩展,当扩展时没法申请到足够的内存,抛出该异常。

本地方法栈

本地方法栈与虚拟机栈的做用是类似的,它们的区别是:进程

  • 虚拟机栈为JVM执行的Java方法服务;
  • 本地方法栈为JVM使用到的Native方法服务。

与虚拟机栈同样,本地方法栈也会抛出StackOverflowError和OutOfMemoryError。

Java堆

栈表明了处理逻辑,而堆表明了数据。

Java堆是Java虚拟机所管理的内存最大的一块,也是垃圾收集器管理的主要区域。

Java堆是全部线程共享的,堆中的共享常量和缓存能够被全部栈访问,节省了空间。

为了更好的回收内存,或者更好的分配内存,将Java堆细分为新生代和老年代。

  • 新生代(还可细分为den区、Form Survivor区和To Survivor区)
  • 老年代

关于每一个区如何回收内存,什么时候回收内存在下一篇中再总结。

堆的大小也是能够调整的,能够经过虚拟机参数-Xmx和-Xms控制。一样,在该区域,若是没有内存分配给对象实例,而且堆也没法再扩展,会抛出OutOfMemoryError异常。

方法区

方法区主要用来存储已经被JVM加载的类信息、常量、静态变量等。

在过去(当自定义类加载器使用不广泛的时候),类几乎是“静态的”而且不多被卸载和回收,所以类也能够被当作“永久的”。另外因为类做为JVM实现的一部分,它们不禁程序来建立,由于它们也被认为是“非堆”的内存。

方法区也是全部线程共享的内存区域。

方法区和Java堆同样,也是须要进行垃圾回收的,该区域的内存回收目标主要是针对常量池的回收的和对类型的卸载。

当方法区没法知足内存分配需求时,将抛出OutOfMemoryError异常。

Hotspot废除永久代

永久代的问题

永久代是Hotspot中的一个概念,其余JVM的实现未必有,例如JRockit就没有(只要不触碰进程可用内存上限就不会出问题)。Hotspot使用在内存中划分出一块区域来存储类的元信息、类变量以及内部字符串等,称为永久代,把它做为方法区来使用。

永久代是一段连续的内存空间,咱们在JVM启动以前能够经过设置-XX:MaxPermSize的值来控制永久代的大小,32位机器默认的永久代的大小为64M,64位的机器则为85M。永久代的垃圾回收和老年代的垃圾回收是绑定的,一旦其中一个区域被占满,这两个区都要进行垃圾回收。

  • 问题1:因为咱们能够经过‑XX:MaxPermSize 设置永久代的大小,一旦类的元数据超过了设定的大小,程序就会耗尽内存,并出现内存溢出错误(OOM)。
  • 问题2:永久代中的元数据可能会随着每一次Full GC发生而进行移动。而且为永久代设置空间大小也是很难肯定的,由于这其中有不少影响因素,好比类的总数,常量池的大小和方法数量等。
  • 问题3:HotSpot虚拟机的每种类型的垃圾回收器都须要特殊处理永久代中的元数据。

迎来元空间

Hotspot在Java8中将类的元数据移到了一个与堆不相连的本地内存区域,这个区域就是元空间

因为类的元数据分配在本地内存中,元空间的最大可分配内存就是系统可用内存空间。用户能够经过-XX:MaxMetaspaceSize为元空间设置一个空间可用最大值,若是不进行设置,JVM会根据类的元数据大小动态增长元空间的容量。

每个类加载器的存储区域都称做一个元空间,全部的元空间合在一块儿就是咱们一直说的元空间。当一个类加载器被垃圾回收器标记为再也不存活,其对应的元空间会被回收。在元空间的回收过程当中没有重定位和压缩等操做。可是元空间内的元数据会进行扫描来肯定Java引用。

元空间虚拟机负责元空间的分配,其采用的形式为组块分配。组块的大小因类加载器的类型而异。在元空间虚拟机中存在一个全局的空闲组块列表。当一个类加载器须要组块时,它就会从这个全局的组块列表中获取并维持一个本身的组块列表。当一个类加载器再也不存活,那么其持有的组块将会被释放,并返回给全局组块列表。

元空间存在的问题

元空间虚拟机采用了组块分配的形式,同时区块的大小由类加载器类型决定。类信息并非固定大小,所以有可能分配的空闲区块和类须要的区块大小不一样,这种状况下可能致使碎片存在。元空间虚拟机目前并不支持压缩操做,因此碎片化是目前最大的问题。

参考资料:

《深刻理解Java虚拟机》

Java永久代去哪儿了

相关文章
相关标签/搜索