• 0

  • 0

把玩JVM内存模型系列-JVM内存模型概述以及程序计数器

1 month ago

世界如一面镜子,皱眉视之,它也皱眉看你,笑着看他,他也笑着看你

为什么想要了解JVM内存模型?

我们都知道在java的世界里,创建一个对象new一个就行了,不用像C、C++那样写一段释放对象内存的代码,因为java虚拟机帮我们做了这事,可是一旦这样做,也相当于把控制内存区域的权利交给了Java的虚拟机,一旦出现内存泄漏和溢出方面的问题,如果不了解虚拟机是怎样使用内存的,那就只剩下万能的重启了(你也不想5分钟重启一万遍吧)。

所以哈,出于以下两个目标,需要盘盘这个JVM内存模型:

  • 1.出现内存泄漏、溢出的时候,不至于惊慌失措
  • 2.明白咱们创建的各种“妖魔鬼怪”是在jvm的哪里“立了山头”

JVM把它管理的内存分为了哪几个区域(划分了哪些山头)

根据《Java虚拟机规范》的规定,Java虚拟机管理的内存将会包括以下几个运行时数据区域:方法区、虚拟机栈、本地方法栈、堆、程序计数器

JVM管理的内存区域
  • 方法区、堆这两块区域被所有线程共享,虚拟机栈、本地方法栈、程序计数器都是线程私有
  • 内存中的所有区域都公用执行引擎和本地库接口

程序计数器

场景:

在多线程的环境下:当A线程先向处理器发出指令,但当执行到中途一半时,B线程过来执行,且优先级高,此时处理器将A线程挂起,B线程执行,当B 执行结束需要唤醒A 线程必须得知道A线程到底执行到哪了,这种时候就需要我们的程序计数器出场了。

存的到底是啥玩意:

它是一块较小的内存空间,严格来说是一个数据结构,保存的是当前执行的字节码的偏移地址(有部分小伙伴说是行号,其实那不是行号,是指令的偏移地址,只是为了好理解,才说是行号的),当执行到下一条指令的时候,改变的只是程序计数器中保存的地址,并不需要申请新的内存来保存新的指令地址;因此,永远都不可能内存溢出的;因此,jvm虚拟机规范,也就没有规定,也是唯一一个没有规定 OutOfMemoryError异常的区域;

字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。但是对于native方法,由于方法体不是由java字节码构成,所以计数器不存储native方法的行号(undefine)

字节码偏移地址

程序计数器的特点

  • 线程私有

Java的多线程是通过线程轮流切换并分配cpu时间片方式来实现,也就是说,在同一时刻一个处理器内核只会执行一条线程,处理器切换线程时并不会记录上一个线程执行到哪个位置,所以为了线程切换后依然能恢复到原位,每条线程都需要有各自独立的程序计数器。

  • JVM规范中唯一没有规定OutOfMemoryError情况的区域

程序计数器存储的是字节码文件的偏移地址,而偏移地址的范围是可知晓的,在一开始分配内存时就可以分配一个绝对不会溢出的内存。

  • 为什么执行Native方法,值为空?

对native方法而言,它的方法体并不是由Java字节码构成的,自然无法应用上述的“Java字节码行号”的概念。所以JVM规范规定,如果当前执行的方法是native的,那么pc寄存器的值未定义——是什么值都可以。

那么执行native方法怎么跳回到原先的调用处的行号呢?

Java线程总是需要以某种形式映射到OS线程上。映射模型可以是1:1(原生线程模型)、n:1(绿色线程 / 用户态线程模型)、m:n(混合模型)。

以HotSpot VM的实现为例,它目前在大多数平台上都使用1:1模型,也就是每个Java线程都直接映射到一个OS线程上执行。此时,native方法就由原生平台直接执行,并不需要理会抽象的JVM层面上的“pc寄存器”概念——原生的CPU上真正的PC寄存器是怎样就是怎样。

jvm规范:If the method currently being executed by the thread is native, the value of the Java Virtual Machine's pc register is undefined

翻译过来就是一个线程执行Native方法,程序计数器的值未定义,可不是一定为空,任何值都可以。native方法执行后会退出(栈帧pop),方法退出返回到被调用的地方继续执行程序。

说人话:也就是说,我不管你返回什么,我不记录,我只记录下调用native之前的java方法的字节码行号,不管你返回的是什么,我还是从原来的偏移地址继续往下调用

程序计数器什么时候创建

什么时候分配程序计数器对应的内存呢?我们试想下,一个线程在执行的任何期间,都会失去CPU执行权,因此,我们要从一个线程被创建开始执行,就要无时无刻的记录着该线程当前执行到哪里了!因此,线程计数器,必须是线程被创建开始执行的时候,就要一同被创建

总结

  • 程序计数器存储的是字节码的行号,目的是在多线程环境下,线程切换后,程序能回到原先的线程位置继续往下执行
  • 程序计数器是在线程创建的时候一同创建,并分配相应的内存
  • 程序技术器是线程私有的,每个线程都有一个对应的程序计数器
  • 对于native方法而言,由于native方法不是由Java字节码构成,所以无法应用字节码行号的概念,native方法执行后会退出(栈帧pop),方法退出返回到被调用的地方继续执行程序。

点关注,不迷路

好了各位,以上就是这篇文章的全部内容了。我后面会每周都更新常用技术栈相关的文章,如果这个文章写得还不错,觉得「小沙弥」我有点东西的话 求点赞👍 求关注❤️ 求分享👥 对我来说真的 非常有用!!!

白嫖不好,创作不易,各位的支持和认可,就是我创作的最大动力,我们下篇文章见!

迷途小沙弥 | 文 【原创】

如果本篇博客有任何错误,请批评指教,不胜感激 !

免责声明:文章版权归原作者所有,其内容与观点不代表Unitimes立场,亦不构成任何投资意见或建议。

java

0

Relevant articles

未登录头像

No more data