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

Почему эта программа много раз повторяется, когда у вас есть "println" после циклов?

Вот небольшой код, который я пытаюсь сделать. Эта программа занимает много времени для выполнения. Во время работы, если я пытаюсь убить его с помощью кнопки завершения в eclipse, он возвращает Terminate Failed. Я могу убить его с терминала с помощью kill -9 <PID>.

Но, когда я не печатаю результат переменной в последней строке программы (пожалуйста, проверьте прокомментированную часть кода), программа немедленно выйдет из программы.

Мне интересно:

  • Почему требуется время для выполнения, когда печатается значение результата?
    Обратите внимание: если я не печатаю value, тот же цикл сразу заканчивается.

  • Почему eclipse не может убить программу?

Обновление 1: Кажется, JVM оптимизирует код во время выполнения (не во время компиляции). Этот поток полезен.

Обновление 2: Когда я печатаю значение value, jstack <PID> не работает. Работает только jstack -F <PID>. Любая возможная причина?

    public class TestClient {

        private static void loop() {
            long value =0;

            for (int j = 0; j < 50000; j++) {
                for (int i = 0; i < 100000000; i++) {
                    value += 1;
                }
            }
            //When the value is being printed, the program 
            //is taking time to complete
            System.out.println("Done "+ value);

            //When the value is NOT being printed, the program 
            //completes immediately
            //System.out.println("Done ");
        }

        public static void main(String[] args) {
            loop();
        }
    }
4b9b3361

Ответ 1

Это оптимизация компилятора JIT (не оптимизация java-компилятора).

Если вы сравниваете байт-код, сгенерированный java-компилятором для двух версий, вы увидите, что цикл присутствует в обоих из них.

Вот как выглядит декомпилированный метод с println:

private static void loop() {
    long value = 0L;

    for(int j = 0; j < '썐'; ++j) {
        for(int i = 0; i < 100000000; ++i) {
            ++value;
        }
    }

    System.out.println("Done " + value);
}

Вот как выглядит декомпилированный метод при удалении println:

private static void loop() {
    long value = 0L;

    for(int j = 0; j < '썐'; ++j) {
        for(int i = 0; i < 100000000; ++i) {
            ++value;
        }
    }
}

Как видите, петли все еще существуют.

Однако вы можете включить ведение журнала компилятора JIT и сборку со следующими параметрами JVM:

-XX:+UnlockDiagnosticVMOptions -XX:+LogCompilation -XX:+PrintAssembly

Вам также может понадобиться загрузить hsdis-amd64.dylib и поместить в ваш рабочий каталог (MacOS, HotSpot Java 8)

После запуска TestClient вы должны увидеть код, созданный компилятором JIT в консоли. Здесь я выведу только выдержку из вывода.

Версия без println:

# {method} 'loop' '()V' in 'test/TestClient'
0x000000010e3c2500: callq  0x000000010dc1c202  ;   {runtime_call}
0x000000010e3c2505: data32 data32 nopw 0x0(%rax,%rax,1)
0x000000010e3c2510: sub    $0x18,%rsp
0x000000010e3c2517: mov    %rbp,0x10(%rsp)
0x000000010e3c251c: mov    %rsi,%rdi
0x000000010e3c251f: movabs $0x10dc760ec,%r10
0x000000010e3c2529: callq  *%r10              ;*iload_3
                                              ; - test.TestClient::[email protected] (line 9)
0x000000010e3c252c: add    $0x10,%rsp
0x000000010e3c2530: pop    %rbp
0x000000010e3c2531: test   %eax,-0x1c18537(%rip)        # 0x000000010c7aa000
                                              ;   {poll_return}
0x000000010e3c2537: retq

Версия с println:

# {method} 'loop' '()V' in 'test/TestClient'
0x00000001092c36c0: callq  0x0000000108c1c202  ;   {runtime_call}
0x00000001092c36c5: data32 data32 nopw 0x0(%rax,%rax,1)
0x00000001092c36d0: mov    %eax,-0x14000(%rsp)
0x00000001092c36d7: push   %rbp
0x00000001092c36d8: sub    $0x10,%rsp
0x00000001092c36dc: mov    0x10(%rsi),%r13
0x00000001092c36e0: mov    0x8(%rsi),%ebp
0x00000001092c36e3: mov    (%rsi),%ebx
0x00000001092c36e5: mov    %rsi,%rdi
0x00000001092c36e8: movabs $0x108c760ec,%r10
0x00000001092c36f2: callq  *%r10
0x00000001092c36f5: jmp    0x00000001092c3740
0x00000001092c36f7: add    $0x1,%r13          ;*iload_3
                                              ; - test.TestClient::[email protected] (line 9)
0x00000001092c36fb: inc    %ebx               ;*iinc
                                              ; - test.TestClient::[email protected] (line 9)
0x00000001092c36fd: cmp    $0x5f5e101,%ebx
0x00000001092c3703: jl     0x00000001092c36f7  ;*if_icmpge
                                              ; - test.TestClient::[email protected] (line 9)
0x00000001092c3705: jmp    0x00000001092c3734
0x00000001092c3707: nopw   0x0(%rax,%rax,1)
0x00000001092c3710: mov    %r13,%r8           ;*iload_3
                                              ; - test.TestClient::[email protected] (line 9)
0x00000001092c3713: mov    %r8,%r13
0x00000001092c3716: add    $0x10,%r13         ;*ladd
                                              ; - test.TestClient::[email protected] (line 10)
0x00000001092c371a: add    $0x10,%ebx         ;*iinc
                                              ; - test.TestClient::[email protected] (line 9)
0x00000001092c371d: cmp    $0x5f5e0f2,%ebx
0x00000001092c3723: jl     0x00000001092c3710  ;*if_icmpge
                                              ; - test.TestClient::[email protected] (line 9)
0x00000001092c3725: add    $0xf,%r8           ;*ladd
                                              ; - test.TestClient::[email protected] (line 10)
0x00000001092c3729: cmp    $0x5f5e101,%ebx
0x00000001092c372f: jl     0x00000001092c36fb
0x00000001092c3731: mov    %r8,%r13           ;*iload_3
                                              ; - test.TestClient::[email protected] (line 9)
0x00000001092c3734: inc    %ebp               ;*iinc
                                              ; - test.TestClient::[email protected] (line 8)
0x00000001092c3736: cmp    $0xc350,%ebp
0x00000001092c373c: jge    0x00000001092c376c  ;*if_icmpge
                                              ; - test.TestClient::[email protected] (line 8)
0x00000001092c373e: xor    %ebx,%ebx
0x00000001092c3740: mov    %ebx,%r11d
0x00000001092c3743: inc    %r11d              ;*iload_3
                                              ; - test.TestClient::[email protected] (line 9)
0x00000001092c3746: mov    %r13,%r8
0x00000001092c3749: add    $0x1,%r8           ;*ladd
                                              ; - test.TestClient::[email protected] (line 10)
0x00000001092c374d: inc    %ebx               ;*iinc
                                              ; - test.TestClient::[email protected] (line 9)
0x00000001092c374f: cmp    %r11d,%ebx
0x00000001092c3752: jge    0x00000001092c3759  ;*if_icmpge
                                              ; - test.TestClient::[email protected] (line 9)
0x00000001092c3754: mov    %r8,%r13
0x00000001092c3757: jmp    0x00000001092c3746
0x00000001092c3759: cmp    $0x5f5e0f2,%ebx
0x00000001092c375f: jl     0x00000001092c3713
0x00000001092c3761: mov    %r13,%r10
0x00000001092c3764: mov    %r8,%r13
0x00000001092c3767: mov    %r10,%r8
0x00000001092c376a: jmp    0x00000001092c3729  ;*if_icmpge
                                              ; - test.TestClient::[email protected] (line 8)
0x00000001092c376c: mov    $0x24,%esi
0x00000001092c3771: mov    %r13,%rbp
0x00000001092c3774: data32 xchg %ax,%ax
0x00000001092c3777: callq  0x0000000109298f20  ; OopMap{off=188}
                                              ;*getstatic out
                                              ; - test.TestClient::[email protected] (line 13)
                                              ;   {runtime_call}
0x00000001092c377c: callq  0x0000000108c1c202  ;*getstatic out
                                              ; - test.TestClient::[email protected] (line 13)
                                              ;   {runtime_call}

Также у вас должен быть файл hotspot.log с шагами компилятора JIT. Вот выдержка:

<phase name='optimizer' nodes='114' live='77' stamp='0.100'>
            <phase name='idealLoop' nodes='115' live='67' stamp='0.100'>
                <loop_tree>
                    <loop idx='119' >
                        <loop idx='185' main_loop='185' >
                        </loop>
                    </loop>
                </loop_tree>
                <phase_done name='idealLoop' nodes='197' live='111' stamp='0.101'/>
            </phase>
            <phase name='idealLoop' nodes='197' live='111' stamp='0.101'>
                <loop_tree>
                    <loop idx='202' >
                        <loop idx='159' inner_loop='1' pre_loop='131' >
                        </loop>
                        <loop idx='210' inner_loop='1' main_loop='210' >
                        </loop>
                        <loop idx='138' inner_loop='1' post_loop='131' >
                        </loop>
                    </loop>
                </loop_tree>
                <phase_done name='idealLoop' nodes='221' live='113' stamp='0.101'/>
            </phase>
            <phase name='idealLoop' nodes='221' live='113' stamp='0.101'>
                <loop_tree>
                    <loop idx='202' >
                        <loop idx='159' inner_loop='1' pre_loop='131' >
                        </loop>
                        <loop idx='210' inner_loop='1' main_loop='210' >
                        </loop>
                        <loop idx='138' inner_loop='1' post_loop='131' >
                        </loop>
                    </loop>
                </loop_tree>
                <phase_done name='idealLoop' nodes='241' live='63' stamp='0.101'/>
            </phase>
            <phase name='ccp' nodes='241' live='63' stamp='0.101'>
                <phase_done name='ccp' nodes='241' live='63' stamp='0.101'/>
            </phase>
            <phase name='idealLoop' nodes='241' live='63' stamp='0.101'>
                <loop_tree>
                    <loop idx='202' inner_loop='1' >
                    </loop>
                </loop_tree>
                <phase_done name='idealLoop' nodes='253' live='56' stamp='0.101'/>
            </phase>
            <phase name='idealLoop' nodes='253' live='56' stamp='0.101'>
                <phase_done name='idealLoop' nodes='253' live='33' stamp='0.101'/>
            </phase>
            <phase_done name='optimizer' nodes='253' live='33' stamp='0.101'/>
        </phase>

Вы можете дополнительно проанализировать файл hotspot.log, созданный компилятором JIT, с помощью инструмента JitWatch https://github.com/AdoptOpenJDK/jitwatch/wiki

JitWatch TriView for TestClient

Чтобы отключить компилятор JIT и запустить виртуальную машину Java во все интерпретируемом режиме, вы можете использовать опцию -Djava.compiler = NONE JVM.

Аналогичный вопрос в этом сообщении Почему моя JVM делает некоторую оптимизацию цикла выполнения и делает мой код ошибкой?

Ответ 2

Это из-за оптимизации компилятора /JVM. Когда вы печатаете результат, вычисление и, следовательно, компилятор будет поддерживать цикл.

Со стороны, компилятор /JVM удалит цикл, когда вы ничего не используете из цикла.

Ответ 3

В принципе, JVM действительно очень умный. Может показаться, что если вы используете какую-либо переменную или нет, и на основании этого она может фактически удалить любую обработку, связанную с этим. Поскольку вы прокомментировали свою строку кода, которая печатает "значение", она считает, что эта переменная не будет использоваться нигде и не будет запускать цикл даже не один раз.

Но когда вы сделаете напечатаете значение, оно должно запустить ваши циклы, что опять же очень большое число (50000 * 100000000). Теперь время работы этого цикла зависит от вашего множества факторов, включая, помимо прочего, процессор вашего компьютера, память, предоставленную JVM, загрузку процессора и т.д.

Что касается вопроса о том, что eclipse не может его прекратить, я могу легко убить программу, используя eclipse на моей машине. Возможно, вам стоит еще раз проверить.

Ответ 4

Я подозреваю, что когда вы не распечатываете результат, компилятор замечает, что значение value никогда не используется и, следовательно, может удалить весь цикл в качестве оптимизации.

Таким образом, без println вы вообще не зацикливаетесь, и программа сразу же выходила, когда вы печатаете значение, которое вы делаете, итерации 5 000 000 000 000, и это может немного затянуться.

В качестве предложения попробуйте

public class TestClient {
public static long loop() {
    long value =0;

    for (int j = 0; j < 50000; j++) {
        for (int i = 0; i < 100000000; i++) {
            value += 1;
        }
    }
    return value   
 }

public static void main(String[] args) {
    // this might also take rather long
    loop();
    // as well as this
    // System.out.println(loop());
}
}

Здесь компилятор не сможет оптимизировать цикл в loop(), поскольку он может быть вызван из разных других классов, поэтому он будет выполняться во всех случаях.

Ответ 5

Почему требуется время для выполнения, когда печатается значение результата? Обратите внимание: если я не печатаю значение, тот же цикл сразу заканчивается.

Чтобы быть правдивым, я запустил ваш код в eclipse (windows), и он продолжает работать, даже если вы прокомментируете строку system.out.println. Я дважды проверял режим отладки (если вы откроете перспективу отладки, вы увидите все запущенные приложения (по умолчанию) в верхнем левом углу.)

Но если он быстро работает для вас, то наиболее правдоподобным является то, что это связано с оптимизацией java-компилятора/JVM. Мы все научились java быстро, несмотря на то, что это интерпретируемый (в первую очередь) язык, потому что он преобразует исходный код в байт-код, использует JIT-компилятор, Hot spot и т.д.

Почему eclipse не может убить программу?

Я могу успешно убить программу в eclipse (windows). Возможно, проблема связана с конкретной версией eclipse или linux. (Не уверен). Быстрый поиск в Google дает несколько сценариев, когда eclipse не может завершить работу программы.