2017-01-23 5 views
2
// the java source code 
public class Demo { 
    private final Object lock = new Object(); 

    public void read() { 
     synchronized (lock) { 
      // more code here ... 
     } 
    } 
} 

// the decompiled .class file 
public class Demo { 
    private final Object lock = new Object(); 

    public void read() { 
    // Why Java compiler add this line? Is the 'read this.lock' redundant? 
    Object var1 = this.lock; 
    synchronized(this.lock) { 
     // more code here ... 
    } 
    } 
} 

Байткод здесь: javap -l -p -s demo.classПочему компилятор Java добавляет «избыточное чтение» перед «синхронизированным блоком»?

public void read(); 
descriptor:()V 
flags: ACC_PUBLIC 
Code: 
    stack=2, locals=3, args_size=1 
    0: aload_0 
    1: getfield  #3     // Field lock:Ljava/lang/Object; 
    4: dup 
    5: astore_1 
    6: monitorenter 
    7: aload_1 
    8: monitorexit 
    9: goto   17 
    12: astore_2 
    13: aload_1 
    14: monitorexit 
    15: aload_2 
    16: athrow 
    17: return 
    Exception table: 
    from to target type 
     7  9 12 any 
     12 15 12 any 
    LineNumberTable: 
    line 15: 0 
    line 16: 7 
    line 17: 17 
    LocalVariableTable: 
    Start Length Slot Name Signature 
     0  18  0 this Lxechoz/vipshop/com/demo/thread/Demo; 
    StackMapTable: number_of_entries = 2 
    frame_type = 255 /* full_frame */ 
     offset_delta = 12 
     locals = [ class xechoz/vipshop/com/demo/thread/Demo, class java/lang/Object ] 
     stack = [ class java/lang/Throwable ] 
    frame_type = 250 /* chop */ 
     offset_delta = 4 

Я думаю, что линия 1: getfield #3 // Field lock:Ljava/lang/Object;

соответствует Object var1 = this.lock;.

Я знаю, что компилятор оптимизирует код, добавив или удалив некоторый код.

Но, почему компилятор добавить прочитать заявление перед синхронизируются блок.

Зачем это необходимо? Или почему это оптимизация ?

+1

Возможно, стоит проверить код байта напрямую. Декомпилятор может иметь некоторую роль в этом. – Henry

ответ

3

Вот фактические байткоды.

public void read(); 
    Code: 
     0: aload_0 
     1: getfield  #3     // Field lock:Ljava/lang/Object; 
     4: dup 
     5: astore_1 
     6: monitorenter 
     7: aload_1 
     8: monitorexit 
     9: goto   17 
     12: astore_2 
     13: aload_1 
     14: monitorexit 
     15: aload_2 
     16: athrow 
     17: return 
    Exception table: 
     from to target type 
      7  9 12 any 
      12 15 12 any 

Вы увидите, что есть два места, где aload_1 используется для загрузки замок из кадра стека.

  • Первый раз, когда он должен использоваться как операнд для monitorexit со смещением 8. Это тот случай, когда код обычно выходит из синхронизированного блока.
  • Второй раз, когда он должен использоваться как операнд для monitorexit при смещении 14. Это тот случай, когда код разматывает гипотетическое исключение , в котором блокировка монитора должна быть освобождена до повторного изменения текущего исключение.

(Также см. Псевдокод @ SubOptimal.)

Байт-коды могут быть затянуты. (К примеру, оптимизации байткод компилятор может понять, что это может перезагрузить замок с lock поля, а не временная переменной. Тем не менее, это только потому, что законно lock является final!)

Однако ... стратегия компилятора Java НЕ оптимизировать байт-коды, произведенные javac. Вместо этого оптимизация с большой нагрузкой выполняется во время компиляции JIT. В этот момент можно было бы ожидать, что собственный код сохранит блокировку в регистре ... если бы это было оптимально.

«Дополнительная переменная», вероятно, является артефактом декомпилятора, который вы используете. Он не понимает идиомы, которые использует компилятор. Он добавляет локальную переменную, не понимая, что переменная используется в синтетическом блоке обработчика, который он скрывает от вас.

Никогда неразумно относиться к тому, что декомпилятор говорит как «истина». Хорошо известно, что декомпилированный код может вводить в заблуждение ... или даже не действительный код Java.

Конечно, любые наблюдения за оптимизацией кода, основанные исключительно на выходе декомпилятора, не имеют никакого значения.


1 - На самом деле, если рассматривать Thread.kill() и ThreadDeath исключение, которое используется для его осуществления, это не гипотетический. Даже для пустого блока.

+1

На самом деле для повторного чтения поля потребуется две инструкции: aload_0; getfield', второй - три байта, имеющий в общей сложности четыре байта, по сравнению с одним байтом единственной инструкции «aload_1». Другими словами, даже при подсчете 'dup; astore_1', оба имеют размер одного байта, используя локальную переменную, выигрывает перечитывание поля, как только вам нужно получить доступ к значению два раза. И здесь, к нему обращаются в трех местах ... – Holger

+0

Моя реальная точка: 1) декомпилированная Java вводит в заблуждение, 2) байткоды объяснимы и 3) ** ни один из них ** не может многое сделать, как быстро * оптимизированный родной код * будет выполнять. Ваше наблюдение интересно, но не противоречит моему «сообщению». –

+0

Это не должно было противоречить. Вы правильно сказали: «Стратегия компилятора Java НЕ предназначена для оптимизации байткодов», но предыдущий оператор «байт-коды могут быть затянуты» неверен в этом конкретном случае, так как это уже самый компактный способ выражения этого намерения. Мой комментарий не был предназначен для создания впечатления от фундаментальной ошибки в вашем ответе, поскольку, помимо того небольшого вопроса, который был рассмотрен в моем комментарии, все правильно. – Holger

4

От Синхронизация JLS 3.14

в виртуальной машине Java осуществляется путем ввода монитора и выхода, либо в явном виде (путем использования инструкции monitorenter и monitorexit) или неявно (с помощью вызова метода и возврата инструкции) ,

Для того, чтобы monitorexit всегда выполняется компилятор добавляет неявный catch условие для Throwable.

Для применения правильного спаривания monitorenter и monitorexit инструкции по завершению резкого вызова метода, то компилятор генерирует обработчик исключений (§2.10), который будет соответствовать любому исключению и чьей ассоциированному код выполняет необходимые инструкции monitorexit.

При использовании javap -c Demo вы можете увидеть это Addtional байткод по смещению 12-16

0: aload_0 
1: getfield  #3     // Field lock:Ljava/lang/Object; 
4: dup 
5: astore_1 
6: monitorenter 
7: aload_1 
8: monitorexit 
9: goto   17 
12: astore_2 
13: aload_1 
14: monitorexit 
15: aload_2 
16: athrow 
17: return 
Exception table: 
    from to target type 
     7  9 12 any 
     12 15 12 any 

сгенерированный код, как псевдо-код

Object var1 = this.lock; 
try { 
    monitorenter(var1); 
    // more code here ... 
    monitorexit(var1); 
} catch (Throwable t) { 
    monitorexit(var1); 
    throw t; 
} 
+0

@StephenC Вы правы. Это не должно было быть исключено в псевдокоде. – SubOptimal

Смежные вопросы