深入理解Java虚拟机3 - JVM内存模型

在这里插入图片描述
jvm内存空间分为五个部分:

  • 方法区
  • 本地方法栈
  • 程序计数器
  • 虚拟机栈

程序计数器

程序计数器记录的是当前线程即将执行的那一条字节码指令的地址,也就是当前线程执行的位置。

  • 每个线程都有自己的计数器(私有,生命周期和线程相同)。
  • 执行java方法时,程序计数器是有值的,执行native本地方法时,计数器值为空。
  • 程序计数器占用内存非常小,不会出现OutOfMemoryError

虚拟机栈

Java虚拟机栈是描述Java方法执行线程内存模型,每个方法被执行的时候,Java虚拟机栈会同步创建一个栈帧用于存储方法运行时所需的信息,可以说Java虚拟机栈是由一个个栈帧组成的。

  • 虚拟机栈保存方法的调用过程
  • 栈说明了程序运行中的瞬时状态
  • 栈是线程私有的,生命周期和线程相同
  • 每一个方法被调用直至执行完毕的过程,就对应一个栈帧在虚拟机栈中从入栈到出栈的过程。
  • 栈的深度有限制,java1.5后默认每个栈空间1mb

栈帧的组成

  • 局部变量表
  • 操作数栈
  • 动态连接
  • 返回地址(方法出口)
    在这里插入图片描述

1.局部变量表

局部变量表按定义顺序存储两块内容:

  • 方法参数
  • 方法内的局部变量
局部变量表特点
  • 线程私有,不允许跨线程访问,随方法调用创建,方法退出销毁
  • 局部变量表所需的内存空间在编译期间就已确定
  • 局部变量元数据(符号引用)存储在字节码中
  • 局部变量表是栈帧中最主要的存储空间,决定了栈的深度
  • 存储局部变量的单位是slot(变量槽),32位以内的类型(int/float/char…/引用类型)占用一个slot,64位类型(long/double)占用两个slot
  • slot复用:当槽位有空余时,后产生的局部变量会复用之前的槽位
  • 类的构造方法和实例方法中局部变量表的0号slot默认为this,指向当前类实例,static方法则不是,所以static方法没有this关键字

2.操作数栈

字节码指令在执行过程中的中间计算过程存储在操作数栈

3.动态链接

将保存在字节码文件中方法的符号引用转为运行时内存直接引用
在这里插入图片描述

4.返回地址

保存该方法调用者的程序计数器值,用于方法正常执行后后续执行
在这里插入图片描述

Java虚拟机栈可能出现两个异常
  • StackOverFlowError:若Java虚拟机栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前Java虚拟机栈的最大深度的时候,就抛出StackOverFlowError异常。
  • OutOfMemoryError:若 Java 虚拟机栈的内存大小允许动态扩展,且当线程请求栈时内存用完了,无法再动态扩展了,此时抛出OutOfMemoryError异常。

本地方法栈

本地方法栈与虚拟机栈很相似,区别是虚拟机栈为虚拟机执行java方法(也就是字节码)服务,而本地方法栈是为虚拟机使用到的本地方法服务。

  • 虚拟机规范中队本地方法栈中方法使用的语言,使用方式与数据结构没有强制规定。
  • Sun HotSpot虚拟机把本地方法栈和虚拟机栈合二为一。

Java 虚拟机所管理的内存中最大的一块,Java堆用来存放对象实例的空间,数组,字符串常量池和静态变量(1.8之后)。

  • 是各个线程共享的内存区域。(整个Java虚拟机只有一个堆,所有的线程都访问同一个堆。而程序计数器、Java虚拟机栈、本地方法栈都是一个线程对应一个的。)
  • 虚拟机启动时创建
  • 堆内存在物理上是分散的,逻辑上是连续的
  • 线程共享的java堆中可以划分出多个线程私有的分配缓冲区(TLAB)用于提高JVM的并发处理效率
    在这里插入图片描述

JDK1.8堆结构

在这里插入图片描述
Java 堆是垃圾收集器管理的主要区域,因此也被称作GC堆(Garbage Collected Heap),从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以Java堆还可以细分为:新生代和老年代。进一步划分的目的是更好地回收内存,或者更快地分配内存。

  • 新生代:用来存放新生的对象,该区域会被频繁GC
  • 老生代:新生代保存的稳定对象放入老年代,不会频繁GC
  • 元空间:内存的永久保存区域,主要存放Class元数据,几乎不会GC,在 JDK 1.8前叫永久代。

方法区

方法区是用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。

方法区特点

  • 线程共享
  • 《Java虚拟机规范》把方法区描述为堆的一个逻辑部分,但是在实现中与堆内存无关,所以方法区也叫“非堆Non-Heap”,目的是与Java堆区分开。
  • 方法区是个概念,JVM对方法区如何实现做更多要求
    • 1.7前HotSopt的方法区实现称为“永久代——Permanent Generation”
    • 1.8后HotSopt的方法区实现称为“元空间——Metaspace”
  • 虚拟机规范对方法区要求比较宽松,甚至可以不实现垃圾回收,但并非数据进入方法区后就"永久存在"了。

永久代和元空间的区别

  • 永久代是JDK7(含)之前的方法区实现,是占用JVM内存保存数据,容易内存溢出
  • 元空间是JDK8之后的方法区实现,是使用本地内存(Native Memory)保存数据,直接受到本机的物理内存限制,最大为可用物理内存

运行时常量池

在这里插入图片描述
运行常量池是方法区的一部分。Class文件除了有类的版本,字段,方法,接口等描述信息外,还有常量池表,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放在方法区的运行时常量池。

运行常量池具有动态性,在运行期间也可以将新的常量放入池中,例如String类的intern()方法。

当常量池无法申请到内存时会抛出OutOfMemoryError异常。

常量池中的某些常量没有被引用时就需要被回收。

直接内存

直接内存并不属于Java虚拟机的内存,但是也会被频繁地使用。

JDK1.4新加入了NIO(New Input/Output) 类,引入了一种基于通道(Channel) 与缓存区(Buffer) 的 I/O 方式。它可以用Native函数库直接分配堆外内存,然后通过一个存储在Java堆里的DirectByteBuffer对像作为这块内存的引用进行操作。避免了Java堆和Native堆之间的数据复制,从而提高了内存效率。

直接内存的分配不会收到Java虚拟机堆大小的限制,但是,既然是内存,还是会收到本机总内存的限制,不够的时候就会抛出OutOfMemoryError异常。

面试题:static静态变量存储在什么地方?

在这里插入图片描述

扩展:java字符串常量池,字节码常量池和运行时常量池

https://blog.csdn.net/zm13007310400/article/details/77534349

总结

  1. 线程私有的区域:程序计数器、虚拟机栈、本地方法栈。生命周期和其所属线程一样。
  2. 线程共享的区域:堆和方法区。虚拟机启动就创建,停止就销毁。
  3. Java虚拟机内存模型中的两个栈分别是虚拟机栈和本地方法栈。两个栈的区别是:虚拟机栈描述的是Java方法运行过程的内存模型,而本地方法栈式描述Java本地方法过程的内存模型。
  4. Java虚拟内存模型中的两个堆分别是Java堆和方法区。方法区属于Java堆的逻辑部分。区别是:堆中存放实例对象,数组,字符串常量池和静态变量(1.8之后),而方法区存放类信息、常量、即时编译器编译的代码数据。
  5. 堆是Java虚拟机中最大的一块内存区域,也是垃圾收集器的主要工作区域。new对象永远放在堆中。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值