Java性能优化之本地变量和实例变量

原创 2018年04月16日 17:43:03

0x01 发现

在JDK源码中可以大量见到将实例变量赋值给本地变量后,再使用的情况,如:LinkedBlockingQueue源码中的片段(删除了注释和一些不必要的代码):

public class LinkedBlockingQueue<E> extends AbstractQueue<E>
        implements BlockingQueue<E>, java.io.Serializable {
    private static final long serialVersionUID = -6903933977591709194L;

    /** Lock held by take, poll, etc */
    private final ReentrantLock takeLock = new ReentrantLock();

    /** Wait queue for waiting takes */
    private final Condition notEmpty = takeLock.newCondition();

    /** Lock held by put, offer, etc */
    private final ReentrantLock putLock = new ReentrantLock();

    /** Wait queue for waiting puts */
    private final Condition notFull = putLock.newCondition();

    public void put(E e) throws InterruptedException {
        if (e == null) throw new NullPointerException();
        // Note: convention in all put/take/etc is to preset local var
        // holding count negative to indicate failure unless set.
        int c = -1;
        Node<E> node = new Node<E>(e);
        final ReentrantLock putLock = this.putLock;
        final AtomicInteger count = this.count;
        putLock.lockInterruptibly();
        try {
            /*
             * Note that count is used in wait guard even though it is
             * not protected by lock. This works because count can
             * only decrease at this point (all other puts are shut
             * out by lock), and we (or some other waiting put) are
             * signalled if it ever changes from capacity. Similarly
             * for all other uses of count in other wait guards.
             */
            while (count.get() == capacity) {
                notFull.await();
            }
            enqueue(node);
            c = count.getAndIncrement();
            if (c + 1 < capacity)
                notFull.signal();
        } finally {
            putLock.unlock();
        }
        if (c == 0)
            signalNotEmpty();
    }
}

从上面的片段中可以看出,putLockcount两个实例变量被赋值给本地的同名变量后才使用,为什么要这么做呢?在深入研究之前,我们不妨来大胆的假设一下这样做的目的,因为本地变量是在栈中保存,在编译器就已经确定好的,而实例变量是在堆中分配的,在程序运行期间才可以确定内存位置,猜测是为了提升性能才使用这种方式。那为什么这样做可以提升性能呢?

0x02 测试

首先我们来做一个测试,看看这样做是否会带来性能的提升,DEMO如下:

public class TestLocalVar {
    private final static int N = 10000000;
    private final static int M = 100;
    private final AtomicLong count = new AtomicLong();

    public static void main(String[] args) {
        TestLocalVar testLocalVar = new TestLocalVar();

        long start = System.currentTimeMillis();

//        for (int i = 0; i < M; i++) {
//            testLocalVar.visitLocalVar();
//        }
//        System.out.println("visit local var cost " + String.valueOf((System.currentTimeMillis() - start) / M) + " ms");

        for (int i = 0; i < M; i++) {
            testLocalVar.visitInstanceVar();
        }
        System.out.println("visit instance var cost " + String.valueOf((System.currentTimeMillis() - start) / M) + " ms");
    }

    private void visitLocalVar() {
        final AtomicLong count = this.count;
        for (int i = 0; i < N; i++) {
            count.getAndIncrement();
        }
    }

    private void visitInstanceVar() {
        for (int i = 0; i < N; i++) {
            count.getAndIncrement();
        }
    }
}

我们来分别跑一下访问本地变量和访问实例变量所消耗的时间,运行结果如下(运行结果只是取平均值,实际运行情况可能与此处结果不符合):

  • visit instance var cost 156 ms
  • visit local var cost 150 ms

从结果可以看出来,访问本地变量确实比访问实例变量要快。

0x03 深入

反编译class文件到字节码可以看到,在访问实例变量引用对象方法的时候,总是需要通过ALOAD获取this引用,再通过GETFIELD指令去获取实例变量的引用,最后通过INVOKEVIRTUAL指令去调用引用对象的方法。

而访问本地变量的话,只需要在将实例变量赋值给本地变量的时候,调用ALOADGETFIELDASTORE指令,在实际调用实例变量引用对象的方法的时候,只需要通过指令ALOAD获取本地变量,再通过INVOKEVIRTUAL指令调用即可。

上述测试代码对应的字节码(只截取两个visit方法):

// access flags 0x2
  private visitLocalVar()V
   L0
    LINENUMBER 27 L0
    ALOAD 0
    GETFIELD cn/zyview/myexp/TestLocalVar.count : Ljava/util/concurrent/atomic/AtomicLong;
    ASTORE 1
   L1
    LINENUMBER 28 L1
    ICONST_0
    ISTORE 2
   L2
   FRAME APPEND [java/util/concurrent/atomic/AtomicLong I]
    ILOAD 2
    LDC 10000000
    IF_ICMPGE L3
   L4
    LINENUMBER 29 L4
    ALOAD 1
    INVOKEVIRTUAL java/util/concurrent/atomic/AtomicLong.getAndIncrement ()J
    POP2
   L5
    LINENUMBER 28 L5
    IINC 2 1
    GOTO L2
   L3
    LINENUMBER 31 L3
   FRAME CHOP 1
    RETURN
   L6
    LOCALVARIABLE i I L2 L3 2
    LOCALVARIABLE this Lcn/zyview/myexp/TestLocalVar; L0 L6 0
    LOCALVARIABLE count Ljava/util/concurrent/atomic/AtomicLong; L1 L6 1
    MAXSTACK = 2
    MAXLOCALS = 3

  // access flags 0x2
  private visitInstanceVar()V
   L0
    LINENUMBER 34 L0
    ICONST_0
    ISTORE 1
   L1
   FRAME APPEND [I]
    ILOAD 1
    LDC 10000000
    IF_ICMPGE L2
   L3
    LINENUMBER 35 L3
    ALOAD 0
    GETFIELD cn/zyview/myexp/TestLocalVar.count : Ljava/util/concurrent/atomic/AtomicLong;
    INVOKEVIRTUAL java/util/concurrent/atomic/AtomicLong.getAndIncrement ()J
    POP2
   L4
    LINENUMBER 34 L4
    IINC 1 1
    GOTO L1
   L2
    LINENUMBER 37 L2
   FRAME CHOP 1
    RETURN
   L5
    LOCALVARIABLE i I L1 L2 1
    LOCALVARIABLE this Lcn/zyview/myexp/TestLocalVar; L0 L5 0
    MAXSTACK = 2
    MAXLOCALS = 2
}

通过JVM规范我们也可以看到,GETFIELD指令有两个步骤,简单来说,找到引用的对象,然后将对象压入到操作栈,ALOAD指令只有一个步骤,就是从栈中直接压入到操作栈,所以这就可以解释为什么访问本地变量比访问实例变量要快。

0x04 总结

当方法中只需要访问一次实例变量,则不需要赋值给本地变量,直接访问更快,当需要访问2次及以上的情况且实例变量是引用类型,赋值给本地变量会优于直接访问实例变量。但是,通过DEMO可以看出来,当访问10亿次以上,才会带来几毫秒的性能提升,所以如果对性能要求不是很高的话,直接访问即可。

征服Node.js 7.x视频课程(3):基础知识

本课程主要介绍了Node.js中console对象方法的使用、常用命令以及如何调试Node.js脚本。
  • 2017年04月18日 15:40

PB变量的作用域

PowerBuilder的变量作用域共有四种:全局变量、实例变量、共享变量和局部变量。不同作用域的变量需要在不同的位置说明,下面分别予以介绍。在编程窗口、窗口画笔、用户对象画笔或菜单画笔中,选择“De...
  • wsich
  • wsich
  • 2011-06-19 16:31:00
  • 3183

python中类变量和实例变量__之实例变量

1.Python中实例变量 class Test: def f(self, name): self.name = name def f1(self): ...
  • kc_1197977022
  • kc_1197977022
  • 2017-04-24 22:37:14
  • 924

从头认识多线程-2.1 局部变量与实例变量的线程安全

这一章节我们来讨论一下局部变量与实例变量的线程安全。1.结论局部变量线程安全的实例变量不是线程安全的2.代码清单package com.ray.deepintothread.ch02.topic_1;...
  • raylee2007
  • raylee2007
  • 2016-04-21 22:22:17
  • 7494

Java实例变量、类变量与局部变量

一、实例变量 也叫对象变量、类成员变量;从属于类由类生成对象时,才分配存储空间,各对象间的实例变量互不干扰,能通过对象的引用来访问实例变量。但在Java多线程中,实例变量是多个线程共享资源,要注...
  • zhangliangzi
  • zhangliangzi
  • 2015-12-21 22:08:50
  • 8833

成员变量、实例变量、属性变量的联系

@interface MyViewController :UIViewControlle { UIButton *yourButton; int count; id data; } @pr...
  • www9500net_
  • www9500net_
  • 2016-03-23 22:43:45
  • 1520

实例变量和类变量的区别

Java类体中的成员变量可以分为实例变量和类变量。其中类变量需用static修饰,否则则为实例变量。类变量又称为static变量或者静态变量。例如: class Book{ string ...
  • jinjidexiaocainiao
  • jinjidexiaocainiao
  • 2016-03-27 15:51:00
  • 1476

Java中字段、属性、成员变量、局部变量、实例变量、静态变量、类变量、常量

首先看个例子:package zm.demo;public class Demo { private int Id;//成员变量(字段)、实例变量(表示该Id变量既属于成员变量又属于实例变量)...
  • zm13007310400
  • zm13007310400
  • 2017-08-23 22:50:16
  • 1101

实例变量和类变量、类方法和实例方法

类体中包括成员变量和局部变量,而成员变量又可以细分为实例变量和类变量,在声明成员变量的时候,用static给予修饰的称作类变量,否则称作实例变量。 那么,类变量和实例变量有什么区别呢? 我们知道,...
  • qq_30070433
  • qq_30070433
  • 2016-08-25 12:01:13
  • 2340

Python类变量和实例变量区别

深入理解python类的实例变量和类变量 Python变量的本质:被赋值 1 普通python变量(非类相关变量)很容易理解,在被赋值后即变量存在,可读可写 2 Python类的变量(类变量和实例...
  • u014036026
  • u014036026
  • 2014-03-11 14:43:13
  • 13332
收藏助手
不良信息举报
您举报文章:Java性能优化之本地变量和实例变量
举报原因:
原因补充:

(最多只允许输入30个字)