Подтвердить что ты не робот

Неиспользуемые локальные переменные в методе приобретают память в JVM?

Я столкнулся с этим сообщением в SO Используют ли неинициализированные примитивные переменные экземпляра память?

В нем говорится: "В Java стоит ли объявлять переменную экземпляра класса без памяти, не инициализируя ее? Например: имеет ли int i; используйте любую память, если я не инициализирую ее с помощью я = 5;?

Мой вопрос в том, что в случае локальных переменных, например, у меня есть метод foo()

public int foo(){

  int x;

//Write code which does not use/initialize x
}

Будет ли занимать локальную переменную x?

Edit

Джон Ответ

ОБНОВЛЕНИЕ: сделав еще немного исследований по этому вопросу, я нахожу эту страницу, которая подсказывает мне, что, хотя скомпилированный байт-код подразумевает, что пространство выделено для x, оно действительно может быть оптимизировано jvm. К сожалению, я не нашел полного описания выполненных оптимизаций. В частности, в главе документации JVM по компиляции не упоминается удаление неиспользуемых переменных из стека. Таким образом, если не считать дальнейших открытий, я бы ответил, что это зависит от реализации, но это похоже на оптимизацию, которую мог бы выполнять любой уважающий себя компилятор. Заметьте также, что неважно, что это локальная переменная, а не поле. Фактически локальные переменные наиболее вероятны для оптимизации, поскольку они легче всего анализировать и устранять. (именно потому, что они локальны)

Посмотрим, можно ли найти больше доказательств, которые поддерживают это.

4b9b3361

Ответ 1

Это вопрос, который стоит изучить с помощью javap.

public class Foo
{
public int bar(){

  System.out.println("foo");
    return 8;
  }
public int foo(){

  int x;
  System.out.println("foo");
    return 8;
  }
}

Обратите внимание, что разница между foo() и bar() заключается в том, что объявляется локальная переменная x, а другая - нет.

Теперь посмотрите на код jvm (используйте javap -v Foo, чтобы увидеть это на своем компьютере)

  public int bar();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String foo
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: bipush        8
        10: ireturn
      LineNumberTable:
        line 6: 0
        line 7: 8

  public int foo();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String foo
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: bipush        8
        10: ireturn
      LineNumberTable:
        line 12: 0
        line 13: 8
}

Интересно, что линейный вывод идентичен, но locals для bar - 1, а для foo - 2. Таким образом, похоже, что пространство действительно выделено для x, хотя выход компилятора не работает, никогда не использовать его.

ОБНОВЛЕНИЕ: сделав еще немного исследований по этому вопросу, я нахожу эту страницу, которая предлагает мне, что, хотя скомпилированный байт-код подразумевает, что пространство выделяется для x, он действительно может быть оптимизирован jvm. К сожалению, я не нашел полного описания выполненных оптимизаций. В частности, в главе документации JVM compiling не упоминается удаление неиспользуемых переменных из стека. Таким образом, если не считать дальнейших открытий, я бы ответил, что это зависит от реализации, но это похоже на оптимизацию, которую мог бы выполнять любой уважающий себя компилятор. Заметьте также, что неважно, что это локальная переменная, а не поле. Фактически локальные переменные наиболее вероятны для оптимизации, поскольку они легче всего анализировать и устранять. (именно потому, что они локальны)

Ответ 2

Переменные уровня класса/уровня экземпляра будут автоматически инициализированы значениями по умолчанию. Таким образом, да, они будут занимать некоторое пространство, когда класс инициализируется/создается экземпляр соответственно.

Что касается локальных переменных метода, то нет, если они только объявлены, но не инициализированы, то они не будут использовать какое-либо пространство, они так же хороши, как игнорируются компилятором..

Если ваш код был таким:

public static void main(String[] args) {
    int i;  // ignored
    int j = 5;
    String s = "abc";
    String sNull; // ignored
}

Байт-код:

  LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       6     0  args   [Ljava/lang/String;
            2       4     2     j   I
            5       1     3     s   Ljava/lang/String;
   }

Ответ 3

Немного растягивается на тестовом поле от @JonKiparsky:

public class StackSlotTest {
    public int bar(){
        int a;
        a = 5;
        System.out.println("foo");
        return a;
    }

    public int foo(){
        int x;
        int a;
        a = 5;
        System.out.println("foo");
        return a;
      }
}

Я добавил переменную a к обоим методам и добавил набор и использовал его в обоих.

  public int bar();
    Code:
       0: iconst_5
       1: istore_1
       2: getstatic     #2                  // Field java/lang/System.out:Ljava/
io/PrintStream;
       5: ldc           #3                  // String foo
       7: invokevirtual #4                  // Method java/io/PrintStream.printl
n:(Ljava/lang/String;)V
      10: iload_1
      11: ireturn

Вы видите, что байт-код iload_1 загружает значение a для возврата. На второй слот стека. (Первый - указатель this.)

  public int foo();
    Code:
       0: iconst_5
       1: istore_2
       2: getstatic     #2                  // Field java/lang/System.out:Ljava/
io/PrintStream;
       5: ldc           #3                  // String foo
       7: invokevirtual #4                  // Method java/io/PrintStream.printl
n:(Ljava/lang/String;)V
      10: iload_2
      11: ireturn

В этом случае значение a загружается с помощью iload_2 для доступа к третьему слоту, потому что второй слот занят (сорт) переменной (полностью неиспользуемой) x.

Ответ 4

Способ, которым примитивная переменная хранится в локальной переменной (в байт-коде), имеет:

istore (для значений int), dstore (для значений double), fstore (для значений float) и т.д.

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

Итак, если вы делаете что-то вроде:

int i;
//...
i = 12;
//...

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

//...
bipush 12
istore_1 1

Если вы никогда не инициализируете свою переменную, компилятор не может нажать значение в верхней части стека оператора, поэтому операция хранилища невозможна. это не оптимизация компилятора, это просто способ работы байт-кода. Итак, ваш компилятор просто удаляет вашу строку.

Возьмем этот простой пример:

public static void main(String[] args) {
    int i;
    int j = 10;
    System.out.println(j);
    i = 12;
    System.out.println(i);
}

И посмотрите, когда инициализируется локальная переменная i:

public static void main(String[] p0) {
    bipush 10
    istore_2 2
    getstatic PrintStream System.out
    iload_2 2
    invokevirtual void PrintStream.println(int)
    bipush 12
    istore_1 1
    getstatic PrintStream System.out
    iload_1 1
    invokevirtual void PrintStream.println(int)
    return
}

Вы можете заметить, что локальная переменная # 2 используется перед локальной переменной # 1.

Взять этот последний пример:

public static void main(String[] args) {
    int i;
    int j = 10;
    System.out.println(j);
}

Байт-код будет выглядеть следующим образом:

public static void main(String[] p0) {
    bipush 10
    istore_2 2
    getstatic PrintStream System.out
    iload_2 2
    invokevirtual void PrintStream.println(int)
    return
}

Локальная переменная 1 никогда не используется и что-то хранится в локальной переменной 2. Из-за этого здесь будет выделено 2 x 32 бита, даже если локальная переменная 1 не используется.

edit: (из комментария Hot Licks)

Интерпретатор JVM будет выделять столько локальных слотов, сколько указано в заголовке метода. Они выделяются независимо от того, используются они или нет. Длинные и двойные значения получают два слота (хотя слоты 64 bits и только один используется в большинстве современных реализаций).

Неинициализированная локальная переменная int, например, потребовала бы 32 бита.

Ответ 5

Нет. Причина: оптимизация времени компиляции.

Как вы код:

public int foo(){
  int x;

  //Write code which does not use/initialize x
}

Большинство компиляторов Java смогут понять, что x не играет никакой роли в выполнении кода, поэтому они полностью удаляют его благодаря оптимизации компиляции.

Даже если вы используете:

  int x = 999;

x не следует включать - некоторые немые компиляторы могут включать его, но это может измениться в будущем.

Однако

  int x = methodA();

Будет определенно включен x.

Современные компиляторы устранят большую неэффективность кода, поэтому вы можете сосредоточиться на своих проблемах.