JVM——内存模型(一):程序计数器

释放双眼,带上耳机,听听看~!

拥有最高权利却又从事着平民百姓的基础工作是一种什么样的体验?

对于从事C、C++的程序员来说,这种感觉他们实在是熟悉得不能再熟悉了。在内存管理的领域,不论是对象的生命的开始,还是终结,所有对象的命运都被他们掌握在手里。他们既是掌管最高权利的皇帝,也是从事基础工作的平民。

那么Java程序员又是什么样的?

对于Java程序员来说,他们的体验在这一方面也许就没有C、C++他们的那么丰富了。为什么?
因为咱们有虚拟机呀!

在虚拟机自动内存管理机制的帮助下,Java程序员不再需要为每一个new操作去写配对的delete/free代码,因此更加不容易出现内存泄漏和内存溢出的问题(不明白什么叫内存泄漏或者内存溢出的伙伴们可以参考:JVM——内存溢出和内存泄漏的区别)。

不过,将内存控制的权利完全交给虚拟机,这样真的好吗?

上文说到有了虚拟机之后更不容易出现内存泄漏和内存溢出,但是这并不代表这些事情不会发生。若是发生了内存泄漏和内存溢出,那该怎么办呢?

还能怎么办?当然是屁颠屁颠地跑去学习虚拟机是如何使用内存的呗!

当然啦,学习虚拟机也许很枯燥,不过这段时间本帅博主将与大家一起了解认识一下JVM的相关知识,想必大家就不会孤单啦~。今天我们就来了解一下运行时数据区域中的程序计数器。

1.运行时数据区域

上文中我们说到Java将内存管理的权力交给了虚拟机,那么虚拟机所管理的内存都有什么呢?我们来看看下面这张图:

JVM——内存模型(一):程序计数器

 从上面这个图我们可以看出来,运行时数据区域包括:方法区、堆、虚拟机栈、本地方法栈与数据库,其中方法区和堆是所有线程共享的数据区域。

那么五个东西到底是个啥呢?我们这段时间就一起来认识一下。接下来今天先认识一下
程序计数器。

** 2.程序计数器**

 做学问就要有敢于问,一看到程序计数器,“这东西是个嘛?”就要立马脱口而出。

这东西是个嘛?

今天我们请来了《深入理解Java虚拟机》的作者周志明大佬,让我们来听听这位大佬是如何定义程序计数器的。

**周大佬:**程序计数器,英文名叫Program Counter Register,它是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。

那这个程序计数器又能做点什么呢?

**周大佬:**在我们虚拟机的概念(注意,这里说的是仅仅是概念模型噢,毕竟各种虚拟机可能会通过一些更加高效的方式去事先,)里面,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,像什么分支、循环、跳转、异常处理、线程恢复等功能都需要依赖这个程序计数器来完成。

注:如果不了解字节码的话呢,可以参考:杂谈——探秘字节流与字符流

看了周志明大佬的描述,也许有的伙伴可能会产生程序计数器是否是多余的疑问。

因为沿着指令的顺序执行下去,即使是分支跳转这样的流程,跳转到指定的指令处按顺序继续执行是完全能够保证程序的执行顺序的。假设程序永远只有一个线程,这个疑问没有任何问题,也就是说并不需要程序计数器。

但实际上程序是通过多个线程协同合作执行的。所以说,程序计数器还是有用武之地的。既然是多线程,那程序计数器该怎么分配呢?

首先我们要搞清楚JVM的多线程实现方式。

JVM的多线程是通过CPU时间片轮转(即线程轮流切换并分配处理器执行时间)算法来实现的,
在任何一个确定的时刻,一个处理器(对于多核处理器来说则是一个内核)都只会执行一条线程中的指令

也就是说,某个线程在执行过程中可能会因为时间片耗尽而被挂起,而另一个线程获取到时间片开始执行。当被挂起的线程重新获取到时间片的时候,它要想从被挂起的地方继续执行,就必须知道它上次执行到哪个位置。那么如何知道自己执行到哪一个位置呢,由谁来记录?

**在JVM中,通过程序计数器来记录某个线程的字节码执行位置。**因此,为了线程切换之后能够恢复到一个正确的执行位置,每条线程都需要有一个独立的程序计数器,即具备线程隔离的特性,各条线程之间计数器互不影响,独立存储(我们称这一类内存区域为“
线程私有”的内存)。

需要注意,程序计数器记录的值分两种情况

  • 如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址
  • 如果正在执行的是Native方法,这个计数器的值为空(Undefined)。

 

3.程序计数器的特点

程序计数器的特点主要有以下六点:

  1. 线程隔离性(即程序计数器的内存空间是线程私有的),每个线程工作时都有属于自己的独立计数器(上文提到过)。
  2. 执行java方法时,程序计数器是有值的,且记录的是正在执行的字节码指令的地址(参考上一小节的描述)。
  3. 执行native本地方法时,程序计数器的值为空(Undefined)。因为native方法是java通过JNI直接调用本地C/C++库,可以近似的认为native方法相当于C/C++暴露给java的一个接口,java通过调用这个接口从而调用到C/C++方法。由于该方法是通过C/C++而不是java进行实现。那么自然无法产生相应的字节码,并且C/C++执行时的内存分配是由自己语言决定的,而不是由JVM决定的。具体流程且看下图:JVM——内存模型(一):程序计数器
  4. 程序计数器占用内存很小,在进行JVM内存计算时,可以忽略不计。
  5. 程序计数器,是唯一一个在java虚拟机规范中没有规定任何OutOfMemoryError的区域。

上文说到程序计数器,保存的是当前执行的字节码的偏移地址。当执行到下一条指令的时候,改变的只是程序计数器中保存的地址,并不需要申请新的内存来保存新的指令地址;因此,永远都不可能内存溢出的。

  1. **线程计数器,必须是线程被创建开始执行的时候,就要一同被创建。**一个线程在执行的任何期间,都会失去CPU执行权,因此,我们要从一个线程被创建开始执行,就要无时无刻的记录着该线程当前执行到哪里了。

 

 

 

给TA打赏
共{{data.count}}人
人已打赏
安全技术

Bootstrap 4 Flex(弹性)布局

2021-12-21 16:36:11

安全技术

从零搭建自己的SpringBoot后台框架(二十三)

2022-1-12 12:36:11

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索