运行时数据区域

Java虚拟机在执行Java的过程中会把管理的内存划分为若干个不同的数据区域。这些区域有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,而有的区域则依赖线程的启动和结束而创建和销毁。

Java虚拟机包括下面几个运行时数据区域:

程序计数器

程序计数器是一块较小的区域,它的作用可以看做是当前线程所执行的字节码的行号指示器。在虚拟机的模型里,字节码指示器就是通过改变程序计数器的值来指定下一条需要执行的指令。分支,循环等基础功能就是依赖程序计数器来完成的。

由于java虚拟机的多线程是通过轮流切换并分配处理器执行时间来完成,一个处理器同一时间只会执行一条线程中的指令。为了线程恢复后能够恢复正确的执行位置,每条线程都需要一个独立的程序计数器,以确保线程之间互不影响。所以程序计数器是“线程私有”的内存。

如果虚拟机正在执行的是一个Java方法,则计数器指定的是字节码指令对应的地址,如果正在执行的是一个本地方法,则计数器指定问空undefined。程序计数器区域是Java虚拟机中唯一没有定义OutOfMemory异常的区域。

Java虚拟机栈

和程序计数器一样也是线程私有的,生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会创建一个栈帧用于存储局部变量表,操作栈,动态链接,方法出口等信息。每一个方法被调用的过程就对应一个栈帧在虚拟机栈中从入栈到出栈的过程。

通常所说的虚拟机运行时分为栈和堆,这里的栈指的就是虚拟机栈或者说虚拟机栈中的局部变量表部分。

局部变量表存放了编译器可知的各种基本数据类型、对象引用和returnAddress类型(指向一条字节码指令的地址)。局部变量表所需的内存空间在编译器完成分配,当进入一个方法时这个方法需要在帧中分配多大的内存空间是完全确定的,运行期间不会改变局部变量表的大小。(64为长度的long和double会占用两个局部变量空间,其他的数据类型占用一个)

Java虚拟机栈可能出现两种类型的异常:1. 线程请求的栈深度大于虚拟机允许的栈深度,将抛出StackOverflowError。2.虚拟机栈空间可以动态扩展,当动态扩展是无法申请到足够的空间时,抛出OutOfMemory异常。

堆栈溢出错误一般是递归调用嘛。下面的代码就可以出现:

  1. package T20131009;
  2. public class StackOverflowTest {
  3. public static void main(String[] args) {
  4. method();
  5. }
  6. public static void method(){
  7. for(;;)
  8. method();
  9. }
  10. }

运行结果:

  

 2.OutOfMemoryError

  内存溢出一般是出现在申请了较多的内存空间没有释放的情形。下面的代码就可以出现:

  1. package T20131009;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. public class OutOfMemoryTest {
  5. public static void main(String[] args){
  6. List list=new ArrayList();
  7. for(;;){
  8. int[] tmp=new int[1000000];
  9. list.add(tmp);
  10. }
  11. }
  12. }

运行结果:

本地方法栈

本地方法栈和虚拟机栈基本类似,只不过Java虚拟机栈执行的是Java代码(字节码),本地方法栈中执行的是本地方法的服务。本地方法栈中也会抛出StackOverflowError和OutOfMemory异常。

堆是Java虚拟机所管理的内存中最大的一块。堆是所有线程共享的一块区域,在虚拟机启动时创建。堆的唯一目的是存放对象实例,几乎所有的对象实例都在这里分配,不过随着JIT编译器的发展和逃逸技术的成熟,栈上分配和标量替换技术使得这种情况发生着微妙的变化,对上分配正变得不那么绝对。

附:在Java编程语言和环境中,即时编译器(JIT compiler,just-in-time compiler)是一个把Java的字节码(包括需要被解释的指令的程序)转换成可以直接发送给处理器的指令的程序。当你写好一个Java程序后,源语言的语句将由Java编译器编译成字节码,而不是编译成与某个特定的处理器硬件平台对应的指令代码(比如,Intel的Pentium微处理器或IBM的System/390处理器)。字节码是可以发送给任何平台并且能在那个平台上运行的独立于平台的代码。

Java堆是垃圾收集器管理的主要区域,所以也称为“GC堆”。由于现在的垃圾收集器基本上都是采用分代收集算法,所以Java堆还可细分为:新生代和老生代。在细致一点可分为Eden空间,From Survivor空间,To Survivor空间。如果从内存分配的角度看,线程共享的Java堆可划分出多个线程私有的分配缓冲区。不过无论如何划分,都与存放内容无关,无论哪个区域,都是用来存放对象实例。细分的目的是为了更好的回收内存或者更快的分配内存。

Java堆可以是物理上不连续的空间,只要逻辑上连续即可,主流的虚拟机都是按照可扩展的方式来实现的。如果当前对中没有内存完成对象实例的创建,并且不能在进行内存扩展,则会抛出OutOfMemory异常。

方法区

方法区也是线程共享的区域,用于存储已经被虚拟机加载的类信息,常量,静态变量和即时编译器(JIT)编译后的代码等数据。Java虚拟机把方法区描述为堆的一个逻辑分区,不过方法区有一个别名Non-Heap(非堆),用于区别于Java堆区。

Java虚拟机规范对这个区域的限制也非常宽松,除了可以是物理不连续的空间外,也允许固定大小和扩展性,还可以不实现垃圾收集。相对而言,垃圾收集行为在这个区域是比较少出现的(所以常量和静态变量的定义要多注意)。方法区的内存收集还是会出现,不过这个区域的内存收集主要是针对常量池的回收和对类型的卸载。

一般来说方法区的内存回收比较难以令人满意。当方法区无法满足内存分配需求时将抛出OutOfMemoryError异常。

运行时常量池

运行时常量池是方法区的一部分,Class文件中除了有类的版本,字段,方法,接口等信息以外,还有一项信息是常量池用于存储编译器生成的各种字面量和符号引用,这部分信息将在类加载后存放到方法区的运行时常量池中。Java虚拟机对类的每一部分(包括常量池)都有严格的规定,每个字节用于存储哪种数据都必须有规范上的要求,这样才能够被虚拟机认可,装载和执行。一般来说,除了保存Class文件中描述的符号引用外,还会把翻译出来的直接引用也存储在运行时常量池中。

运行时常量池相对于Class文件常量池的另外一个重要特征是具备动态性,Java虚拟机并不要求常量只能在编译期产生,也就是并非预置入Class文件常量池的内容才能进入方法区的运行时常量池中,运行期间也可将新的常量放入常量池中。

常量池是方法区的一部分,所以受到内存的限制,当无法申请到足够内存时会抛出OutOfMemoryError异常。

对象访问

对象访问在Java语言中无处不在,即使是最简单的访问,也会涉及到Java栈,java堆,方法区这三个最重要的内存区域之间的关联关系。如下面的代码:

 Object obj = new Object();

       假设这段代码出现在方法体中,那么“Object obj”部分的语义将会反映到Java栈的本地变量表中,作为一个reference类型的数据存在。而“new Object();”部分的语义将会反应到Java堆中,形成一块存储Object类型所有实例数据值(Instance Data)的结构化内存,根据具体类型以及虚拟机实现的对象分布的不同,这块内存的长度是不固定的。另外,在JAVA堆中还必须包含能查找到此对象内存数据的地址信息,这些类型数据则存储在方法区中。

       由于reference类型在Java虚拟机中之规定了指向对象的引用,并没有规定这个引用要通过哪种方式去定位,以及访问到Java堆中的对象的具体位置,因此虚拟机实现的对象访问方式会有所不同。主流的访问方式有两种:句柄访问方式和直接指针。

1. 如果使用句柄访问方式,Java堆中将会划分出一块内存来作为句柄池,reference中存储的就是对象的地址,而句柄中包含了对象实例数据和类型数据各自的具体地址信息。

2. 如果通过直接指针方式访问,Java堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,reference中直接存储的就是对象的地址。

两种方式各有优势,局并访问方式最大的好处是reference中存放的是稳定的句柄地址,在对象被移动时,只会改变句柄中的实例数据指针,而reference本身不需要被修改。而指针访问的最大优势是速度快,它节省了一次指针定位的开销,由于对象访问在Java中非常频繁,一次这类开销积少成多后也是一项非常可观的成本。

具体的访问方式都是有虚拟机指定的,虚拟机Sun HotSpot使用的是直接指针方式,不过从整个软件开发的范围来看,各种语言和框架使用句柄访问方式的情况十分常见。

java内存划分的更多相关文章

  1. Java虚拟机系列(一)---Java内存划分

    Java和C++之间有一堵由内存管理和垃圾收集技术所围成的“高墙”,墙外的人想进去,墙内的人却想出来.  ------摘自<深入理解Java虚拟机> 作为一个Java程序员,因为虚拟机的好 ...

  2. 简单认识JAVA内存划分

    Java的内存划分为五个部分 那么又是哪五个部分呢?跟着我往下看! 介绍: 每个程序运行都需要内存空间,所以Java也不例外:而Java把从计算机中申请的这一块内存又进行了划分!而所在目的是为了让程序 ...

  3. 2019.12.10 定义数组及java内存划分

    //数据类型[ ] 数组名 = new 数据类型[元素个数或数组长度]; int[] x = new int[100]; //类型[] 数组名 = new 类型[]{元素,元素,……}; String ...

  4. java 虚拟机内存划分,类加载过程以及对象的初始化

    涉及关键词: 虚拟机运行时内存 java内存划分 类加载顺序  类加载时机  类加载步骤  对象初始化顺序  构造代码块顺序 构造方法 顺序 内存区域   java内存图  堆 方法区 虚拟机栈 本地 ...

  5. Java学习-内存划分及内存的调用关系

    一.JAVA内存划分 JAVA的内存可以划分为五个部分:堆.栈.方法区.本地方法区和寄存器. 堆(Heap):凡是new出来的东西都在堆中 如: integer = new Integer(2) // ...

  6. JVM之--Java内存结构(第一篇)

    最近在和同事朋友聊天的时候,发现一个很让人思考的问题,很多人总觉得JVM将java和操作系统隔离开来,导致很多人不用熟悉操作系统,甚至不用了解JVM本身即可完全掌握Java这一门技术,其实个人的观点是 ...

  7. Java内存数据模型

    本篇文章带来的是对Java内存数据模型的介绍,这对于我们深入理解Jvm虚拟机工作的原理和Java内存的划分大有裨益,好了,为了让我们理解的更为深刻,我们将会加入图片辅助的方法去理解. 本篇博文的目录: ...

  8. 浅析java内存管理机制

    内存管理是计算机编程中的一个重要问题,一般来说,内存管理主要包括内存分配和内存回收两个部分.不同的编程语言有不同的内存管理机制,本文在对比C++和Java语言内存管理机制的不同的基础上,浅析java中 ...

  9. Java 内存区域划分

            JVM的内存区域划分 学过C语言的朋友都知道C编译器在划分内存区域的时候经常将管理的区域划分为数据段和代码段,数据段包括堆.栈以及静态数据区.那么在Java语言当中,内存又是如何划分的 ...

随机推荐

  1. 找出A字符串中出现B字符串的起始位置

    /** * 找出B出现在A中的起始位置 * @param A * @param lenA * @param B * @param lenB * @date 2016-10-7 * @author sh ...

  2. C/C++之指针加减法

    C和C++中可对指针进行加减,但对其进行乘除则基本无实际意义.一般来说,对指针进行减法的前提是减数和被减数均指向同一数组.加法同理.需要注意的是,两个指针的减法,结果是两个地址之间索引变量的数目,而不 ...

  3. 增加SWAP空间的方法

    增加swap空间的方法 背景:安装oracle数据库需要,需要设置swap空间为16G,当前swap空间只有4G,需要增加12Gswap空间. 1.创建一个空文件 # dd if=/dev/zero ...

  4. LAMP环境搭建

    安装前说明: 先安装gcc gcc-c++ make 等 可用yum install -y gcc gcc-c++ make 也可以使用rpm安装不过需要注意依赖 1.安装apache 下载链接:ht ...

  5. PowerShell vs. PsExec for Remote Command Execution

    Posted by Jianpeng Mo / January 20, 2014 Monitoring and maintaining large-scale, complex, highly dis ...

  6. 分布式入门之5:paxos

    paxos是去中心化协议,较难理解.   proposer, accepter是其中的主要角色.前者发起投票,后者批准投票. 核心思想是,一旦超过半数的accepter同意某个投票,整个流程结束,批准 ...

  7. 在ABP模板工程中使用MySql

    1 下载一个新的ABP模板项目 http://www.aspnetboilerplate.com/ 2 在Windows上安装MySql, 创建一个新的数据库 sampledb https://dev ...

  8. SQL Server取datetime的日期部分

    在c#中有个Date属性用于返回日期,其实就是当天0点. DateTime dtNow = DateTime.Now; DateTime dtNow2 = dtNow.Date; Console.Wr ...

  9. ClientScript.RegisterStartupScript 不起作用

    asp.net webform 使用 ClientScript.RegisterStartupScript 不起作用 form 加上 runat="server",ok

  10. JavaScript- jquery easyui 可编辑表格插件 easyui.editgrid

    最近项目前端使用 jquery+easyui来做,用了几个月,觉得easyui是一个很不错的开源前端UI框架, 虽然偶尔会遇到一些小问题,但是凭借多年前端开发的实力积累 都一一解决了,其中比较典型的就 ...