java的内存区域

虚拟机在java程序运行的过程当中,会把它所管理的内存划分红为若干个不一样的数据区。这些不一样的数据区的做用、建立和销毁的时候也是不一样的,有的区域随着虚拟机的进程的启动和存在,有的区域则依赖于用户线程的启动和结束而创建和销毁。按照java虚拟机SE7版本的规定,java虚拟机内存区域主要包含以下图所示的几个数据区域。java

       

java虚拟机运行时数据区算法

 

1.程序计数器数据结构

程序计数器是是一块由每一个线程独有的一块较小的内存空间,是独立存储的,线程与线程之间的计数器是互不影响的。当一个线程正在执行一个java方法时,这个线程的程序计数器记录的是正在执行的虚拟机字节码指令的地址;若是执行的是native方法,那这个计数器就不记录任何数据,计数器的值是为空的。另外,该内存区域是也在java虚拟机规范中惟一一个没有规定任何OutOfMemoryError(内存溢出)状况的区域。这个应该很好理解,由于这块区域中,除了虚拟机字节码的地址,不会记录其余的东西,因此通常也不会发生内存溢出的问题。函数

 

2.虚拟机栈ui

和程序计数器同样,虚拟机栈也是由每一个线程独有的内存空间,它的生命周期和线程是一致的。虚拟机栈是java方法执行的内存模型:每一个方法在执行的同时都会建立一个栈帧,用来存储方法执行过程当中的变量表、操做数栈、动态连接、方法出口等信息。每个方法从调用到执行完成的过程,就对应着一个栈帧在虚拟机中入栈和出栈的过程。若是下图所示。线程

方法在虚拟机中调用的过程指针

 

局部表量表:存放了编译器可知的各类基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用类型(reference类型,它不是对象自己,多是一个指向对象起始地址的引用指针,也可能指向一个表明对象的句柄或者与此对象相关的一个位置)和returnAddress类型(指向了一条字节码指令的地址,我感受多是对应下一个帧的方法字节码地址)。对象

另外,在这个区域中java虚拟机规定了两种异常情况:StackOverflowError(栈溢出异常)和OutOfMemoryError(内存溢出异常)。接口

  • 栈溢出:当线程请求的栈深度大鱼虚拟机所容许的深度时,抛出。
  • 内存溢出:若是虚拟机支持动态扩展虚拟机栈(当前大部分虚拟机均可以动态扩展,不过也容许固定长度的虚拟机栈),当扩展时没法申请到足够的内存时,就会抛出内存溢出。

 

3.本地方法栈生命周期

本地方法栈和虚拟机栈所发挥的做用是 同样的,他们之间的区别是虚拟机栈执行的是java代码,而本地方法栈是为虚拟机执行Native方法而服务的。在虚拟机规范中,对于本地方法中使用的语言、使用方式与数据结构么有强制规定,虚拟机能够根据本身的须要自行实现它。例如,对于咱们熟悉的Sun的HotSpot虚拟机,它直接把本地方法栈和虚拟机栈合二为一。与虚拟机栈同样,本地方法栈也有栈溢出和内存溢出。

 

4.堆

对于平常应用中,java堆一般都是Java虚拟机所管理中所占内存最大的一块内存区域。这块内存区域是被全部的线程所共享的,它的生命周期伴随着java虚拟机的启动和关闭而建立和消亡。按照java虚拟机的规范的描述,在这个区域内是存放全部的java对象实例的地方。实际的虚拟机实现上,这个区域也是存放了几乎全部的java对象实例。值得注意的是,随着技术的发展,特别是JIT技术(Just-In-Time Compiler:即时编译技术,它是一个把Java的字节码(包括须要被解释的指令的程序)转换成能够直接发送给处理器的指令的程序。当你写好一个Java程序后,源语言的语句将由Java编译器编译成字节码,而不是编译成与某个特定的处理器硬件平台对应的指令代码(好比,Intel的Pentium微处理器或IBM的System/390处理器)。字节码是能够发送给任何平台而且能在那个平台上运行的独立于平台的代码。)的发展,全部的对象都在堆内存上分配也不是那么的绝对了。

而咱们经常所说的Java虚拟机的垃圾回收机制一般就是针对这块区域的,因此这段区域也一般被亲切地叫作GC堆。如今GC堆回收算法基本都是采用的分带回收的算法,这样作的目的是为了提升垃圾回收的效率,把java对象实例按照可能的生命的周期分配在不一样的堆空间,也就是不一样的代中,这样就能够根据不一样的代而采用不一样的回收算法,从而提升GC的效率。具体怎么分代,不一样的虚拟机能够有不一样的实现,简单能够分为新生代和老年代,再详细点还能够分为:Eden代、From Suivive和To Survive等等。

另外,在Java中咱们经常使用的Thread Local(Tread Local Allocation Buffer,TLAB)线程私有内存区域并非在线程私有的虚拟机栈或者是本地方法栈中。实际上,Thread Local也是在堆内存中分配的,只是从对内存中划分出来部分区域做为线程私有的。因此,堆内存全部的对象都是线程共享的,起码从这点来看不是那么的绝对。

Java对内存区域是逻辑上连续的存储空间,物理上能够不连续。在虚拟机实现时,堆的大小能够是固定的,也能够是可扩展的,如今基本上主流的虚拟机的堆大小都是可扩展的(能够经过-Xmx:JVM初始内存和-Xms:JVM最大的内存),当堆中没有内存完成实例分配,并且堆也没法再扩展时,将会抛出OutOfMemoryError。

 

5.方法区

方法区和java堆同样,是各个线程共享的内存区域,它用于存储已经被虚拟机加载的类信息 、常量、静态变量、即时编译器编译后的代码等数据。虽然java虚拟机规范把方法去描述为堆的一个逻辑部分,可是其实它还有另外一个别名,叫作Non-Heap,目的应该是与java堆区分开。方法区和堆同样,能够有物理上不连续可是逻辑上连续的内存空间,能够选择固定的内存大小,也能够选择扩展内存,另外,它还能够不实现垃圾回收。跟堆同样,当方法去没法知足内存分配的需求时,就会抛出OutOfMemoryError异常。

运行常量池是方法去的一部分,Class文件中除了有类的版本、字段、方法、接口等信息描述外,还有一项信息就是常量池,用于存放编译期生成的各类字面量和符号引用,这部份内容将在类加载后进入方法区的运行时常量池存放。java并不要求常量必定是编译期才会产生的,也就是并不是预置在Class文件中的常量池才会进入到运行时常量池中,运行期间也能够产生新的常量放入常量池,这种特性经常使用的就是String类的intern()方法。

 

6.直接内存

直接内存并非虚拟机运行时内存的一部分,固然也不是java虚拟机规范中定义的内存区域。可是这部分区域在java应用中也是会被频繁使用的部分,也会致使OutOfMemoryError。在JDK1.4中引入了NIO类,它是一种基于通道(Channel)和缓冲区(Buffer)的IO方式,它可使用Native函数库在堆外分配内存,而后经过一个存储在Java堆中的DirectByteBuffer对象做为这块内存的引用进行操做。显然,这种方式的直接内存跟java堆内存没有直接的关系,因此它的内存空间的大小并不会收到堆内存大小的限制。固然,它仍是会收到本机的总内存以及处理器寻址空间的限制。若是在配置JVM运行时参数时忽略了这部份内存,极有可能发生运行时各个内存区域大小的总和大于物理内存的大小而致使动态扩展时发生OutOfMemoryError异常。

 

总结:Java虚拟机中大体分为线程私有和线程共享等两大内存区域。其中线程私有的内存区域分为:虚拟机栈、本地方法栈和程序计数器;线程共享的区域可分为:方法区和堆(严格上来讲,方法区也是属于堆内存的一部分,这里稍微区分开了);另外还有一个不属于上述内存区域的java直接内存,是在堆以外独立分配的内存空间,其大小不受堆内存的限制。每一个内存区域的大小、做用和声明周期各有不一样。GC机制即垃圾回收主要发生在堆内存区域,堆内存区域在GC时候按照划分的不一样区域(即常说的分代)使用不一样的垃圾回收的算法来提升垃圾回收的效率。

相关文章
相关标签/搜索