JVM运行时内存区域

来源:互联网 发布:中国有多少网络作家 编辑:程序博客网 时间:2024/05/21 12:00

JVM运行时内存区域

Java程序的执行首先是源文件编译成字节码文件,JVM通过类加载将用到的字节码文件加载,然后交由JVM执行引擎执行。执行过程中,JVM会使用一块内存来保存运行时的数据以及相应的信息,这块内存就是Java虚拟机运行时数据区,也就是我们说的Java内存区域。运行时数据区划被分为几个部分,具体可以分为线程共享区域和线程隔离区域两大部分。

============图==============

  • 线程共享区域:随着虚拟机进程的启动而存在,当前进程的所有线程都可以访问该区域。

  • 线程隔离区域:随着进程线程的启动而存在,且随着线程的结束而销毁,每个线程都拥有属于自己的区域,相互之间不能访问。

在往下分运行时数据区一共可以分为五大部分,方法区、Java虚拟机栈、堆、本地方法栈、程序计数器。其中方法区、堆,是线程共享。Java虚拟机栈、本地方法栈、程序计数器,是线程私有的。

相比较线程共享区域,线程隔离区域会稍微好理解一点,我们就先从线程隔离区域开始讲起。

线程隔离区域


程序计数器

程序计数器是当前线程执行的字节码行号指示器,如果当前线程执行的是Java方法记录的就是当前执行的字节码行号,如果执行的Native方法则计数器值为空。

由于Java的多线程是抢占式的,抢占式调度指的是每条线程执行的时间、线程的切换都由系统控制,系统控制指的是在系统某种运行机制下,可能每条线程都分同样的执行时间片,也可能是某些线程执行的时间片较长,甚至某些线程得不到执行的时间片。在这种机制下,一个线程的堵塞不会导致整个进程堵塞。因而为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器。

程序计数器是唯一一个不会抛出OOM异常的区域。

Java虚拟机栈

Java虚拟机栈是存放栈帧的地方,每个方法在执行的同时都会创建一个栈帧用于存放局部变量表、操作数栈、方法出口信息等。每一个方法的执行过程,都对应着一个栈帧在虚拟机栈入栈出栈过程。在这里说下局部变量表,顾名思义就是用来存储编译期可知的局部变量。局部变量表存储单位是Slot,对于基本数据类型(出了long和double)、对象引用都只用一个Slot。long和double则会占用2个Slot空间。

本地方法栈

本地方法栈看名字就和Java虚拟机栈比较类似,确实两只是作用对象不同。本地方法栈作用对象是Java方法,本地方法栈作用对象是Native方法。本地方法栈并不是一定要单独存在,例如HostSpot虚拟机就将两者合并为一个。

线程共享区域


方法区

方法区是存储被虚拟机加载的类信息、常量、静态变量、即时编译(jit)后的代码等数据。以上是虚拟机规范里的,但不同虚拟机实现细节上会有所偏差,比如HotSpot虚拟机,方法区在jdk1.7之前称为永久代(PermGen)。

HotSpot使用永久代来实现方法区,让GC分代收集扩展至方法区,如此一来HotSpot的垃圾收集器也可以用来管理方法区。由于方法区主要存储类的相关信息,所以对于动态生成类的情况比较容易出现永久代的内存溢出。最典型的场景就是,在 jsp 页面比较多的情况,容易出现永久代内存溢出。

在jdk1.8的使用HotSpot使用元空间(Metaspace)来代替永久代,因为永久代有PermSize上限更容易照成内存溢出。而且永久代会为 GC 带来不必要的复杂度,回收效率偏低(因为方法区存储的东西存活时长比较久,例如被加载的类)。而元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。

在这里说明一下,移除永久代的工作从JDK1.7就开始了。JDK1.7中,存储在永久代的部分数据就已经转移到了Java Heap或者是 Native Heap。但永久代仍存在于JDK1.7中,并没完全移除。

运行时常量池

运行时常量池是方法区中用来存编译期产生的字面量、符号引用和运行时产生的常量。编译期产生的字面量和符号引用刚开始是在class文件的常量池中,当类加载后进入运行时常量池。运行时常量池相对于class文件常量池具有的一个重要特征是具备动态性,java语言并没有要求常量一定只有编译期才能产生,运行期间也可能将新的常量放入运行时常量池,例如String的intern方法。

堆是Java虚拟机所管理的内存最大的一块,是用来存放对象实例的。同时堆也是垃圾收集器管理的主要区域,从内存回收角度来看,由于目前的收集器基本都采用分代收集算法,所以堆还可以细分为:新生代和老年代,相应的新生代和老年代也可以细分下去。堆只是在逻辑上连续的,在物理上可以不连续。

直接内存


题外话,直接内存并不是JVM运行时内存区域但平常会用到,也会导致OOM。直接内存不受java堆得分配,是直接在物理内存上。jdk1.4加入的nio,引入了一种基于通道与缓存的io方式,使用Native函数直接分配堆外的内存,然后通过存储在Java堆得DirectByteBuffer对象进行操作。避免了数据在Java堆和Native堆来回复杂,大小受本机内存影响。

总结


图待补充

原创粉丝点击