2

JVM运行时数据区域之程序计数器

Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来

-----------《深入理解java虚拟机》

在用C编程的时候,我们malloc一块空间总是需要手动进行free,不然就会造成内存泄露即使用过的内存没被释放,造成内存不够用了;而在Java中,垃圾回收器会自动帮我们回收在后台回收那些不再“使用”的内存,所以在Java编程中不容易出现内存泄露和溢出的问题。但是一旦出现例如java.lang.OutOfMemoryError:Java heap space之类的溢出,如果你不了解JVM运行时数据区域,那么你就可能又要默默的打开浏览器,面向百度开始编程了,所以说学习JVM还是非常重要滴!

tip:接下来会带大家一步一步学习JVM的相关内容,这一篇先带大家了解一下程序计数器,比较好理解,全当热身。

运行时数据区域

先概览一下,不同的JDK因为优化的问题所以内存区域会有不同,但是整体的一个的结构还不变的。

image

Java虚拟机在执行Java程序的时候会把它管理的内存区域划分为若干个不同的数据区域,这些区域各有各有的用途,创建以及销毁时间。有些内存区域由所有线程共享,有些则线程私有;有些内存区域随着用户线程的启动和结束而建立和销毁。

加点料
在java线程中有俩类线程,用户线程和守护线程;用户线程就是在我们创建线程时的默认的一类线程,
它允许在前台,例如我们执行main方法的时候,JVM会启动一个main线程,这个main线程就是用户线程;
而守护线程就是运行在后台的线程(默默守护,我哭辽),它服务于用户线程,例如GC时的GC内存回收线程。  
JVM与用户线程共存亡,只要有用户线程JVM就不会停止执行,当只剩下守护线程时,JVM才会停止执行

热身之程序计数器

在介绍java的计数器前,我先来讲一下CPU中的程序计数器,这俩个东东给大家一起介绍感觉可有助于理解

CPU中的程序计数器(PC)

CPU中的PC是一个大小为一个字的存储设备(寄存器),在任何时候,PC中存储的都是内存地址(是不是有点像指针?),而CPU就根据PC中的内存地址,到相应的内存取出指令然后执行并且在更新PC的值。在计算机通电后这个过程会一直不断的反复进行。计算机的核心也在于此。这个过程我会在字节码执行引擎那部分深入去介绍一下。

JAVA运行时数据区域程序计数器

在CPU中PC是一个物理设备,而java中PC则是一个一块比较小的内存空间,它是当前线程字节码执行的行号指示器。在java的概念模型中,字节码解释器就是通过改变这个计数器中的值来选取下一条执行的字节码指令的,它的程序控制流的指示器,分支,线程恢复等功能都依赖于这个计数器。

我们知道多线程的实现是多个线程轮流占用CPU而实现的,而在线程切换的时候就需要保存当前线程的执行状态,这样在这个线程重新占用CPU的时候才能恢复到之前的状态,而在JVM状态的保存是依赖于PC实现的,所以PC是线程所私有的内存区域,这个区域也是java运行时数据区域唯一不会发生OOM的区域

加点料一:

先给大家看一下字节码的样子,后面在详细介绍

源程序

public class Main {  
 public static void main(String[] args) throws IOException {  
 int a=1;  
 int b=2;  
 System.out.println(a+b);  
 }  
}

javac编译后的部分字节码

{public static void main(java.lang.String[]) throws java.io.IOException;  
 descriptor: ([Ljava/lang/String;)V  
 flags: ACC_PUBLIC, ACC_STATIC  
 Code:  
 stack=3, locals=3, args_size=1  //栈深度最大为3,3个变量槽  
 0: iconst_1             //常量1压入栈   
 1: istore_1             //栈顶元素出栈存入变量槽1  
 2: iconst_2             //常量2压入栈  
 3: istore_2             //栈顶元素出栈存入变量槽2  
 4: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;   
 //调用静态方法main  
 7: iload_1           //将变量槽1中值压入栈  
 8: iload_2           //将变量槽2中值压入栈  
 9: iadd              //从栈顶弹出俩个元素相加并且压入栈  
 10: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V  
 //调用虚方法  
 13: return   //返回  
 LineNumberTable:  
 line 18: 0  
 line 19: 2  
 line 20: 4  
 line 21: 13  
 LocalVariableTable:  
 Start  Length  Slot  Name   Signature  
 0      14     0  args   [Ljava/lang/String;  
 2      12     1     a   I  
 4      10     2     b   I  
 Exceptions:  
 throws java.io.IOException  
 }
加点料二:

操作系统管理多进程:每fork()创建一个进程,OS会为这个进程创建一个PCB(Process Control Block)来记录这个进程的信息例如PID等,当发生进程切换的时候,OS会在PCB保存进程执行的当前的信息,例如内存,寄存器使用的情况等,在恢复进程的时候,再根据PCB恢复状态

//代码,ax,bx,cs就是寄存器  
//pCur当前进程PCB,pNew下一个执行线程的PCB  
 switch_to(pCur,pNew){  
 //当前进程执行信息保存到PCB中  
 pCur.ax=CPU.ax;  
 pCur.bx=CPU.bx;  
 pCur.cs=CPU.cs;  
 .....  
 //恢复进程执行信息  
 CPU.ax=pNew.ax;  
 CPU.bx=pNew.bx;  
 .....  
 }

程序计数器的内容感觉有点少,为了不让大家感到枯燥,所以加了点料 ,peace;

重磅资源!!!

关注小白不想当码农微信公众号。

后台回复java核心技术卷关键字领取《java核心技术卷》pdf

回复jvm领取《深入理解Java虚拟机》pdf和《自己动手写jvm

回复设计模式领取《headfirst设计模式》pdf

回复计算机网络领取《计算机网络自顶向下》pdf


最后

我是不想当码农的小白,平时会写写一些技术博客,推荐优秀的程序员博主给大家还有自己遇到的优秀的java学习资源,希望和大家一起进步,共同成长。

以上内容如有错误,还望指出,感谢

公众号点击交流,添加我的微信,一起交流编程呗!

公众号: 小白不想当码农

image


小白不想当码农
13 声望3 粉丝