Java程序运行时数据区域

概述       

 java程序员虽然把内存控制的权利全权交给Java虚拟机,但是,当出现内存泄漏和溢出方面的问题时,如果不了解虚拟机是怎样使用内存的,那么排查错误将会成为一项异常艰难的任务。

        java程序运行时的数据区域(java虚拟机管理的内存)分为多个线程共享内存和线程私有内存

  • 所有线程共享的数据区域:依赖虚拟机进程的启动和结束而建立和销毁。细分为:堆和方法区(Java 8之后是元空间)。
  • 线程私有的数据区域:依赖用户线程的启动和结束而建立和销毁。细分为:虚拟机栈、本地方法栈和程序计数器。

Java线程与内存 -《码出高效》

图摘自《码出高效》

 1. 程序计数器

  • 占有很小的内存空间。
  • 可以理解为线程执行的字节码文件(class文件)的行号指示器。对于java方法来说,记录的是正在执行的字节码指令的地址;对于本地方法(native声明的)来说,计数器值为空。
  • 各个线程之间的程序计数器互不影响,独立存储。每个线程都需要维护一个这样独立的计数器,目的是为了线程切换后能够恢复到正确的执行位置。
  • 字节码解释器的工作就是通过改变计数器的值来选择下一条需要执行的字节码指令。分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖计数器来完成。
  • Java虚拟机规范没有规定任何与该区域相关的OutOfMemoryError异常情况。

2. Java虚拟机栈

  • 虚拟机栈描述的是java方法(区分的是native方法)执行时的内存模型。
  • 每个线程维护一个独立的虚拟机栈
  • 虚拟机栈由多个栈帧组成,每个方法在执行的同时都会创建一个栈帧。遵循先入后出的选择进行入栈和出栈,栈顶的栈帧对应的方法是当前正在执行的方法,每一个方法从调用直至执行完成的过程,就对应一个栈帧在虚拟机栈中的入栈和出栈过程。
  • 每个栈帧由局部变量表操作数栈动态链接方法出口等信息构成。
    • 局部变量表:即常说的不严谨意义上的栈内存(与堆内存对应)。主要存放各种基本数据类型对象引用(地址)等局部变量。除了基本数据类型中的long和double占用两个局部变量空间(slot),其他数据类型都只占1个。
    • 操作数栈:栈结构,可以理解为用于数值计算的临时数据存储区域。比如:
      • a+b这个运算,涉及到的就是从局部变量中取出a和b,然后压入操作数栈,再依次弹出执行加法运算,运算结果入栈,运算结果再出栈入局部变量表。
      • i++和++i结果不同的原因:假设i初始为0
        • i++:先从局部变量表取出0,压入操作数栈;然后i在局部变量表的slot上自增为1;然后操作数栈的栈顶的0弹出后,覆盖掉局部变量表中i=1的值,所以,最终i=0。
        • ++i:先在局部变量表的slot上自增,i=1;然后从局部变量表中加载数字1到操作数栈;然后将此时操作数栈顶的值(1)弹出,存入局部变量表中,并将局部变量表中值覆盖掉,最终i=1。
    • 动态链接:每个栈帧中包含一个在常量池中对当前方法的引用(地址), 目的是支持方法调用过程的动态连接
    • 方法出口:方法返回地址,方法被调用的地方。
  • java虚拟机规范中,对虚拟机栈这个区域规定了两种异常情况:
    • 虚拟机栈不可动态扩展:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常
    • 虚拟机栈可以动态扩展:如果扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常

操作栈的压栈与出栈-《码出高效》

 3. 本地方法栈

  • 本地方法栈为虚拟机使用到的Native方法服务。
  • 在虚拟机规范中,对本地方法栈中方法使用的语言、使用方式与数据结构没有强制规定,因此,具体的虚拟机可以自由实现它。甚至有的虚拟机,比如HotSpot直接把本地方法栈和虚拟机栈 合二为一。
  • 与虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError和OutOfMemoryError。

4. Java堆

  • Java堆是所有线程共享的一块内存区域,也是Java虚拟机所管理的内存中最大的一块。常说的共享内存主要是指堆内存(方法区占很小的比重)。
  • 所有对象实例数组都在堆上分配(通过new实现)。
  • Java堆是垃圾收集器管理的主要区域。因此也称“GC堆(Garbage Collected Heap)”。
  • 现在使用的主流垃圾回收算法是分代收集算法,因此,Java堆也被分为新生代老生代。(方法区一般称为永久代,有些虚拟机中压根就没有永久代,即方法区不是按照永久代方式实现)。新生代又分为:Eden空间、From Survivor空间、To Survivor空间等。
  • 线程共享的的Java堆中可能划分出多个线程私有的分配缓冲区,但存储的内容也是对象实例。
  • 进一步划分的目的是更好地回收内存,或者更好地分配内存。
  • Java堆可以处于物理上不连续的内存空间中,只需要逻辑上连续即可。
  • 如果堆中没有内存完成实例分配,并且堆也无法扩展时,将会抛出OutOfMemoryError异常。

5. 方法区

  • 方法区是各个线程共享的区域,用于存储已经被加载的类信息常量静态变量即时编译器编译后的代码等数据。
  • Java虚拟机规范将方法区描述为堆的一个逻辑部分,但是方法区有一个别名叫做Non-Heap(标记为Non-Heap),目的是与Java堆区分开来。
  • HotSpot虚拟机的设计团队把GC分代回收扩展到方法区,用永久代的方式实现方法区,以便像管理Java堆一样管理这部分内存,能够省去专门为方法区编写内存管理代码的工作。
  • 方法区与“永久代”本质上并不等价。其他虚拟机并不存在“永久代”的概念。
  • 如何实现方法区属于虚拟机实现细节,并且不受虚拟机规范约束。
  • 使用“永久代”的方式实现方法区并不是一个好主意,这样容易发生内存溢出的问题。Java 8中,永久代已被移除,取而代之的是元数据区。
  • 当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值