从编译角度看java域和方法

这里写图片描述

上图展示了,我们java类是如何从java字节码文件中加载到内存中,再从内存中解析到方法区中的,然后被使用的过程。

java域的编译

而我们的java域初始化是在解析和初始化这两个阶段完成的,解析先把域中基本类型和String类型初始化为0和“”,域中引用类型初始化为null(系统默认设置),然后在初始化阶段进行clinit()和init()的初始化(用户代码设置)。
而域又分为静态域和非静态域。
静态域:在clinit()中初始化,属于类变量被存储在方法区中。
非静态域:在init()中初始化,属于实例变量存储在堆中对象中。

代码示例:

这里写图片描述
这里写图片描述

编译后的代码,可以看到初始化的顺序:
这里写图片描述

代码块执行的时机:静态代码块在编译期间被添加到clinit()中,非静态代码块编译期间被添加到init()中,添加的顺序和代码块在代码中的顺序是一致的。

得出的结论
只有一个类被加载之后才会按图示进行接下来的操作,而我们的静态域是属于方法区的,每个类对应这一个class实例,而我们的实例对象就是根据这个class实例来生成的,因此静态域的初始化只有一次,而我们的非静态域是和实例对应的,处于对象中,也就是新建一个对象就初始化一次。

final static域的编译

final static静态常量域,分为两个情况,第一就是final static修饰的变量类型且初始化的数据是常量类型和基本类型,那么这个变量就是在编译期间就被初始化的,因为编译过程中常量是存在常量池中可以确定的,虚拟机会把这些变量当做常量,如果是引用类型的,那么就需要在clinit()初始化,它的初始化和static的初始化是一样道理。

如下代码所示:
这里写图片描述

编译后的代码:
这里写图片描述
这里写图片描述

我们可以看 i2和s2都没有在clinit()中,其他的非final 的静态域都在clinit()中初始化了。这就要分辨“haha”和 new String(“heihei”);的区别了,前者是常量字符串,后者是引用类型的对象。实际上,类在初始化步骤之前会在解析阶段初始化系统默认值,而我们的final static修饰的变量系统默认值是我们看到的“haha”,2;而其他的默认值就是null或者0或者“”;接下来就会在初始化阶段执行clinit()和init()初始化函数。

结论
- final static 修饰的原始类型和Sring类型(非引用类型),并不会被翻译在clinit方法中,而是在类初始化步骤之前执行initSFields方法时候得到初始化赋值。
- final static 修饰的引用类型,初始化任然在clinit方法中。

误区:常常看到如果一个field是常量,那么推荐使用final static修饰。很明显这句话有问题,得到优化的仅仅是final static的原始类型和String类型域(非引用类型),如果是引用类型那么还是不会有什么优化的。

java中解析一个域的话,必须确认该域所在的类被解析了,而解析一个类还要保证自己的父类被加载并且被解析,该类的被加载并且解析后,就可以拿到域的索引,然后就可以得到域了。

  • 在热部署中,我们的final static域如果是基本类型或者是String(非引用 类型)是可以被替换的,因为可以找到方法区中的常量进行替换。
  • 我们的final static域是个引用类型的话,是不允许的,因为这个fielld的初始化是在clinit()中进行的,我们知道类从 加载到使用只会调用一次clinit()函数,这样就没办法修改了。

java方法的编译

在类被解析之后,会为该类在方法区中生成一个对应的虚方法表。除了内部类和匿名内部类可能会造成method的新增,我们发现项目中如果应用了混淆,也可能导致方法的内联和裁剪,那么最后也会导致method的新增/减少,一下介绍哪些场景会造成方法的内联和裁剪。

方法内联

  1. 方法没有被其他任何地方应用到,毫无疑问,方法会被内联掉
  2. 方法足够简单,比如一个方法的实现就只要一行,该方法会被内联掉,那么任何调用该方法的地方都会被该方法的实现替换掉。
  3. 方法只被一个地方引用到,这个地方会被方法的实现替换掉。

    代码示例 如下:
    这里写图片描述

这里写图片描述

那么编译后会发现不会出现print方法,因为满足只被一个地方引用的条件。

方法裁剪

当方法的参数没有被使用过,那么该参数就会被裁剪掉。
如下代码所示:
这里写图片描述

编译后的代码:
这里写图片描述

会发现编译后的代码中方法中不存在context参数,该参数被裁剪掉了,有什么办法可以保证方法不被裁剪掉吗?当然有,如下代码:
这里写图片描述

注意这里不能用基本类型false,必须用包装类Boolean,因为如果写基本类型,if语句可能会被优化掉。

在热部署中,最好不要裁剪和内联,因为会导致方法的减少或者增加,裁剪导致method增加,因为新补丁内是有参数的会被认为是新方法,故统一各类增加了新方法;内联导致method减少,因为满足条件的方法会被合成到被调用的方法中,这样方法数就减少了一个。在热部署中是不允许域/方法的新增和减少的。

猜你喜欢

转载自blog.csdn.net/u012345683/article/details/74885979