丰满乳胸确乎有读者在上一篇著作里留言说

  • 首页
  • 免费观 看成人网站
  • 将粗大挺进邻居人妻
  • 亚洲国产成人AV线
  • 伊人狼人大蕉香AV
  • 行房时间短怎么治
  • 丰满乳胸确乎有读者在上一篇著作里留言说
    发布日期:2022-09-23 06:07    点击次数:92
    别这是厨房会有人来丰满乳胸

    全球好,我是阿谁永远 18 岁的老妖魔~嘘

    从《JVM 内存区域分裂》这篇著作中,全球应该 get 到了,Java 造谣机内存区域不错分裂为要领计数器、Java 造谣机栈、土产货步骤栈和堆。今天,咱们来围绕其中的一个区域——Java 造谣机栈,深刻地张开下。

    先诠释一下哈。这篇著作的标题里带了一个“携程口试官”,有标题党的嫌疑。但有一说一,确乎有读者在上一篇著作里留言说,携程口试官问他了 Java 造谣机内存方面的学问点,是以今天的标题我就“借题表露”了。

    从“相遇恨晚”这个词中,我估摸着这名读者在这道口试题前边折戟沉沙了。这样说吧,口试官确乎可爱问 Java 造谣机方面的学问点,因为很能测验出又名应聘者的果然功底,是以我筹办多写几篇这方面的著作,但愿能给全球多少量点匡助~

    Java 造谣机以步骤四肢基本的实行单元,“栈帧(Stack Frame)”则是用于撑持 Java 造谣机进行步骤调用和步骤实行的基本数据结构。每一个栈帧中都包含了局部变量表、操作数栈、动态流通、步骤复返地址和一些格外的附加信息(比如与调试、性高手机料到的信息)。之前的著作里有提到过这些办法,并做了一些浮浅扼要的先容,但我合计还不够详备,是以这篇要点要来先容一下栈帧中的这些办法。

    1)局部变量表

    局部变量表(Local Variables Table)用来保存步骤中的局部变量,以及步骤参数。当 Java 源代码文献被编译成 class 文献的时间,局部变量表的最大容量就仍是详情了。

    咱们来看这样一段代码。

    public class LocalVaraiablesTable {     private void write(int age) {         String name = "默然王二";     } } 

    write() 步骤有一个参数 age,一个局部变量 name。

    然后用 Intellij IDEA 的 jclasslib 稽查一下编译后的字节码文献 LocalVaraiablesTable.class。不错看到 write() 步骤的 Code 属性中,Maximum local variables(局部变量表的最大容量)的值为 3。

    按理说,局部变量表的最大容量应该为 2 才对,一个 age,一个 name,为什么是 3 呢?

    当一个成员步骤(非静态步骤)被调用时,第 0 个变量其实是调用这个成员步骤的对象援用,也即是阿谁大名鼎鼎的 this。调用步骤 write(18),试验上是调用 write(this, 18)。

    点开 Code 属性,稽查 LocalVaraiableTable 就不错看到详备的信息了。

    第 0 个是 this,类型为 LocalVaraiablesTable 对象;第 1 个是步骤参数 age,类型为整形 int;第 2 个是步骤里面的局部变量 name,类型为字符串 String。

    诚然了,局部变量表的大小并不是步骤中总共局部变量的数目之和,它与变量的类型和变量的作用域料到。当一个局部变量的作用域箝制了,它占用的局部变量表中的位置就被接下来的局部变量取代了。

    来看底下这段代码。

    public static void method() {     // ①     if (true) {         // ②         String name = "默然王二";     }     // ③     if(true) {         // ④         int age = 18;     }     // ⑤ } 
    method() 步骤的局部变量表大小为 1,因为是静态步骤,是以不需要添加 this 四肢局部变量表的第一个元素; ②的时间局部变量有一个 name,局部变量表的大小变为 1; ③的时间 name 变量的作用域箝制; ④的时间局部变量有一个 age,局部变量表的大小为 1; ⑤的时间局 age 变量的作用域箝制;

    对于局部变量的作用域,《Effective Java》 中的第 57 条提议:

    将局部变量的作用域最小化,不错增强代码的可读性和可人慕性,并裁减出错的可能性。

    在此,我还有少量要请示全球。为了尽可能省俭栈帧耗用的内存空间, 熟妇局部变量表中的槽是不错重用的,就像 method() 步骤演示的那样,这就意味着,合理的作用域有助于擢升要领的性能。

    局部变量表的容量以槽(slot)为最小单元,一个槽不错容纳一个 32 位的数据类型(比如说 int,诚然了,《Java 造谣机圭表》中莫得明确指出一个槽应该占用的内存空间大小,但我认为这样更容易相识),像 float 和 double 这种明确占用 64 位的数据类型会占用两个紧挨着的槽。

    来看底下的代码。

    public void solt() {     double d = 1.0;     int i = 1; } 

    用 jclasslib 不错稽查到,solt() 步骤的 Maximum local variables 的值为 4。

    为什么等于 4 呢?带上 this 也就 3 个呀?

    稽查 LocalVaraiableTable 就澄澈了,变量 i 的下标为 3,也就意味着变量 d 占了两个槽。

    2)操作数栈

    同局部变量表通常,操作数栈(Operand Stack)的最大深度也在编译的时间就详情了,被写入到了 Code 属性的 maximum stack size 中。当一个步骤刚脱手实行的时间,操作数栈是空的,在步骤实行经由中,会有各式字节码指示往操作数栈中写入和取出数据,也即是入栈和出栈操作。

    来看底下这段代码。

    public class OperandStack {     public void test() {         add(1,2);     }      private int add(int a, int b) {         return a + b;     } } 

    OperandStack 类共有 2 个步骤,test() 步骤中调用了 add() 步骤,传递了 2 个参数。用 jclasslib 不错看到,人妻少妇偷人精品视频test() 步骤的 maximum stack size 的值为 3。

    这是因为调用成员步骤的时间会将 this 和总共参数压入栈中,调用结束后 this 和参数都会逐个出栈。通过 「Bytecode」 面板不错稽查到对应的字节码指示。

    aload_0 用于将局部变量表中下标为 0 的援用类型的变量,也即是 this 加载到操作数栈中;

    iconst_1 用于将整数 1 加载到操作数栈中; iconst_2 用于将整数 2 加载到操作数栈中; invokevirtual 用于调用对象的成员步骤; pop 用于将栈顶的值出栈; return 为 void 步骤的复返指示。

    再来看一下 add() 步骤的字节码指示。

    iload_1 用于将局部变量表中下标为 1 的 int 类型变量加载到操作数栈上(下标为 0 的是 this); iload_2 用于将局部变量表中下标为 2 的 int 类型变量加载到操作数栈上; iadd 用于 int 类型的加法运算; ireturn 为复返值为 int 的步骤复返指示。

    操作数中的数据类型必须与字节码指示匹配,以上头的 iadd 指示为例,该指示只可用于整形数据的加法运算,它在实行的时间,栈顶的两个数据必须是 int 类型的,不可出现一个 long 型和一个 double 型的数据进行 iadd 号召相加的情况。

    3)动态流通

    每个栈帧都包含了一个指向运行频繁量池中该栈帧所属步骤的援用,持有这个援用是为了撑持步骤调用经由中的动态流通(Dynamic Linking)。

    来看底下这段代码。

    public class DynamicLinking {     static abstract class Human {        protected abstract void sayHello();     }          static class Man extends Human {         @Override         protected void sayHello() {             System.out.println("须眉哭吧哭吧不是罪");         }     }          static class Woman extends Human {         @Override         protected void sayHello() {             System.out.println("山下的女人是老虎");         }     }      public static void main(String[] args) {         Human man = new Man();         Human woman = new Woman();         man.sayHello();         woman.sayHello();         man = new Woman();         man.sayHello();     } } 

    全球对 Java 重写有了解的话,应该能看懂这段代码的理由。Man 类和 Woman 类袭取了 Human 类,况兼重写了 sayHello() 步骤。来看一下运行效果:

    须眉哭吧哭吧不是罪 山下的女人是老虎 山下的女人是老虎 

    这个运行效果很好相识,man 的援用类型为 Human,但指向的是 Man 对象,woman 的援用类型也为 Human,但指向的是 Woman 对象;之后,man 又指向了新的 Woman 对象。

    从面向对象编程的角度,从多态的角度,咱们对运行效果是很好相识的,但站在 Java 造谣机的角度,它是若何判断 man 和 woman 该调用哪个步骤的呢?

    用 jclasslib 看一下 main 步骤的字节码指示。

    第 1 行:new 指示创建了一个 Man 对象,并将对象的内存地址压入栈中。 第 2 行:dup 指示将栈顶的值复制一份并压入栈顶。因为接下来的指示 invokespecial 会破钞掉一个面前类的援用,是以需要复制一份。 第 3 行:invokespecial 指示用于调用构造步骤进行启动化。 第 4 行:astore_1,Java 造谣机从栈顶弹出 Man 对象的援用,然后将其存入下标为 1 局部变量 man 中。 第 5、6、7、8 行的指示和第 1、2、3、4 行雷同,不同的是 Woman 对象。 第 9 行:aload_1 指示将第局部变量 man 压入操作数栈中。 第 10 行:invokevirtual 指示调用对象的成员步骤 sayHello(),留意此时的对象类型为 com/itwanger/jvm/DynamicLinking$Human。 第 11 行:aload_2 指示将第局部变量 woman 压入操作数栈中。 第 12 行同第 10 行。

    留意,从字节码的角度来看,man.sayHello()(第 10 行)和 woman.sayHello()(第 12 行)的字节码是透彻疏浚的,但咱们都知道,这两句指示最终实行的筹办步骤并不疏浚。

    究竟发生了什么呢?

    还得从 invokevirtual 这个指示入部下手,看它是若何已毕多态的。字据《Java 造谣机圭表》,invokevirtual 指示在运行时的通晓经由不错分为以下几步:

    ①、找到操作数栈顶的元素所指向的对象的试验类型,记作 C。

    ②、要是在类型 C 中找到与常量池中的描写符匹配的步骤,则进行看望权限校验,要是通过则复返这个步骤的径直援用,查找箝制;不然复返 java.lang.IllegalAccessError 突出。

    ③、不然,按照袭取关系从下往上一次对 C 的各个父类进行第二步的搜索和考据。

    ④、要是弥远莫得找到允洽的步骤,则抛出 java.lang.AbstractMethodError 突出。

    也即是说,invokevirtual 指示在第一步的时间就详情了运行时的试验类型,是以两次调用中的 invokevirtual 指示并不是把常量池中步骤的符号援用通晓到径直援用上就箝制了,还会字据步骤接管者的试验类型来聘用步骤版块,这个经由即是 Java 重写的本色。咱们把这种在运行期字据试验类型详情步骤实行版块的经由称为动态流通。

    4)步骤复返地址

    当一个步骤脱手实行后,唯一两种花样不错退出这个步骤:

    浅薄退出,可能会有复返值传递给表层的步骤调用者,步骤是否有复返值以及复返值的类型字据步骤复返的指示来决定,像之前提到的 ireturn 用于复返 int 类型,return 用于 void 步骤;还有其他的一些,lreturn 用于 long 型,freturn 用于 float,dreturn 用于 double,areturn 用于援用类型。

    突出退出,步骤在实行的经由中际遇了突出,况兼莫得获取妥善的措置,这种情况下,是不会给它的表层调用者复返任何值的。

    不管是哪种花样退出,在步骤退出后,都必须复返到步骤伊始被调用时的位置,要领工夫不绝实行。一般来说,步骤浅薄退出的时间,PC 计数器的值会四肢复返地址,栈帧中很可能会保存这个计数器的值,突出退出时则不会。

    步骤退出的经由试验上等同于把面前栈帧出栈,因此接下来可能实行的操作有:复原表层步骤的局部变量表和操作数栈,把复返值(要是有的话)压入调用者栈帧的操作数栈中,调换 PC 计数器的值,找到下一条要实行的指示等。

    本文转载自微信公众号「默然王二」,不错通过以下二维码存眷。转载本文请料到默然王二公众号。