JVM学习笔记之方法调用

    最近有空,继续写写jvm的学习笔记。这次写写java中的方法调用过程。 
  
    程序在有限的资源下运行当然是越快越好,这就离不开优化。一般来说都是业务逻辑优化(这也是最有效的),说到程序的运行的优化就不得不牵扯到JVM底层的字节码了。查看字节码的方法是javap -c **.class,这里建议  javap -c **.class > **.txt  来保存成文本文件方便用工具查看。 
    从class生成的字节码来看,JAVA的方法调用分为4种: invokestatic、invokevirual、invokespecial、invokeinterface 。 
  
    为了说明他们的区别,还得说下JVM中类的存贮和方法的早/迟绑定。 
    1. Class的类方法的存放地方和属性: 
    JVM有一个所有线程间共享的“方法区”,用来存贮每个类结构的常数池、域、方法数据、方法和构造函数。包括类和实例初始化与接口类型初始化中用到的特殊方法。所以每当有线程调用某个类的方法时,都要从方法区调用。java类的方法有很多修饰,比如static、final、private等,这个决定了jvm在底层调用方法上的不同。 

    2.方法的早/迟绑定 
    简单来说,分辨一个方法是早绑定还是迟绑定可以通过是根据引用调用方法还是通过对象来调用方法。 
    当一个方法是static时,在任何地方都可以直接调用,比如Math.abs(4),这个时候就是早绑定,因为这里不需要new,当然没对象了。早绑定不仅仅限于static,当调用private方法时,也是早绑定,因为private只能被自身类方法调用。比如: 
Java代码 
Java代码 
  1. class A{    
  2.   private void methodA(){}    
  3. }    
  4.           
  5. class B extends A{    
  6.   private void methodB(){}    
  7. }    
  8.     
  9. classA a = new classB();    

    这里是无法这么用a.methodA()和a.methodB(); (原因显而易见,这里就不说了) 
    我觉得这样理解private是静态绑定应该好点。 
  
    有了以上的说明,可以说清楚方法的具体区别了。 
    invokestatic:用于static修饰的方法。任何时候调用只需要所属的CLASS名,无需new,JVM可以直接映射到方法区,是执行速度最快的。如果static方法有参数,则invokestatic指令前还会有个指令,作用是把参数从栈弹出给invokestatic指令。(详细说明这里需要再详细解释下方法运行机制,这里以后再说,这里特别声明是为了和invokevirtual等方法做个区别) 
    invokevirtual:用于public和protected修饰的且没有static修饰的方法,在invokevirtual之前,总可以见到astore和aload的指令, 是因为在调用invokevirtual时,会从栈里弹出两个参数,是objectref和我们自定义的参数列表。objectref就是this,因为非static方法不是直接从方法区用的,所以得匹配所属类,是默认的隐式参数,无法从代码层指定objectref。参数列表就是我们自定义的传入参数了。invokevirtual是类的方法调用最慢的指令(因为迟绑定需要多重校验),但是却是运用最多的,java面向对象的多态性离不开它。 
    invokespecial:用于3种情况,前2种类默认的方法<init>()和super修饰的方法,它们可以为隐式,<init>()是默认的无参构造函数,super()是默认的调用父类构造函数的函数。当然我们也可以在代码层自定义些参数。invokespecial可以默认从构造函数里递归调用super(),而invokevirtual不行(动态绑定是只运行当前类中的方法)之前说过invokespecial是静态绑定的,如果换用动态绑定是会出错的(例如构造函数的例子),第三种是非static修饰的private方法,原因之前也说了。invokespecial的运行速度比较特殊,在super和init()时,我们可以不用关心(关心也无用,改不了),对于非static的private方法,速度是快于invokevirtual的。 
    invokeinterface:用于接口调用的情况,速度是最慢的,因为接口不知道类的具体信息,所以每次运行前得遍历整个类(校验+匹配),而invokevirtual是直接关联类的,方法偏移量是固定的。 
  
    这里再补充说明个问题,我看别的帖子说,有final声明的方法也是先绑定的,这里我不清楚,这里只说说我的想法 
    首先final是用于不可修改,不可继承的用途,而不是改变使用或者说调用的方法。 
    其次,《深入java虚拟机》中也只说了invokestatic和invokespecial是先绑定、invokeinterface和invokevirual没有说明,而我实践javap的时候,所有方法没有因为加了final而改变字节码的方法(不像static),有兴趣可以试试。 
  
    最后总结说下大致的代码优化规则。 
    首先,4种运行速度为:invokestatic > invokespecial > invokevirual > invokeinterface。 
    所以常用的方法是, 
    1. 根据具体的业务要求,分离出常用的任务写成static方法,加快速度。有人也怀疑static方法会不会占用更多的内存,我认为不会,因为无论是什么样的方法都得占用方法区的空间,调用也是引用调用。再说对于现在上G的内存,我们写的几K的东西也算不上多大开销。 
    2. 遵循高内聚,低耦合的模式,一个类只对外提供必要的public个protected方法,大部分的内部逻辑就用private修饰,一来速度快,二来也免得别人调用起来方法太多看的麻烦。 
    3. 对于接口来说,不是接口用的越多越好,抽象出来的接口应该越精简越好,我的亲身体会是接口多了很麻烦,毕竟越灵活的东西越难理解。 
  
    我对于方法调用的理解就到这么多,欢迎拍砖。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值