JVM-运行时数据区域

  java虚拟机管理的内存包括几个运行时数据内存:方法区、虚拟机栈、本地方法栈、堆、程序计数器、其中方法区和堆是由线程共享的数据区,其他几个是线程隔离的数据区。程序计数器、虚拟机栈、本地方法栈,随线程而生,线程亡而亡

程序计数器

  程序计数器是一块较小的内存,可以看作是当前线程所执行的行号指示器。
  字节码解释器工作的时候通过改变这个计数器的值来选取下一条需要执行的字节码的指令、分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成
  如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Native方法,这个计数器则为空。
此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemotyError情况的区域
  线程私有的内存
    由于java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现,在任何一个确定的时间,一个处理器(对多核处理器来说是一个内核)只会执行一条线程中的指令。因此为了线程切换能够恢复到正确的执行位置上,每条线程都有一个独立的线程计数器,各条线程之间计数器互不影响,独立存储,我们叫这类内存区域线程私有的内存

Java虚拟机栈

  优点:存取速度比堆要快,仅次于直接位于CPU中的寄存器
  缺点:存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。
  
  与程序计数器一样,Java虚拟机栈也是线程私有的,它的生命周期与线程相同
虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧用于储存局部变量表、操作数栈、动态链接、方法出口等信息。每个方法从调用直至完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。

栈内存就是虚拟机栈,或者说是虚拟机栈中局部变量表的部分
 局部变量表存放了编辑期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它不代表对象本身,可能是一个指向对象起始地址的引用指针,也可能指向一个对象代表的句柄或其他与此对象相关的位置)和returnAddress类型(指向了一条字节码指令的地址)
  其中64位长度的long和double类型的数据会占用两个局 部变量空间,其余的数据类型只占用1个。
局部变量表所需的内存空间在编译器间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小

Java虚拟机规范对这个区域规定了两种异常状况:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常。如果虚拟机扩展时无法申请到足够的内存,就会抛出 OutOfMemoryError异常

注:栈帧是方法运行时的基础数据结构

本地方法栈

  本地方法栈和虚拟机栈发挥的作用是非常类似的,他们的区别是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务
  Native方法:指使用Java以外的其他语言编写的方法

Java虚拟机规范允许本地方法栈实现成固定大小或者根据计算来动态扩展和收缩,如果采用固定大小的本地方法栈,那么每一个线程的本地方法栈容量可以在创建栈的时候独立选定
  
  有的虚拟机把本地方法栈和虚拟机栈合二为一,比如Sun HotSpot 虚拟机。
任何本地方法接口都会使用某种本地方法栈,当虚拟机调用本地(native)方法时,虚拟机不会创建新的栈帧,虚拟机栈会保持不变,只是简单的动态连接并直接调用相关的本地方法
  
  本地方法栈可能发生如下异常情况
   如果线程请求分配的栈容量超过本地方法栈允许的最大容量,Java虚拟机将会抛出一个StackOverflowError异常
   如果本地方法栈可以动态扩展,并且再尝试扩展的时候无法申请到足够的内存,或者再创建新的线程时没有足够的内存区创建对应的本地方法栈,那么Java虚拟机将会抛出一个OutOfMemoryError异常

Java堆

  堆是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动的时候创建,此内存区域的唯一目的是用于存放对象实例,几乎所有的对象实例都在这里分配内存。所有的对象实例和数组都在堆上分配
  Java堆是垃圾收集器管理的主要区域。Java堆细分为新生代和老年代。不管怎样,划分的目的都是为了更好的回收内存,或者更快地分配内存
  Java堆的容量可以是固定的,也可以随着程序执行的需求动态扩展,并在不需要过多空间时自动收缩
  Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。如果在堆中没有完成实例分配(意思就是堆内存大小不够分配了需要扩展),并且堆也无法再扩展时将会抛出OutOfMemoryError异常

方法区

  根堆一样被所有线程共享
  方法区用于储存已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
  但是!!!方法区中没有实例变量,这是因为,类加载先于对应类对象的产生,而实例变量是和对象关联在一起的,没有对象就不存在实例变量,类加载时没有对象,所以方法区中没有实例变量
  除了跟Java堆一样不需要连续的内存和可以选择固定大小或者可扩展外,还可以选择不实现垃圾收集。这个区域的内存回收目标主要是针对常量池的回收和对类型的卸载
  当方法区无法满足内存分配需求时,将抛出OutOfMemoryErroy异常

运行时常量池

  它是方法区的一部分。Class文件中除了有关的版本、字段、方法、接口等描述信息外、还有一项信息是常量池,用于存放编辑期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放
  Java语言并不要求常量一定只有编辑期才能产生,也就是可能将新的常量放入池中,这种特性被开发人员利用得比较多的便是String类的intern()方法
好处
  常量池避免了频繁的创建和销毁对象而影响系统性能,其实现了对象的共享。例如字符串常量池,在编译阶段就把所有的字符串文字放到一个常量池中。
  节省内存空间:常量池中所有相同的字符串常量被合并,只占用一个 空间。
  当常量池无法再申请到内存时会抛出OutOfMemoryError异常
当创建类或接口时,如果构造运行时常量池所需的内存空间超过了方法区所能提供的最大值,那么Java虚拟机将会抛出一个OutOfMemoryError异常

运行时栈帧结构

  栈帧是用来存储数据和部分结果的数据结构,同时也用来处理动态链接、方法返回值和异常分派
  栈帧随着方法调用而创建,随着方法结束而销毁,无论方法是正常完成还是异常完成(抛出了在方法内未被捕获的异常)都算作方法结束。栈帧的存储空间由创建它的线程分配在Java虚拟机栈 中,每一个栈帧都有自己的本地变量表、操作数栈和指向当前方法所属类的运行时常量池的引用
  本地变量表和操作数栈的容量在编译期确定,并通过相关方法的code属性保存及提供给栈帧使用,因此,栈帧数据结构的大小仅仅取决于Java虚拟机的实现。实现者可以在调用方法时给它们分配内存
  在某条线程执行过程中的某个时间点上,只有目前正在执行的那个方法的栈帧是活动的,这个栈帧称为当前栈帧,这个栈帧对应的方法称为当前方法,定义这个方法的类称作当前类,对局部变量表和操作数栈的各种操作,通常指的是对当前栈帧的局部变量表和操作数栈所进行的操作
  如果当前方法调用了其他方法,或者当前方法执行结束,那这个方法的栈帧就不再是当前栈帧了,调用新的方法时,新的栈帧也会随之而创建,并且会随着程序控制权移交到新方法而成为新的当前栈帧,方法返回之际,当前栈帧会传回此方法的执行结果给前一个栈帧,然后,虚拟机会丢弃当前栈帧,使得前一个栈帧重新成为当前栈帧
  注:栈帧是线程本地私有的数据,不可能在一个栈帧制中引用另外一个线程栈帧

局部变量表

  局部变量使用索引来进行定位访问,首个局部变量的索引值为0
  Java虚拟机使用局部变量表来完成方法调用时的参数传递,当调用类方法时,它的参数将会一次传递到局部变量表中从0开始的连续位置上,当调用实例方法时,第0个局部变量一定用来存储该实例方法所在对象的引用(即Java语言中的this关键字)。后续的其他参数将会传递至局部变量表中从1开始的连续位置上

操作数栈

  操作数栈是一个后入先出(LIFO)的栈,同局部变量表一样,操作数栈的最大深度也在编译的时候写入到方法的code属性max_stacks数据项中
  对于每个方法的调用,JVM会建立一个操作数栈,以供计算使用,
  栈帧在刚刚创建时,操作数栈是空的,Java虚拟机提供一些字节码指令来从局部变量表或者对象实例的字段中复制常量或变量值到操作数栈中,也提供了一些指令用于从操作数栈取走数据、操作数据以及把操作结果重新入栈,在调用方法时,操作数栈也用来准备调用方法的参数以及接受方法返回结果
  在任意时刻,操作数栈都会有一个确定的栈深度,一个long或者double类型的数据会占用两个单位的栈深度,其他数据类型则会占用一个单位的栈深度

动态链接

  每个栈帧内部都包含一个指向当前方法所在类型的运行时常量池的引用,以便对当前方法的代码实现动态链接,在class文件里面,一个方法若要调用其他方法,或者访问成员变量,则需要通过符号引用所表示的方法转换为对实际方法的直接引用,类加载的过程中将要解析尚未被解析的符号引用,并且将对变量的访问转化为变量在程序运行时,位于存储结构中的正确偏移量
  由于对其他类中的方法和变量进行了晚期绑定,所以即便那些类发生变化,也不会影响调用它们的方法

直接内存

  直接内存并不是虚拟机运行时数据区的一部分,也不是java虚拟机规范中定义的内存区域。
  NIO类是一种基于通道和缓冲区的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个储存在Java堆中的DirectByteBuffer对象作为这块直接内存的引用进行操作,这样避免了java堆和natvie堆中来回复制数据

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值