Escape analysis - Анализ локальности
-XX:+DoEscapeAnalysis
Цель escape-анализа - определить локализован ли указатель создаваемого объекта внутри блока объекта или он выходит (escape) за рамки данного блока (в другую нить (thread), может быть возвращен из блока, а также, когда указатель помещён в глобальную переменную).
Исследование данной проблемы длится не один год, одна из подробных статей Escape analysis for Java была представлена в 1999 году.
В java каждая нить (thread) имеет свой собственный кусок памяти - стек (stack), который хранит фреймы (историю вызовов методов), частичные результаты операций, локальные переменные (примитивы, в т.ч. и указатели/ссылки на объекты). В этом смысле java стек очень похож на стек C++, за исключением того, что в java стеком нельзя напрямую оперировать и создавать на нём объекты, как в C++.
Куча (heap), общая для всех нитей область памяти, используется для хранения всех создаваемых объектов.
-XX:-DoEscapeAnalysis
Объекты создаются на общей куче
Благодаря escape-анализу объекты могут быть созданы прямо на стеке или размещены в регистрах (используя register allocator). Не смотря на то , что создание объекта на кучи всё достаточно дешёвая операция - есть обратная сторона - Garbage Collector. Для высоконагруженных приложений даже вызов minor gc это существенные затраты. Объект, созданный на стеке, будет уничтожен, когда он покинет stack frame, в котором он был создан, как следствие - отсутствие нагрузки на (minor) GC.
-XX:+DoEscapeAnalysis
Более того - нет необходимости использовать блокировку для локализованных объектов - т.к. жизненный цикл объекта ограничен одним блоком и никакая другая нить не может его «увидеть» или использовать (прямое следствие из определения pointer escape) .
В качестве примера используем следующий microbenchmark:import java.util.*;
public class Foo {
public static void main(String[] args){
final int warmup = 100 * 1000;
final int run = 1000 * 1000;
foo(warmup);
final long start = System.nanoTime();
foo(run);
final long end = System.nanoTime();
System.out.println((end - start) / 1000 / 1e3);
}
private static int foo(int count){
int s = 0;
for(int i = 0; i < count; i++){
Collection c = new ArrayList();
s += c.size();
}
return s;
}
}
$ java -XX:-DoEscapeAnalysis -verbose:gc -XX:+PrintGCDetails Foo
[GC [PSYoungGen: 12160K->152K(14144K)] 12160K->152K(46528K), 0.0019620 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
[GC [PSYoungGen: 12312K->136K(26304K)] 12312K->136K(58688K), 0.0008050 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC [PSYoungGen: 24456K->136K(26304K)] 24456K->136K(58688K), 0.0006690 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC [PSYoungGen: 24456K->136K(50624K)] 24456K->136K(83008K), 0.0005730 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
65.78
Heap
PSYoungGen total 50624K, used 14728K [0xa3e20000, 0xa7180000, 0xb3ac0000)
eden space 48640K, 30% used [0xa3e20000,0xa4c602a8,0xa6da0000)
from space 1984K, 6% used [0xa6f90000,0xa6fb2050,0xa7180000)
to space 1984K, 0% used [0xa6da0000,0xa6da0000,0xa6f90000)
PSOldGen total 32384K, used 0K [0x844c0000, 0x86460000, 0xa3e20000)
object space 32384K, 0% used [0x844c0000,0x844c0000,0x86460000)
PSPermGen total 16384K, used 1900K [0x804c0000, 0x814c0000, 0x844c0000)
object space 16384K, 11% used [0x804c0000,0x8069b048,0x814c0000)
$ java -XX:+DoEscapeAnalysis -verbose:gc -XX:+PrintGCDetails Foo
0.747
Heap
PSYoungGen total 14144K, used 4135K [0xa3ed0000, 0xa4e90000, 0xb3b70000)
eden space 12160K, 34% used [0xa3ed0000,0xa42d9d90,0xa4ab0000)
from space 1984K, 0% used [0xa4ca0000,0xa4ca0000,0xa4e90000)
to space 1984K, 0% used [0xa4ab0000,0xa4ab0000,0xa4ca0000)
PSOldGen total 32384K, used 0K [0x84570000, 0x86510000, 0xa3ed0000)
object space 32384K, 0% used [0x84570000,0x84570000,0x86510000)
PSPermGen total 16384K, used 1893K [0x80570000, 0x81570000, 0x84570000)
object space 16384K, 11% used [0x80570000,0x807496f0,0x81570000)
и такой же тест, для java.util.Vector
| ArrayList | Vector |
| -EA | +EA | -EA | +EA |
время выполнения, ms: | 65.78 | 0.747 | 115.805 | 0.83 |
срабатываний GC: | 4 | 0 | 5 | 0 |
время работы GC, ms: | 4 | - | 4.5 | - |
Как и ожидалось время выполнения значительно меньше, меньше вызовов GC, который удаляет объекты «как только так сразу» из young geneneration. Так же заметно ускорение работы thread-safe «версии» ArrayList'а - java.util.Vector.
Однако, не стоит надеяться, что благодаря escape-анализу производительность увеличится столь же стремительно как в данном синтетическом тесте. Определённо можно сказать, что нагрузка на GC будет значительно меньше для большинства типов приложений.
график нагрузки gc реального приложения
до использования java6u14 и после,
по вертикали - суммарное время проведённое в gc
Комментариев нет:
Отправить комментарий