笔记-Java基础之运行时数据区域。

java在程序执行阶段将内存划分为了几个区域。

但大致可以从堆栈,线程是否私有两个角度来看。

程序计数器

  线程私有,可以看做当前线程所执行的字节码的行号指示器。字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支,循环,跳转,异常处理,线程恢复等出功能都是需要有这个计数器来完成。

  由于java虚拟机的多线程是通过处理器在多线程之间轮流切换分配时间执行的方式来执行,因此,为了切换线程后可以恢复到正确的执行位置,每个线程都需要有一个独立的计数器,各线程之间的计数器互不影响。

  需要注意的是,线程如果在执行一个java方法,该计数器记录的是正在执行的虚拟机字节码指令的地址。如果线程正在执行一个native本地方法,则该计数器值为空(undefined)。该区域是唯一一个在java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。

 

Java虚拟机栈

  线程私有描述的是java方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧用于存储局部变量表,操作数栈,动态链接,方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈针在虚拟机栈中入栈到出栈的过程。

  通常,我们所说的堆栈中的栈就是指的java虚拟机栈,或者说是虚拟机栈中的局部变量表部分,就是我们方法中的各种变量。

  java虚拟机规范中,对该部分规定了两种异常状况:

1.如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError。

2.如果虚拟机支持扩展,扩展时无法申请到足够的内存,将会抛出OutOfMemoryError。

 

本地方法栈

    线程私有本地方法栈和虚拟机栈所发挥的作用非常相似,区别就在于,虚拟机栈为java虚拟机调用java方法时服务,而本地方法栈则为虚拟机使用到的Native方法服务。

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

 

Java堆(GC堆)

  线程共享,几乎所有的对象实例都是在这里分配内存。这一点在Java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配,Java堆是垃圾收集器管理的主要区域。

  从内存回收的角度来看,Java堆还可以细分为:

  新生代:

      Eden(伊甸区):一开始所有创建的对象实例几乎都在在这里,生命周期较短。

      From Survivor(生存区1):需要和生存区2互相配合。垃圾收集器在伊甸区中收集一次后,将存活的对象移动到生存区1)。

      To Survivor(生存区2):需要和生存区1互相配合。两个生存区始终会保持一个是空的,在Eden区中存活下来的所有对象会在另一个生存区,某个生存区满了的时候会触发GC,将不可达对象清除,释放空间,将剩下来的对象移动到空的生存区并整理空间,如此反复循环。某个对象超过一定的存活次数,就将该对象移动到老年代。

  老年代:

      在老年代中的对象生命周期都是比较长的,GC触发的次数也比较少。

      根据Java虚拟机规范的规定,Java堆可以处于物理上不连续,只要逻辑上是连续的即可。在现实时,既可以是固定大小,也可以是可扩展的。如果在堆中没有内存完成实例分配,并且堆也无法在扩展时,将会抛出OutOfMemoryError

   

方法区(Non Heap-非堆)

  线程共享,该区域用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它还有一个别名叫做Non Heap(非堆),目的应该是与Java堆区分开来。

 很多人更愿意把方法区称为“永久代”,但是其实两者并不等价,仅仅是因为HotSpot虚拟机的设计团队选择把GC分代收集扩展至方法区,或者说使用永久代来实现方法区而已,这样HotSpot的垃圾收集器可以像管理Java堆一样管理这部分内存,能够省去专门为方法区编写内存管理代码的工作。

 Java虚拟机对方法区的限制非常宽松,除了和堆一样不需要连续的内存和可以选择固定大小或者可扩展外,还可以选择不实现垃圾收集。该区域垃圾收集行为较少,所有,进入该区域并不是真正意义上的永久了。

 根据Java虚拟机规范的规定,当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。

运行时常量池

   方法区的一部分,也自然线程共享,Class文件有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中。运行时常量池和常量池的特点如下:

    运行时常量池

    1.属于方法区的一部分。

    2.动态性,比如调用String.intern()方法,动态加入常量。

    3.虚拟机对运行时常量池没有作任何细节的要求。

    4.数据来源分为两部分,一部分是class文件中的常量池,另一部分则是运行时代码中的动态常量。

    常量池

    1.属于Class文件的类信息。

    2.在编译器几乎就可以确定好了所有常量。

    3.因为属于Class信息,Java虚拟机对Class文件每一部分都有严格的要求(加载,初始化,验证等等),因此格式有严格的规定。

    4.数据来源就是Class文件。

既然运行时常量池是方法区的一部分,自然受到方法区内存的限制,当常量池无法在申请到内存时会抛出OutOfMemoryError0异常。

需要注意的是运行时常量池在jdk1.6和之后的版本在代码中的表现会有所差异,具体为:

    例如String.intern()方法,在 jdk1.6及以前,虚拟机会先尝试在运行时常量池中看是否有相同的常量,有就直接返回池中的引用,没有的话会先将该字符串实例复制到池中,然后返回引用。

    而在1.6只有的版本中,这个操作改成了,intern()方法不会在复制实例,只是在运行时池中记录首次出现的实例引用。

 

直接内存

  直接内存并不是虚拟机运行时的一部分,也不是Java虚拟机规范中定义的内存区域。但是该区域也可能导致OutOfMemoryError。

  JDK1.4中加入了NIO类,引入了一种基于通道与缓冲区的I/O方式,可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆2中的DirectByteBuffer对象作为这块内存的引用进行操作。

  显然,Java堆外的内存时不受Java堆大小的限制的,但是这直接内存是受本机总内存大小以及处理器寻址空间的限制。当各个你呢你存区域总和大于物理内存限制从而导致动态扩展时出现OutOfMemory。

猜你喜欢

转载自blog.csdn.net/helianus/article/details/88896400
今日推荐