Неиспользуемые локальные переменные в методе приобретают память в 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 по компиляции не упоминается удаление неиспользуемых переменных из стека. Таким образом, если не считать дальнейших открытий, я бы ответил, что это зависит от реализации, но это похоже на оптимизацию, которую мог бы выполнять любой уважающий себя компилятор. Заметьте также, что неважно, что это локальная переменная, а не поле. Фактически локальные переменные наиболее вероятны для оптимизации, поскольку они легче всего анализировать и устранять. (именно потому, что они локальны)
Посмотрим, можно ли найти больше доказательств, которые поддерживают это.
Ответы
Ответ 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.
Современные компиляторы устранят большую неэффективность кода, поэтому вы можете сосредоточиться на своих проблемах.