00:00
那第二个过程呢,叫类的链接过程,说完以后呢,我们来看第三个过程,这个过程呢,称为叫初始化啊initialization,那我们看一下这个过程当中都有哪些具体的操作,首先呢,说初始化过程呢,它就是执行类构造器方法啊,叫CL in这样的一个过程啊,这呢提到叫类构造器方法,大家可能之前没有听过啊,这个首先方法不等同于咱们说的类的构造器啊,它不是一回事,呃,这个方法名呢已经被定义好了,CL呢可以列成class啊,Class的一个in的方法,说这个方法的话呢,不是不需要咱们来定义啊,咱们在这个使用一个Java类的时候呢,也从来没有定义过这样一个方法啊,不需要你定义张RC的这个编译器呢,会自动的收集类当中所有的类变量,它的一个赋制动作,比如说你的显示赋值和静态代码块中的语句呢,给它合并出来,诶,生成这样的一个诶类构造器方法。哎,就是这个意思啊,那我们首先呢,来看一下这个方法,让大家有一个比较直观的一个印象啊,那这块我们打开这样的一个程序,打开以后的话呢,这块已经有一些代码了,咱们把它直接删掉啊,比如说我们在当前类中,我声明一个,呃,权限其实是什么样的都无所谓了,静态的一个变量,哎,我就要一个int型的number复制为一个一,好,然后呢,我写一个main方法,哎在这里边呢,来打印咱们当前这个类的,哎,这个值啊,这个数值要执行的话呢,这个大家肯定都清楚啊,结果呢,就是一。
01:29
哎,结果就是一啊,这个不用多说,那我们现在想看一下,就是这个词解码文件当中啊,就是咱们刚才直接去rise了,其实在这个具体的执行之前呢,肯定会先编译成自解码文件,呃,自解码文件我们这块呢,稍微刷新一下啊,嗯,在我们第二章当中,咱们当前是class in test啊是这样的一个词解码文件,这个词解码文件啊,我们,嗯,这么着啊,我CTRL一下。诶,我把它放在这个桌面上啊,然后呢,使用它默认的这样的一个工具,大家需要提前装一下啊,就是呃接class lab啊,然后呢,直接双击打开。
02:09
打开的话呢,这就是咱们的这个字节码文件,它的一个整体结构啊,咱们讲第二篇这个自检码的时候呢,会一点点带着大家来看一下,就是到时候咱们会怎么讲啊,到时候会这样讲。先拿这个bary river,呃,先用它把咱们这个自建码文件呢拖进来,大家你看到的这都是16进制来表示的这些呃,字节了哈,咱们一个一个来翻译啊,一个一个来翻译,嗯,然后呢,其实把它翻译完以后,这个简单化啊,其实就是我们看到的这样一个整体的结构啊,这就是关于咱们这个,呃,当前class in test这样一个字节码文件,然后大家看一下这关于method的这个位置。诶,在method这个位置呢,大家你会看到,哎,我们这里边有一个叫CL in。诶,大家会看到这个CL in这个呢,不是咱们这个手动写的,是它自动帮我们生成的这个main方法呢,是咱定义的啊,大家还会看到另外一个结构,这个咱们啊一会来说啊,在这个CL当中,我们看下它的这个code的代码呢,就是我们这有一个,呃,这个把这个变量一啊引进来了,是一个静态的一个变量啊number它的一个值,诶就是这样个情况啊,行这呢我们就诶说明了一下,就是这个c in呢,不是咱们自动来定义的啊,那我们再稍微复杂化一下,那就是我再写一个static的一个静态代码块。
03:35
在这个代码块当中,我对这个number呢进行一个重新赋值,诶重新复制完以后呢,咱们再运行结果肯定是二了啊,你也可以不运行,咱们在build这块有一个重新编译。诶,重新编译也可以,那此时的这个自解码文件啊,以防万一你刷新一下啊,它就是一个呃,新的这样的自解码文件了,把它呢,CTRLC再重新的,诶在我们桌面上呢,进行一个覆盖啊,覆盖完以后呢,再双击打开,再看我们这个method的这个CL in它的一个code,诶大家会看到它一开始复制为一,然后接下来呢,会重新复制为二,那最终呢,我们这个结果方法正常的结束了啊,它的结果呢就是二。
04:19
哎,就是这样的一个过程,也就是说呢,我们这个CL这样一个构造器方法呢,它会把我们的显示初始化和静态代码块的一个初始化合并在一起,构成我们这样的一个方法啊,就是这样的一个结构啊好,下边呢提到说构造器方法它的指令呢,是按照语句在原文件当中出现的一个顺序执行的。啊,一个顺序执行,就比如说咱们刚才呢,现在这个程序写完以后,这个number的这个值呢,是二,这个大家应该没有任何的异义,对吧?然后呢,我们想说明一个特别的情况,比如我在定一个呃,Static in对型的一个变量,啊,我称为叫number吧,这个number的一个值呢,我先写成是十,这个变量呢,咱们可以在s static静态代码块中对它进行一个重新赋值。
05:11
啊,这个不知道大家之前有没有写过这样的这个程序啊,我们声明的话呢,诶看似是在下边,然后它的一个重新赋值呢,是写在上面的,这是可以的,诶这是可以的啊,诶因为呢,我们这个大家应该清楚啊,咱们刚才讲过了,第二个过程呢,叫做link是吧,它叫linking啊在linking当中。他有一个环节呢,叫做诶。在这个prepare这个环节当中,咱们已经给这个number呢复制为零了,诶大家注意啊,相当于这个变量呢,已经被加载到我们内存里边,它也被了,被做了一个叫呃,默认初始化啊,然后这已经是在前面执行过的操作了,在我们现在的啊叫initialization。
06:00
这个我简写了啊呃,Initial手在这个环节当中,咱们开始给它进行一个重新的覆盖,那么这个覆盖过程呢,会按照这个顺序,先是20,然后呢,再把这个20呢覆盖掉,诶改成这个十,也就是说此时呢,我们再去运行打印一下咱们当前这个类的,诶number这样的一个值的话呢,它就变成了十,对吧?诶我们可以跑一下执行啊。哎,确实是实,那我们来看一下这个自解码文件中的这样一个过程,那我刚才因为运行过了,所以这个自解码文件它已经是经过重新编译了啊,我把它呢CTRLC一下。哎,这个我们把它关掉啊,CTRLV覆盖一下当前文件,双击打开,然后还来看我们这method,找我们这个CL in,诶,Code,诶,哎,这个里边我们看着就会稍微的复杂一些哈,因为涉及到两个变量,一个是number,一个是这个,一个是nu啊,一个是我们这个number有两个变量啊。
07:04
那针对于我们这个number变量呢,你看一开始值是一,然后复制为二了,针对于我们,呃,这个nu是吧,U3个字母这个啊,然后这个比较长,这个number呢,这个先复制为20,然后呢又拿十,哎覆盖掉了20,所以最终结果呢是十,诶我们从次解码的角度看这个过程呢,非常的清楚啊,就是当你有不清楚的这个情况下呢,其实你就看这个资金码文件,其实呢,呃,很多问题呢,都可以迎刃而解,当然前提呢是你对这些指令呢要清楚才可以啊,这个咱们在讲解这个后续包括虚拟驿站的过程当中,还会带大家来看相应的这些结构的啊,就是先有一个初步你能看得懂,然后具体详细的每一个部分,我们放到这个自己码文件的时候呢,再来详细的讲解啊,自己码指令。好,这个呢,就是我们强调这个问题,但这里边儿呢,需要注意一个点啊,就是比如在这个位置,我去打印一下谁呢,Number这个三个字母,这个number啊,大家发现呢,是没有问题的,但是如果我们试图去打印一下这个变量,你会发现呢,它就报错了,诶它就报错了,这个错误的信息呢,就是非法的叫前项引用。
08:15
啊叫非法的前项引用,这个大家需要注意一下啊。嗯,报错。前项来引用行,这个大家要注意一下,就是我们声明的变量呢,是在后面的,那么在我们这个前面的静态代码块当中,你是不可以去调用它的啊,你是可以去给它复制,但是不能够再去调用它,这个大家要小心一点。诶保存一下好,这呢是我们说的这个问题,接着往下看说呢,这个C,哎,它呢不同于类的构造器,类的构造器咱都知道了啊,它是跟构造器是不一样的,那这个构造器呢,是从我们虚拟机的视角下来看,就是in这样的一个函数,哎,我们可以称为叫类的构造器函数,那叫in,呃,那这块呢,就诶又回顾咱们以前讲讲过的这个一个知识了啊这个咱们好像可以再看另外一个类了,看这个吧。
09:15
看这个类啊,这个类里边其实这个结构呢,写好了,我也不想用它了,不想用咱们就把它就干掉得了啊。哎,我就删了,行,那这呢是我们定义了一个类,里边什么也没有,什么也没有,那咱们稍微整点东西吧,比如说private int型的一个变量A,这个我复制为一啊private,呃,Static这个static变量我就先不定义了,然后呢,我们整一个main方法。恩恩。然后我int一个B等于一个二,就简单的写两行代码啊,写完以后的话,我们可以直接呢,在这个位置,大家呢,就可以直接进行一个诶重新编译啊。
10:00
好,编译完了,那我们就会在这个位置找到咱们对应的这样的一个自解码文件啊,就它啊,然后把它呢,CTRLC诶放到这儿,然后双击打开,大家看一下我们当前这个类当中,我没有声明静态的变量,也没有静态代码块,那么问一下我们此时这个结构的方法当中会存在C内的这样一个呃类的构造器函数吗?哎,会存在我们刚才提到的这样一个诶类的构造器,呃,方法是吧,存在吗?诶显然呢,是不存在的,你会发现没有对吧?也就是说我们这个类的构造器方法,它就是来将我们对类变量的一个复制动作,就显示复制和竞代码块结合起来的,那如果你要是没有这样的操作呢,它就不会给我们进行生成这样的一个方法了,那我们这个加上看in一个c static的。诶,这个我写成一个三,那此时的话呢,我们再进行一个重新的编译。
11:06
诶重新编译了,编译完以后的话呢,我们再把这个文件CTRLCL。把这个关掉,覆盖一下再打开,显然呢,这时候我们就会生成这个CL内的这样的一个呃,叫类的构造器方法啊,这个就很好理解了,好,我们把这个问题说清楚了啊,然后呢,我们回过来再说下边这个情况,我们发现呢,不管是刚才的情况一,还有我们刚才修改以后,大家都会在method的这个结构里边发现一个呃方法叫做in,这个in的话呢,其实就是我们的构造器函数。哎,因为咱们提过说,哎这里边把咱们以前讲Java语言的这时候的这个知识呢,这个强调一下哈,说任何一个类啊,生病以后啊说都啊生病以后任何别逗号了啊,任何一个类啊生病以后说内部呢,至少哎我们说存在一个类的构造器。
12:05
诶,这个应该很清楚啊,这个构造器呢,可能是你显示声明呢,也可能是系统给我们默认提供的,那像当前呢,就属于默认提供的,那既然呢,一定会存在构造器,那么这个构造器那对应过来就是咱们的这样的一个方法。那这个扣子里边呢,咱们因为也没写什么具体的这个信息了啊,诶我们相当于你看咱知道啊,咱们在这个。呃,构造器当中会调用负类的构造器吗?你看这有个AB的这个in,它的一个调用啊行,大家能清楚这个过程,那我们此时呢,呃,显示的咱定义一个啊public。哎,CL in啊,咱们定义一个这个构造器啊,在这个构造器当中,嗯,这个一方面呢,我们给这个A呢,比如说赋值为十啊,这是一个,然后我再定义一个呃D这样的变量,我命名为20,好,写完了啊,写完以后呢,我们再做一个重新的编译。
13:06
重新编译呢,我们再用这个文件看一下,呃,这个我每次都得去桌面来找这个去去粘过来,然后再去打开,挺麻烦的啊,那这块呢,我们用的这个工具呢,诶大家刚才注意没有啊,我们用这个工具呢,叫做诶接class lab是吧,这样一个工具,诶大家呢,直接在idea当中啊,在idea当中呢,我们打开这个设置啊设置呢有这个插件,诶大家知道这个idea呢,有非常丰富的这个插件,大家在这块呢,去直接搜索这个接class lab。哎,这样个插件就可以了,我这因为已经安装过了啊,我就不用再装了,诶当你把它安装完以后呢,Idea会提示你在做一个重启,OK,重启一下就行了,重启完以后呢,呃,我们首先呢,需要做一个编译一些啊,编译完以后大家呢,在这个view的位置你就能够看到叫show啊BA code with j class lab,直接点击一下啊,在这呢就能呈现这个看到的效果,跟我们用本身这个软件看到这个效果实际上呢是一样的啊,是一样的,大家就不用每次都在桌面上,呃,放过来以后直接把文件,然后再打开看了啊,所以我们就把它关掉了。
14:16
行,那此时呢,我们再打开这个method来看,咱们打开这个in code,好,这里边就我们具体这个操作了,呃,咱们关于这个变量A呢,那进行了一个重新赋值是十是吧,然后这个重新定义一个变量D,诶赋值为20,它现在的操作呢,都出现在我们的这个诶构造器当中了啊那这个构造器呢,嗯,我们生成以后这个自节码文件里边对应的这个阶段in这样一个结构啊好,这是in的这样一个方法。好,这就过了啊,然后呢,说若该类具有负类啊,若该类具有负类,说虚拟呢,会保证子类的CL在执行之前呢,负类的C2呃已经执行完毕了,就是此类这个执行一定要晚于这个负类的执行,那这块呢,我们再通过一个代码呢来看一下啊,这个代码也比较简单,我就直接拿过来说明一下,好大家看啊这呢,我定义了一个内部的静态内部类叫father,然后又义了个静态内部类叫做丧,呃,这个丧呢去继承于father,在这个负类当中,我们定义了一个呃静态的变量A啊,一开始只是一静态代码库中复制为二,然后在这个S当中呢,我们定了个B啊,B呢拿A来重新赋值,那我们在这个外层的呃,测试的类当中的内方法中打印这个子类的一个静态的变量B,那问一下大家,这个结果是几呢?是零,是一还是二?
15:45
如果你要清楚咱们刚才说这个过程啊,哎,你就应该知道我们在给我写这儿吧,诶写到这个上面吧,这。诶,当我们调用这个上点B的时候呢,其实咱们我要执行B方法,它会将我们当前这个类啊测试类加载到内存当中,当然加载这个测试类之前呢,先把它的负类叫加加载啊,咱先就不多说这个了,把它加载完以后,调用它的静态方法,在静态方法当中,我们又调用了另外的一个类的一个静态的变量,所以此时呢,我们会将这个S这个类呢,诶加载进来,然后加载这个S类的时候呢,我们就要执行它对应的C2内的这个方法了,但是执行它之前,我们先要执行负类的啊这个加载,所以呢,咱们首先会加载father类。
16:36
哎,然后呢,其次加载咱们的这个丧这个类,那因为呢,我们先加载了father类,中间呢,又会涉及到它的,呃,加载这个链接,哎,初始化的过程,所以整个这块呢,一步都不少,执行完以后呢,我们再加载上类的时候呢,呃,在初始化这样一个环节把A的值付过来,那其实这个A呢已经是二了,所以我们这个结果呢,诶就是二,哎先执行一下看效果。
17:04
诶没错,那确实是这个二对吧?诶那这时候如果你想看一下这个对应的这个字码文件的话呢,这个我们可以刷新一下啊。啊,然后这个method,那这时候咱们看的是这个内部的这个sun这个类啊,Sun这个类当中,诶,我们说任何一个类,其实你都会看到它有一个这个in的方法啊,因为他都会有这个构造器嘛,然后这个CR,这是它的一个静态的结构CR,因为咱们这有一个呃静态的变量嘛,所以它会有这样的一个方法的生成,呃,这个我们想加载的是这个B嘛。诶把这个往这拽一下,呃,CR这个code,这个code里边呢,呃,先引入了这个A的值,然后把这A的值呢,赋给这个B的这个值,哎,你看现有这个A嘛,对吧,那这个A呢,就是由于我们这个负类呢,提前已经加载过了,哎这样一个情况导致的啊行,那这呢,就是我们所说的,诶此类在加载之前啊,先得保证负类进行一个加载啊这就过了,然后最后一个点说虚拟机必须保证一个类的CR Internet的这个方法呢,在多线能下呢,是被同步加锁的啊这是什么意思呢,想想。
18:18
我们说呀,一个类哎,它往内存中加载的话呢,只需要加载一次就可以了啊,加载完以后我们说把它放在这个叫方法区了,那方法区呢,在JDK有这个还不太清楚啊,在JD8的时候呢,呃用的叫源空间啊,那这个呃原空间的话呢,其实使用的就是本地内存啊,也就是说我们这个呃类加载到内存中以后,它其实是使用的这个呃直接内存给缓存起来了。啊,缓存起来了,那我们之后如果你再使用这个类的话呢,实际上都是用的内存中已经存在的这个类本身。啊,已经存在这个类本身,那么也就是说我们虚拟机执行类的加载的时候呢,它只会调用一次啊CL这个方法啊,保证我们这类呢,就只加载一次,那如果我们你看假设这就是这个类要加载啊,咱现在呢,有两个线程想加载它,那假设这个线程A啊,这是B啊,A在加载的过程当中,我们不让它加载完,不让它加载完啊,这个可以通过代码来实现,然后这个B呢,你想想它要再去加载的话呢,如果说他们互相不矛盾,里边呢有些代码他们都会执行,那如果说这个A没加载完B,呃,就不让他进来,里边可能我们通过一些输出语句来控制,看一下这个B还会不会试图进行加载啊,说的可能有点绕哈,咱们直接来看这个代码就行。
19:43
来大家看一下,我这已经写好了一个程序啊,哎,其实也比较简单,这呢是圈类,我这呢创建了一个类,这个类呢,我有一个静态的代码块。这个静态代码块里边有个输出语句啊呃,输出语句后边呢,有一个well处啊,你会看到这个程序呢,呃,静态代码块是执行出不来的,OK,然后呢,我这呢写在测试类里边呢,就是定义了两个线程啊,这我使用的拉姆达表达式啊,定义两个线程,嗯,然后一个叫线程一,一个叫线程二,我让两个线程呢去start。
20:16
啊,那么我们先分析一下啊,Star的时候呢,他们就会执行,分别来执行我们这个run方法,那他们就会相继的会把这个语句呢输出,输出完以后,那比如说这个线程一先抢到这个语句的执行,其实他们两个是个并行执行的哈,这个线程一呢去执行它,线程二也在执行它,那它俩呢都要去初始化我们当前这个spread这样一个类,诶,但是呢,我们这个类呢,只能被初始化一次,所以你会发现呢,只会有一个线程进来执行这个static把它输出了。哎,如果说我们这个类呢,会加载两次,那你就会看到这个语句呢,可能会被输出两次了,因为大家各自加载各自的嘛,啊,那如果说呢,多个线程去访问同一个类的话呢,这个类说只能被加载一次,那我们就会看到这个语句呢,就只能执行一次。
21:06
因为你执行这一次还没加载完呢,其他的线程呢,进不来啊,这就相当于是一个同步加速的过程啊,来我们跑一下。好,大家看是不是就我们刚才说的这个情况吧。哎,这里边呢,是线程二先开始的啊,线程一开始这两个无所谓啊,它俩并行,谁先谁后都有可能,然后线程二呢,随后抢到了我们对这个类的一个初始化,它就进去了,但是呢,线程二现在初不来,哎,它还在初始化我们这个类,那线程一的话呢,它也没办法进去,因为呢,现在处于一个加速的状态,哎,这就是我们的一个演示,也就是说来验证一下我们,呃一个类呢,只会被加载一次啊,那像类似这样的代码呢,大家需要去避免哈,加载过程当中如果出现问题,会导致其他的线程都进入一个阻塞的状态啊,这个大家要小心一下,诶,我们把它呢关掉。
22:02
好,那通过刚才一些代码演示呢,大家应该比较清楚我们的一个初始化过程啊,对这里边儿的每一句话呢,大家应该有一个比较清晰的理解,好,那整个的话呢,就是我们要强调的类的加载过程啊,这样的三个阶段。好,这个我们就完了,诶这个大家呢,如果在面试当中问到这个问题的时候呢,能够基本上说清楚每一个过程当中的一个主要的操作是什么就可以了啊就可以了。
我来说两句