【JVM】自动内存管理 —— Java内存区域的理解

JVM 运行时数据区域的逻辑划分

在这里插入图片描述

程序计数器

可以看作是当前线程所执行的字节码的行号指示器

  • 行号指示器
    字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,它是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
    下面的图是我目前对java执行过程以及程序计数器的理解:
    在这里插入图片描述

      ```
      字节码:
      	字节码是已经经过编译,但与特定机器码无关,需要解释器转译后才能成为机器码的中间代码。
      Java字节码:
      	顾名思义就是Java虚拟机执行的一种指令格式。
      解释器:
      	就是能够把高级编程语言一行一行直接翻译成机器可以识别运行的低级语言的程序。
      类加载器:
      	就是可以把.class文件加载到Java虚拟机内存的程序
      字节码指令流:
      	这是我自己抽象理解的就是线程将要执行的一系列指令
      ```
    

    大概流程:我们写好的.java文件(源文件)被编译器编译后变成.class文件(字节码),然后被JVM的类加载器加载到内存,通过字节码校验器去做一些校验,校验通过后交由解释器将字节码文件解释成计算机能够识别的机器指令。

    程序计数器就是在解释器解释字节码是发挥作用的,每一个线程都会有自己的计数器,线程执行代码时会使用类加载器加载字节码(不让new一个实例的时候),而它的计数器需要做的就是标记自己执行到哪里了,记住它下一条需要执行的字节码指令的行号。

  • 线程私有
    由于Java虚拟机的多线程是通过线程轮流切换、分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)都只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程 之间计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。

Java虚拟机栈

Java虚拟机栈是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的线程内存模型:每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态连接、方法出口等信息。每一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程

  1. 线程私有的
  2. 生命周期与线程相同(线程栈)
  3. 方法执行的线程内存模型

我对书中这句话的理解:在Java虚拟机栈中,存放着每个(线程调用的)方法所需要的上下文信息。这些信息被放在一个栈帧里面,线程在执行方法时从指定的栈帧中读取需要的数据。栈帧之间时相互隔离的,这貌似刚好解释了为什么方法中参数传递的一些特性(例如方法中的局部变量的修改不会影响到方法外的变量)。对于生命周期的理解,一个线程启动之后自然会执行一些方法,执行方法时就会将方法的上下文信息以栈帧的形式存到当前线程的虚拟机栈中(入栈),在执行完方法时会将该方法的栈帧(方法上下文)从栈中推出(出栈),当作用方法执行结束时,当前线程的虚拟机栈也就空了,线程也执行结束了。下面时我理解的虚拟机栈的结构图:
在这里插入图片描述
在这里插入图片描述

局部变量表

栈帧中我们比较关注的是局部变量表,是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。它存储的数据主要包括:

  1. 编译期可知的各种Java虚拟机基本数据类型 :boolean、byte、char、short、int、float、long、double
  2. 对象引用:reference类型,它并不等同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或者其他与此对象相关的位置
  3. returnAddress类型:指向了一条字节码指令的地址

局部变量表中存放数据的方式:局部变量槽(Slot)。其中64位长度的long和double类型的数据会占用两个变量槽,其余的数据类型只占用一个。
这样一来,所有存放在局部变量表中的数据类型都是由固定大小的,局部变量表所需的内存空间在编译期间就能完成分配,换言之:当进入一个方法时,这个方法需要在栈帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小(也就是变量槽的数量在方法执行时时固定的)。

本地方法栈

Java虚拟机栈于管理Java方法的调用,而本地方法栈用于管理本地方法的调用。也是线程私有的。

本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别只是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的本地(Native)方法服务。

看到一篇在理解本地方法栈启发到我的文章:
Java虚拟机中的本地方法栈
Java虚拟机中的本地方法接口

Java堆

对于Java应用程序来说,Java堆(Java Heap)是虚拟机所管理的内存中最大的一块。

  1. Java堆是被所有线程共享
  2. 虚拟机启动时创建堆。
  3. 目的就是存放对象实例。
  4. Java堆是垃圾收集器管理的内存区域,因此也被叫做“GC堆”

Java堆的细分:

  • 从回收内存的角度看
    于现代垃圾收集器大部分都是基于分代收集理论设计的,所以Java堆也经常出现分代划分出来的区域。
  • 从分配内存的角度看
    所有线程共享的Java堆中可以划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB),以提升对象分配时的效率。

不过无论从什么角度,无论如何划分,都不会改变Java堆中存储内容的共性,无论是哪个区域,存储的都只能是对象的实例,将Java堆细分的目的只是为了更好地回收内存,或者更快地分配内存

Java堆既可以被实现成固定大小的,也可以是可扩展的,不过当前主流的Java虚拟机都是按照可扩展来实现的(通过参数-Xmx和-Xms设定)。

根据《Java虚拟机规范》的规定,Java堆可以处于物理上不连续的内存空间中,但在逻辑上它应该被视为连续的。但对于大对象(典型的如数组对象),多数虚拟机实现出于实现简单、存储高效的考虑,很可能会要求连续的内存空间。

方法区

虽然《Java虚拟机规范》中把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫作“非堆”(Non-Heap),目的是与Java堆区分开来。

  1. 线程共享
  2. 在JVM启动的时候被创建,并且它的实际的物理内存空间中和Java堆区一样都可以是不连续的。
  3. 方法区的大小,跟堆空间一样,可以选择固定大小或者可扩展
  4. 用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。
运行时常量池

是方法区的一部分,用于存放编译期生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。

直接内存

直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是《Java虚拟机规范》中定义的内存区域。

注:
有些概念还没有理解透,等有了新的理解再做补充

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值