一篇文章教你理解jvm内存体系结构(JMM(java内存模型))

0、前言

在我们初学java的时候,我们对java的内存模型的理解很多都是非常简单的,只有栈和堆,很多书也是这么讲的,今天带大家彻底理解java内存模型。

1、概述

Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域(如下图)。这些区域有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而一直存在,有些区域则是依赖用户进程的启动和结束而建立和销毁
java内存模型
上图是我参考《Java虚拟机规范》这本书从网上找的Java虚拟机所管理的内存模型,我们接下来就照着这个图来一一说明
绿色部分:是每个线程私有的内存区域(每个线程各一份),如虚拟机栈、本地方法区(本地方法栈,要与方法区区别开来)、程序计数器等。
橙色部分:是由多个线程共享的一些内存区域,如存放对象的以及存放类型信息等的方法区

2、程序计数器

概念

程序计数器(Program Counter Register) 是一块较小的内存空间,它可以看做是单签线程所执行的字节码的行号指示器(标明我们的代码执行到了哪一行)。在Java虚拟机的概念模型里,字节码解释器在工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,它是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。

为什么说程序计数器要线程私有?

由于Java虚拟机的多线程是通过线程轮秋切换执行实现的,所以,为了线程切换后能够恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储。

注意
  1. 而且程序计数器唯一一个不会出现内存溢出的内存区域。
  2. 如果当前线程执行的是本地(Native)方法本地方法是非Java代码的方法,这个方法可能是C++实现的,但是你可以通过java可以调用这个方法),则程序计数器值应为(Undefined)。

3、Java虚拟机栈

概念

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

局部变量表

局部变量表存放了编译期可知的各种Java虚拟机基本数据类型(char、boolean、byte、int等八大基本数据类型)、对象引用(reference类型,它并不等同于对象本身,可能是一个指向对象起始地址的易用指针,也可能是指向了一个代表对象的句柄或者其他相关的位置)和returnAddress类型(指向了一条字节码指令的地址。)

异常问题
  1. 如果线程请求的栈深度大于虚拟机所允许的深度,抛出StackOverFlowError异常
  2. 如果Java虚拟机栈容量可以动态扩展,当栈无法申请到足够的内存会抛出OutOfMemoryError异常。

3、本地方法栈

概念

本地方法栈(Native Method Stacks)与虚拟机栈非常相似,其区别是虚拟机栈为虚拟机执行Java1方法服务,而本地方法栈则是为虚拟机使用到的本地(Native)方法服务,我们日常使用的HotSpot虚拟机将本地方法栈和虚拟机栈合二为一了。

异常问题
  1. 如果线程请求的栈深度大于虚拟机所允许的深度,抛出StackOverFlowError异常
  2. 如果Java虚拟机栈容量可以动态扩展,当栈无法申请到足够的内存会抛出OutOfMemoryError异常。

4、Java堆

概念

Java堆是虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,此内存区域的唯一目的就是存放对象实例,Java世界里几乎所有的对象实例都在这里分配内存,同时也是GC的主要回收对象

异常问题
  1. 如果Java堆没有内存完成实例分配,并且堆也无法扩展时,java虚拟机将会抛出OutOfMemoryError异常。

5、方法区

概念

方法区(Method Area)与Java堆一样,是各个线程共享的内UC南区域,它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据

说明

方法区只是《Java虚拟机规范》所定义的一种规范,它并不是具体的实现,具体的实现是有两种

  • 一种是JDK7之前的永久代(Permanent Generation)
  • 另一种是JDK8之后的元空间(mate Space)
注意
  • jdk7时就已经把字符串常量池和静态变量从永久代移出了,放到了堆当中,
  • jdk8将还剩在永久代中的数据(主要是类型信息)移到了元空间。
有人问字符串常量池放在那里?

你回答方法区或者是堆都没毛病,方法区是模型、是规范,方法区确实定义了运行时常量池,你回答堆也没毛病,因为在jdk7时,确实把字符串常量池放在了堆中。

永久代与元空间的区别

两者最大的区别是元空间使用本地内存,而永久代使用的是JVM的内存,使用本地内存的好处就是当遇到Java.lang.outofMemoryError:PermGen Space,将不会存在,因为默认的类的元数据分配只受本地内存大小的限制,也就是说,本地内存剩余多少理论上metaspace就可以有多大,这解决了空间不足的问题,不过也不可能任其无限壮大,JVM默认在运行时会根据需要动态的设置其大小,那么这一替换的优势表现为

  • 字符串常量池存在于永久代中,容易出现性能问题和内存溢出。
  • 类的方法的信息大小又难以确定,因此给永久带的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。永久带会为GC带来不必要的复杂性,并且回收效率偏低,在永久代中元数据可能会随着每一次赋GC发生而进行移动,而 hotspot虚拟机每种类型的垃圾回收器都要特殊处理永久代中的元数据,分离出来以后可以简化赋GC,以及以后并发隔离元数据等方面进行优化

6、直接内存

概念

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

为什么会有直接内存?

我认为有两点

  1. 元空间使用的就是直接内存
  2. jdk1.4的NIO也是使用Native函数库直接分配堆外内存(直接内存),从而避免了在Java堆与本地内存之间来回复制数据。
直接内存会不会报异常?

会,直接内存会受本机总内存大小,如果我们在计算内存区域大小时忽略直接内存,使得各个内存区域综合大于实际的物理内存,会抛出OutOfMemoryError异常。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值