При изучении основ Java я частенько сталкивался с различными рекомендациями по использованию типов данных.
В один прекрасный день мне захотелось чуть глубже разобраться и узнать, что же на самом деле происходит под капотом, а не просто запомнить, как правильно. Я не стал разбирать всем приевшийся пример с конкатенацией строк и решил разобрать не менее приевшийся пример с авто-упаковкой/распаковкой типов).
Для начала стоит сказать пару слов о JVM. JVM является стековой машиной. Грубо говоря, каждый раз, когда мы что-то делаем с переменными – они сначала помещаются в стек операндов, который формируется для каждого метода, а затем производятся необходимые вычисления.
Не вдаваясь в детали выглядит это так:
Итак, предположим, что у нас есть задача по вычислению арифметической прогрессии от 0 до 1 000 000 000 с шагом 1.
Случай 1
Предположим, что мы написали такой код:public class Test {
public static void main(String[] args) {
Long result = 0L;
for (int i = 0; i < 1_000_000_000; i++) {
result += i;
}
System.out.println("Result:" + result);
}
}
Будет ли он выполнять поставленную задачу? Да, но будет ли он оптимальным? Разберёмся.
Для начала давайте посмотрим на байт-код, который получился (его можно получить так: javap –c Test.class).
Вот в такую ужасающую конструкцию преобразился наш цикл for:
10: if_icmpge 30
13: aload_1
14: invokevirtual #4 // Method java/lang/Long.longValue:()J
17: iload_2
18: i2l
19: ladd
20: invokestatic #2 // Method java/lang/Long.valueOf:(J)Ljava/lang/Long;
23: astore_1
24: iinc 2, 1
27: goto 7
Давайте по порядку:
- 13 – Помещаем переменную Long result в стек операндов
- 14 – Распаковываем ссылочный Long в примитивный long
- 17 – Помещаем в стек операндов переменную int i
- 18 – преобразуем её к типу long
- 19 – теперь складываем long result и long i
- 20 – запаковываем результат в ссылочный тип Long
- 23 – и сохраняем обратно в переменную.
Случай 2
Попробуем заменить тип переменной result на примитивный long:public class Test {
public static void main(String[] args) {
long result = 0;
for (int i = 0; i < 1_000_000_000; i++) {
result += i;
}
System.out.println("Result:" + result);
}
}
Посмотрим, что получилось. Байт-кода стало меньше.
7: if_icmpge 21
10: lload_1
11: iload_3
12: i2l
13: ladd
14: lstore_1
15: iinc 3, 1
18: goto 4
- 10 – Помещаем в стек операндов нашу переменную result
- 11 – Помещаем в стек операндов переменную int i
- 12 – преобразуем int i в long i
- 13 – складываем long result и long i
- 14 – сохраняем результат обратно в локальную переменную
- Случай 1 – в среднем 5100 мс
- Случай 2 – в среднем 535 мс
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Еще нужно таким образом проверить правда ли что компилятор оптимизирует конкатенацию строк через StringBuilder. Интересно или описанный в статье пример можно принудительно оптимизировать через флаг jvm.
Только пугает, что этих «капотов», одного под другим, сейчас существует десятки и сотни (если еще и энтерпрайз, с его спрингами и хайбернейтами рассматривать) и не под силу человеческому мозгу охватить их все. Эх, где времена бейсика и асма =))))))