00:00
那关于常量池和运行时常量池啊,咱们就先告一段落,这呢也是咱们说的啊,在这个方法区的这个结构当中,这个非常重要的一个结构叫做运行时常量尺。OK,那么关于方法区的这几个部分啊,咱们的讲解呢,就先暂时的告一段落,诶当然咱们这个一会儿呢,还要再说一下这个方法区呢,这是一个经典的内部存储的结构,在JDK的不同版本当中,它的一个变化,这个咱们一会儿再说,在讲解这个变化之前呢,咱们先及时性的呢,给大家举一个例子,通过这个例子呢,让大家呢,再直观的去感受一下这个方法区它的一个使用,哎,通过代码呢,咱们去强调一下,OK,这个代码的话呢,写的也相对来说比较简单一些,这里边儿呢,咱们就没有过多的去涉及到new对象,也就是在这个推空间当中啊,咱们没有过多的去涉及到这个情况,主要呢,我们来看一下这个main方法当中,咱们就来定义变量。那我们知道这个方法呢,其实是以一个战争的方式呢,存储在我们这个这个虚拟战当中,对吧,那么战争当中存储的像我们这里边声明的这些局变量,包括我们这个行参,OK,他们呢,都属于我们这个局部变量表里边声明的相关的一些变量,那相应的一些运算啊,就是我们在这个操作数站当中也进行一个临时的一些存储,OK,那这样的一个代码,我们看一下这个执行过程当中涉及到的这个方法区,虚拟基站,包括呢,程序计录器相关的一个调用情况。
01:23
行,那么这呢是我们说这个代码,这个代码的话呢,我是放在咱们这个目录下了,在咱们这个第九章里边的这个位置啊,就是这个代码行那类代码的话呢,首先咱们先做一个编译。OK,那首先我们已经已经做了一个编译,那编译完成以后的话呢,我们可以怎么着啊,首先呢,是看我们这个字节码文件对应的。OK是在这儿对吧,那找到我们这个自建码文件以后呢,点右键这块我们可以,那在这个控制台这块呢,首先咱们自己手动呢,使用一个JP这样一个指令,对它呢进行一个反编译,OK,嗯,这呢是针对叫method的。
02:07
哎,Area DEMO,嗯,点class,好,那咱们呢,可以给它写入到一个具体的这个文件当中,比如我们这个de test 2.tst。OK可以了是吧,行这我们就执行了,执行完以后呢,这个文件呢,就放在这儿了啊CTRLX,我这呢,把它也是放到咱们前面这个这个包下边。嗯,CTRLV1下放这儿了是吧?行,那这个文件呢,咱们也可以呢,直接双击打开也行啊,或者呢,跟咱们前面那几个一样啊,我CTRLV一下就放到咱们这个桌面上,这个时候呢,大家双击一下啊,这呢是用这个notepad加加打开的啊都可以啊行,那就用它看也行,那么这个时候呢,我们就能看到咱们当前写的这个程序啊,这个程序呢比较简单,它呢这个反编译,或者我们说叫反解析之后啊,把咱们底层的这个呃,算是这个资解码是吧,给它翻译出来,我们能够呢去直观的看是看到这个相关的一些这个结构,这呢是关于我们这个类的说明,这呢是我们这个当前类的一个声明的一个权限啊,这呢就是我们所谓的这叫常量池。
03:09
常列池里边这个信息呢,你会发现呢,还不少呢,对吧,虽然说我们这个代码呢,看似是比较简单,然后呢,我们主要来看的是这个main方法,因为main方法呢,里边是咱们具体的一个执行逻辑,对吧?May方法当中关于它这个参数的类型,关于它的这个呃,修饰符public static的,关于它是一个word的对吧?诶都有说明,Code这块呢,指明了我们当前的这个main方法,它的一个操作数占的一个深度是三,那局部变量表呢,它的一个长度呢是五,我们这个参数呢是一,这个参数的话呢,主要就指的我们这个main方法的这个行参好下边的话呢,就是我们具体的这个执行的一个过程。对吧,来执行这个过程,像前面呢,我们讲这个,嗯,去拟站的时候呢,我们也提过这个呃叫line number啊这呢就指出来是咱们这个,呃这呢涉及到是咱们这个字节码啊,这个全或者叫操作符,它的前面这个其实就是我们在这个,呃,其实就是个指令地址对吧,就是我们这个呃。
04:07
程序记录器里边我们所要保存的这个数啊,记录一下我们这个每一个指令呢,它的一个地址,那这个地址呢,跟咱们真实的一个代码的这个行号呢,它的一个对应关系,那咱们这呢,不就大概就是这个范围嘛,是吧,那这是一个对应关系,然后下边呢,就是咱们所谓的这个叫局部变量表。你看局部变量表里边,你看这个slot这块呢,是零一二三四一共呢是五个嘛,所以这呢我们推的就是五对吧,没问题,那在这个过程当中呢,我们使用这个操速度站呢,最多的这个站的深度呢是三,那了解一下就可以了,那这里呢,咱们主要就关注一下这里边的一个细节,执行过程是什么样子的,OK,那这个执行过程呢,我这呢是把它来这个呈现出来到我们这个PPT当中了,那我们按照这个代码的方式呢,一个一个往下走一走。OK啊,一可以往下走一走,行,这呢是咱们说的这个叫常量池嘛,对吧?哎,这呢,就我们所谓的这个A,不是不是说叫常量池,这是我们这个常量池,这是常量池,然后呢,我们在这个方法区里边,是不是也会保存我们具体这个方法的一个描述啊,包括呢,它的这个呃,代码的一个执行这样的一个过程,是吧?哎,像这些结构呢,都是存储在我们这个方法区当中的行,那么基于我们方法区的这样的一个没方法的一些指令,那我们看一下,在这个指令呢,一个一个执行过程当中,我们内存相互之间,内存之间的这个程序,计数器,虚拟站他们之间的一个协作关系。
05:27
嗯,这里边呢,咱们因为没有这个new对象,所以呢,我这儿就诶不涉及到这个,嗯,堆空间的一个显示了啊堆空间要显示的话呢,那就相对来说就更复杂一些啊行,那我们看一下,那我们就按照这个顺序呢往下走,首先的话呢,这里边儿提到了一个叫BA500。BSI是吧,SI啊哎,PUSH500,然后这时候呢,我们这个序号零,就是在我们这个程序记录器里边记录的当前的一个指令的一个地址啊,那么这个操作的话呢,它就会将这个500呢放入到我们这个操作站当中,OK,这是一个这样操作,然后接着叫s store1啊s store1呢,就是把我们这个数据呢,存储到我们这个本类变量表这个一的这个位置,我们所以呢,仍然是从零开始的。
06:12
这个本列变量表里边存储的就是咱们当前这个方法当中声明的各种变量,当然了也包括我们所谓的这个叫行参。啊,这个咱们前面呢,讲这个虚拟机站的时候呢,提到过,如果是一个非静态的方法的话呢,是不是在这个通常零的这个位置放的是不是就就是Z了。哎,就是这次啊,那这咱这是个静态方法,它就不放这次了,那这儿呢,因为有个行三,所以行三这个零呢,就放的是我们这个XOK,那把这呢,放的是一个一的位置,这个store啊是吧?这个呢,就是我们往里放的这个意思啊,这个放的时候呢,就用的是我们这个,诶往操作这个本地变量表啊,或者叫局部变量表打交道的。接着的话呢,我们叫B是100啊,这个时候呢,就是呃,取一个数100,把这100呢放到我们这个操作数站当中,然后呢,接下来呢,把它再存到我们的这个呃,本内变量板里边。
07:04
OK,没问题是吧,然后接下来呢,叫ILO杠一这呢是从我们这个本地变量板里边取数据的意思,那一呢就是这个索引,那就把这个500呢取出来,然后接下来呢,再把这个嗯二呢索引为二的这个本地边上面呢也取出来,就放在这儿了,哎大家呢,你看到我这里边放的是这样子的啊哎,这是500,然后突然放是这样的,这里边呢,要注意一个点,咱们前面已经讲过了,这呢是个复习本地变量宝呢,相当于是个数组。对吧?诶当我们编译完以后呢,其实大家也能看到啊,编译完以后呢,咱们看这个资金码文件的时候呢,就已经能够确定下来咱们的本地的编量表,包括呢,咱们这个操作站,这是一个深度,当然这个还好吧,因为呢,它是一个栈结构啊,对于咱们说这个数组结构来说呢,是不是一定要确定下来这个数组在创建的时候呢,它的一个长度是多少,对吧。哎,这个大家要注意啊,那实际上呢,咱们这个操作站呢,其实也是用一个数据结构来存放的啊,这个呢,就涉及到咱们数据结构里边讲的这个站,这个站呢,它是一个a dt啊,叫抽象数据结构a dt啊这个抽象数据结构的话呢,它既可以用我们这个数组来实现,它呢,也可以是用这个叫链表来实现的,那在我们这儿呢,这个操作站呢,其实也相当于是个数组来实现的,那这时候呢,我们其实也得知道你这个占到底是多长,对吧,因为我们造数组的话呢,一扭数组就需要明确指定它的一个长度,这个呢长度是三。
08:25
啊,这注意一下行,那操作数站注意它是个站本地变量表或者叫局部变量表,它是一个数组,所以呢,我们这儿呢,数组放的话呢,你就按照这个索引依次往下放,而我们这个操作站呢,这个下边呢,相当于是叫占底是吧?哎,这个上面是这个站点,我们就一个一个从这一端啊往里面去放数据,呃,因为呢,你100是后放的,所以呢,它就会在500的上面啊这也注意一下行,然后下边呢,是做了一个除法操作啊I的是吧,这个除法操作是因为我们这呢有个除法运算。OK,行,那我们就做这个运算,这个运算的话呢,会把我们操作数站里边这个500和100呢都取出来,它俩做一个除法之后呢,得到是五,那出来的这两个弹出站的数据呢,就不再进站了,那得到这个五呢,再放到我们这个操作站当中,然后呢,接下来一个叫S3,把这个五呢再存放在我们这个本地变量宝,这个是三的这个位置。
09:17
对吧,诶把这个五就放在这了,然后呢,接下来我们又有一个叫BA是50啊,再获取一个数五时,把它放在操作数站当中,然后呢,S到四,把它呢再放到我们这个位置上。开放这位置上,OK这就可以了,然后下边一个呢,叫get static,好,这时候大家注意这个get static这个操作啊,这个操作的话呢,我们如果看这它是不是对应呢,要调我们这个,呃,常量池当中这个井二的一个位置,对吧?OK,那咱们就往上去调调这个井二,呃,因为呢,这儿呢,对应的是一个属性啊,属性的一个引用这个井二的话呢,又掉了这个井25或者井26,那找这个25。25的话呢,就找到我们这个system啊,加26呢,也找到我们这叫那名字和这个类型啊,这个呢,又掉了我们的二三十二和33,这个32的话呢,其实是对应的我们当前这个呃,变量的一个名字啊,其实是一个out。
10:11
是吧,这个32呢,其实你往下看这个能找到的就是一个out,然后呢,这个33呢,对应的是我们这个tap,就是你这个变量的一个类型是什么呀,这个33往下再找一下啊,就是print STEM。哎,所以说呢,我们找到刚才说的这个减二,呃,它这里边儿整个调完以后呢,其实就是相当于前面呢,指明的就是我们这个属性,它所属的这个类啊是system。啊,这个类里边的这样一个属性,这个属性的类型呢,是print stream这样的一个类型。OK,这呢,就是调用了咱们这个常量池里边这个现有的这个结构。啊,线的这个结构啊,这呢就算是一个,呃,指定我们这样的一个符号啊,那真正我们再去运行这个代码的时候,真正我们点右键呢,去执行的时候呢,此时我们就会去在内存中去找,因为这里边我们是不是要用到system,要用到这个print stream了,我们就真实的呢,就把这里边儿这个所谓的符号引用呢,哎,比如刚才我们找到这块,你这这个class是吧?哎,就看一下你这个class对应的这个system,所谓的这个符号引用呢,真实的对应的内存中啊,其实也是方法区中这个system类呢,是不是被加载过,如果没有的话呢,就要把我们这个system类呢给它加载进来,然后呢,我们这里所谓的这种符号引用啊,就转换为真实你加载到内存中的那个system了。
11:25
啊,就是一个直接引用啊,那同样的我们这个print STEM呢,也是同样的道理,对吧?你看这儿呢,它只是一个字符串的一个形式啊,是一个自变量的一个形式,那真正我们要用的时候呢,你肯定是要用的一个具体的这个类啊,都需要做一个加载啊,在运行的时候呢,说白了,咱们现在看的这些呢,都是属于自解码文件的这个信息啊,这都算是一些符号引用了啊,这个这呢,你可以看成是一些具体的字面量是吧?看像这呢,都是算一些符号引用,真正呢,当我们去执行的时候呢,都需要把它们对应的这个结构呢,转化为直接引用啊,就符号引用转直直接引用诶是比较难的啊,大家不太容易理解啊,一定要去体会一下这个问题。
12:06
行,这呢就是咱们回过来要说的,这叫get static啊,最终目的呢,咱们是不是掉了system叫al是吧?哎,Al呢,你接着干什么事啊,哎,这个呢,我们把这个三的这个位置取出来。哎,这个取出来是个五把它呢,放在咱们这个操作站当中,哎,刚才那个呢,是不是有个减二是吧,这个取出来它,然后c.out哎,我们呢,接下来诶看看我们接下来这块是什么呀。啊,这是个50啊,接下来我们是不是要做个加的运算,一方面呢,是我们刚才那个五,再一个呢,是把这个50呢也得给它,诶拿到是吧?好,那接着回过来这个呢,先把五放这儿,然后呢,再把我们下边这个50呢也放过来,哎,这个五跟50呢就都有了,然后让他俩做一个加的运算,加的运算呢,结果运算完以后是55,然后再接下来掉了一个叫invo。In watch啊,这个时候呢,相当于是我们做了一个执行这个。
13:02
一个操作是吧,那我们在这看一下。哎,这呢是这个井三,哎,那这个井三的话呢,往上找。哎,在这呢是吧,哎在这里边呢,是一个方法的引用,因为我们要调这个print line这样的一个方法。对吧,哎,就我们这两个方法啊,那这个方法的话呢,是一个方法引用,它是27里边的二十七点二十八,那找27 27呢在这儿就是print stream啊这样一个类,然后找28 28呢是一个print line,这个print line呢,它这个名字呢这块,呃其实又得调了一个35和36,哎35呢指明是你这个方法名,然后呢,36呢是它相应的这个,诶算是这个参数的类型是一个word的,然后那里边是放了一个in特型的这个值是吧?哎相当于呢,就是我们现在做一个调用,就是把刚才呢咱们诶算出来这个数呢,诶是不是要调一下我们这个print这个方法。对吧,诶叫personal一个方法,然后关于这个invoke virtual还是invoke这个special是吧,等等这些呢,咱们到后边讲这个指令的时候呢,还要去说啊,还要去说VO special呢,像典型的咱们说的这个构造器啊,都是special啊,VO virtual这呢叫虚方法调用啊,因为有可能我们这个pre方法是存在着这种想重启啊等等这样的问题是吧。
14:22
哎,像这种方法可能被重写的方法呢,我们都是以某个魔啊,行,哎,这个咱们后边讲中篇的时候呢,会说到关于具体方法调用这块呢,呃,常见的啊,几个啊,几个具体的指令啊,这个不用担心,而且呢,一解释其实也非常简单,行,那么调列方法的话呢,就是把我们最终的那个结果55呢,还做一个输出就可以了。哎,输入就可以了,在整个这个执行的过程当中,大家也会发现呢,咱们这个程序计数器呢,是不是始终会记录当前,呃,这个线程当中,咱们这个没方法它的一个指令地址,那因为有可能我们这个CPU呢,随时可能会切换,从咱们这个当前的线程切换走,那在切换回来的时候呢,咱们得记录啊执行到哪儿了,对吧?诶,这就是我们程序计数器它的一个逻辑。
15:07
OK,那最后呢,这个咱们有一个return,就是结束咱们这个方法的一个调用,这个return结束执行完以后呢,咱们相当于整个这个当前虚拟一战当中,我们没方法的这个战争啊,就没有作用了啊,他呢就直接呢就弹出战了。OK啊,那程序计数器呢,你可以看一下,你这个没方法完以后呢,还要执行其他的一些当前线程中的方法吗?如果有的话呢,这个呃,程序计数器的,再记录人家的那个具体的指令地址就可以了。OK啊,那我们这里边执行这没方法的这些操作呢,实际上也是算在这个方法区当中存放的。嗯,这个大家关注一下行,那通过这个呢,其实咱们相当于又是熟悉的前面讲的程序计数器和虚拟站啊,这呢,我没有再去这个添加一个这个堆空间啊,堆空间呢,就涉及到我们这个对象结构了,对象结构呢,相对来说又更复杂一些,复杂到哪了呢?就是我们这个堆空间里边你用的这个对象它呢,是不是还会里边有一些部分信息呢,要指向我们方法区里边的你这个对象实体的,比如我们说这个类型信息。
16:08
啊,那么这个指向的话呢,这就涉及到我们堆空间中,你这个对象呢,它的一些内存布局啊,这个的话呢,咱们放到下一张啊,就是这一张。哎,这张呢,咱们给大家讲。啊,就是涉及到呢,咱们这个对象堆空间,它的一个实例化的一个方式过程是吧?啊以及呢,内存布局和访问定位这个呢,在大场面面试当中啊,也经常爱被问到这样的问题啊是比较细致的,行,那咱们讲方法区的时候呢,咱们再说这个第十章这个情况啊行,那我们这个例子呢,就先说到这儿。
我来说两句