IT

루프에서 나머지 작업을 실행하는 Java 스레드가 다른 모든 스레드를 차단합니다.

itgroup 2022. 11. 18. 21:36
반응형

루프에서 나머지 작업을 실행하는 Java 스레드가 다른 모든 스레드를 차단합니다.

다음 코드 스니펫은 2개의 스레드를 실행합니다.하나는 1초마다의 단순한 타이머 로깅이고, 2번째는 나머지 동작을 실행하는 무한 루프입니다.

public class TestBlockingThread {
    private static final Logger LOGGER = LoggerFactory.getLogger(TestBlockingThread.class);

    public static final void main(String[] args) throws InterruptedException {
        Runnable task = () -> {
            int i = 0;
            while (true) {
                i++;
                if (i != 0) {
                    boolean b = 1 % i == 0;
                }
            }
        };

        new Thread(new LogTimer()).start();
        Thread.sleep(2000);
        new Thread(task).start();
    }

    public static class LogTimer implements Runnable {
        @Override
        public void run() {
            while (true) {
                long start = System.currentTimeMillis();
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // do nothing
                }
                LOGGER.info("timeElapsed={}", System.currentTimeMillis() - start);
            }
        }
    }
}

그 결과 다음과 같은 결과가 나타납니다.

[Thread-0] INFO  c.m.c.concurrent.TestBlockingThread - timeElapsed=1004
[Thread-0] INFO  c.m.c.concurrent.TestBlockingThread - timeElapsed=1003
[Thread-0] INFO  c.m.c.concurrent.TestBlockingThread - timeElapsed=13331
[Thread-0] INFO  c.m.c.concurrent.TestBlockingThread - timeElapsed=1006
[Thread-0] INFO  c.m.c.concurrent.TestBlockingThread - timeElapsed=1003
[Thread-0] INFO  c.m.c.concurrent.TestBlockingThread - timeElapsed=1004
[Thread-0] INFO  c.m.c.concurrent.TestBlockingThread - timeElapsed=1004

무한 태스크가 왜 다른 모든 스레드를 13.3초 동안 차단하는지 이해할 수 없습니다.스레드 우선 순위 및 기타 설정을 변경하려고 했지만 아무 것도 실행되지 않았습니다.

이것을 수정하기 위한 제안(OS 콘텍스트 전환 설정 조정 포함)이 있으면 알려주세요.

(Peter Lawrey 덕분에) 여기서 설명한 결과, 이 일시정지의 주된 원인은 루프 내부의 세이프 포인트에 도달하는 일이 거의 없기 때문에 JIT 컴파일된 코드 치환을 위해 모든 스레드를 정지하는 데 오랜 시간이 걸린다는 것을 알 수 있었습니다.

하지만 나는 왜 안전지점에 거의 도달하지 않는지 더 깊이 들어가 보기로 결심했다.왜 백점프를 하는지 좀 헷갈리더라고요.while이렇게 '어울리지 않다'라고 합니다.

소환합니다.-XX:+PrintAssembly 돕다

-XX:+UnlockDiagnosticVMOptions \
-XX:+TraceClassLoading \
-XX:+DebugNonSafepoints \
-XX:+PrintCompilation \
-XX:+PrintGCDetails \
-XX:+PrintStubCode \
-XX:+PrintAssembly \
-XX:PrintAssemblyOptions=-Mintel

조사 결과 람다의 세 번째 재컴파일 후C2컴파일러가 루프 내부에서 세이프포인트 폴링을 완전히 폐기했습니다.

갱신하다

중 " " "i.0은 볼 수 ★★★★★★★★★★★★★★★★★★.C2되었습니다.

for (int i = OSR_value; i != 0; i++) {
    if (1 % i == 0) {
        uncommon_trap();
    }
}
uncommon_trap();

원래 무한 루프는 카운터를 사용하여 정규 유한 루프로 재형성되었습니다!유한 카운트 루프 내의 세이프 포인트폴링을 배제하기 위한 JIT 최적화로 인해 이 루프에서도 세이프 포인트폴링은 이루어지지 않았습니다.

후, 「 」는 「 」를 참조해 주세요.i0 흔치 않은 이 취해졌다메서드가 최적화되어 인터프리터에서 계속 실행되었습니다. 지식을 중C2무한 루프를 인식하고 컴파일을 포기했습니다.나머지 메서드는 적절한 세이프 포인트를 가진 인터프리터로 진행되었습니다.

꼭 읽어야 할 블로그 투고 "Safepoints: 안전 포인트와 이 특정 문제를 다루는 니산 와카트의 "의미, 부작용오버헤드"

매우 긴 카운트 루프에서의 세이프 포인트 삭제는 문제가 있는 것으로 알려져 있습니다.(Vladimir Ivanov 덕분에) 이 문제를 해결했습니다.

버그가 최종적으로 수정될 때까지 회피책을 사용할 수 있습니다.

  1. 를 사용해 볼 수 있습니다(전반적인 퍼포먼스가 저하되어 JVMJDK-8161147 크래시의 원인이 될 수 있습니다).사용후C2컴파일러는 백 점프 시 세이프 포인트를 계속 유지하며 원래 포즈는 완전히 사라집니다.
  2. 문제가 으로 디세블로 , 「」를 사용합니다.
    -XX:CompileCommand='exclude,binary/class/Name,methodName'

  3. 또는 safepoint를 수동으로 추가하여 코드를 다시 작성할 수도 있습니다.를 들어, 「」입니다.Thread.yield()시 콜 시 콜이 발생함int i로로 합니다.long i(고마워, 니산 와카르트)도 일시정지를 수정합니다.

는 ,, 음, 음, 음, 음, 음, 루, 루, you, you, you, you, you, you, you, you, you, you, you, you, you, you, you, you, you, you, you, you, you, i == 0 스레드를, 이 내의 스레드가 .이 메서드를 컴파일하여 코드 교체를 트리거할 때 모든 스레드를 안전한 지점으로 가져와야 하지만, 이 작업은 코드를 실행하는 스레드뿐만 아니라 JVM 내의 모든 스레드를 잠그는 데 매우 오랜 시간이 걸립니다.

다음 명령줄 옵션을 추가했습니다.

-XX:+PrintGCApplicationStoppedTime -XX:+PrintGCApplicationConcurrentTime -XX:+PrintCompilation

시간이 더 걸릴 것 같은 부동 소수점을 사용하도록 코드를 수정했습니다.

boolean b = 1.0 / i == 0;

그리고 출력에 표시되는 것은

timeElapsed=100
Application time: 0.9560686 seconds
  41423  280 %     4       TestBlockingThread::lambda$main$0 @ -2 (27 bytes)   made not entrant
Total time for which application threads were stopped: 40.3971116 seconds, Stopping threads took: 40.3967755 seconds
Application time: 0.0000219 seconds
Total time for which application threads were stopped: 0.0005840 seconds, Stopping threads took: 0.0000383 seconds
  41424  281 %     3       TestBlockingThread::lambda$main$0 @ 2 (27 bytes)
timeElapsed=40473
  41425  282 %     4       TestBlockingThread::lambda$main$0 @ 2 (27 bytes)
  41426  281 %     3       TestBlockingThread::lambda$main$0 @ -2 (27 bytes)   made not entrant
timeElapsed=100

주의: 코드를 교환하려면 스레드를 안전한 지점에서 정지해야 합니다.단, 이러한 안전지점에 도달하는 경우는 매우 드물다고 생각됩니다(가능성이 있는 것은,i == 0을 ""로 변경"

Runnable task = () -> {
    for (int i = 1; i != 0 ; i++) {
        boolean b = 1.0 / i == 0;
    }
};

비슷한 지연이 보입니다.

timeElapsed=100
Application time: 0.9587419 seconds
  39044  280 %     4       TestBlockingThread::lambda$main$0 @ -2 (28 bytes)   made not entrant
Total time for which application threads were stopped: 38.0227039 seconds, Stopping threads took: 38.0225761 seconds
Application time: 0.0000087 seconds
Total time for which application threads were stopped: 0.0003102 seconds, Stopping threads took: 0.0000105 seconds
timeElapsed=38100
timeElapsed=100

루프에 조심스럽게 코드를 추가하면 지연이 길어집니다.

for (int i = 1; i != 0 ; i++) {
    boolean b = 1.0 / i / i == 0;
}

얻다

 Total time for which application threads were stopped: 59.6034546 seconds, Stopping threads took: 59.6030773 seconds

그러나 항상 안전점이 있는 네이티브 메서드를 사용하도록 코드를 변경합니다(내재가 아닌 경우).

for (int i = 1; i != 0 ; i++) {
    boolean b = Math.cos(1.0 / i) == 0;
}

인쇄하다

Total time for which application threads were stopped: 0.0001444 seconds, Stopping threads took: 0.0000615 seconds

" " 추가: " "if (Thread.currentThread().isInterrupted()) { ... }이치노

주의: 이는 16개의 코어 머신에서 발생했기 때문에 CPU 리소스가 부족하지 않습니다.

그런지 답을 찾았다.이것들은 세이프 포인트라고 불리며 GC에 의해 발생하는 Stop-The-World로 가장 잘 알려져 있습니다.

다음 문서 참조: JVM에서 일시 중지 로깅

이벤트가 다르면 JVM이 모든 애플리케이션 스레드를 일시 중지할 수 있습니다.이러한 일시정지를 Stop-The-World(STW) 일시정지라고 합니다.STW 포즈가 트리거되는 가장 일반적인 원인은 가비지 수집(github의 예), 다양한 JIT 액션(예), 편향된 잠금 해제(예), 특정 JVMTI 조작 등 응용 프로그램을 정지해야 하는 경우가 많습니다.

애플리케이션 스레드를 안전하게 정지할 수 있는 지점은 safepoint라고 불립니다.이 용어는 모든 STW 포즈(일시정지)를 나타낼 때도 자주 사용됩니다.

GC 로그가 네이블로 되어 있는 것은 거의 일반적입니다.단, 모든 세이프 포인트에 대한 정보가 캡처되는 것은 아닙니다.모든 것을 얻으려면 다음 JVM 옵션을 사용합니다.

-XX:+PrintGCApplicationStoppedTime -XX:+PrintGCApplicationConcurrentTime

GC를 명시적으로 참조하는 이름 지정에 대해 궁금한 경우 경보하지 마십시오.이러한 옵션을 켜면 가비지 수집 일시 중지뿐만 아니라 모든 세이프 포인트가 기록됩니다.위에서 지정한 플래그를 사용하여 다음 예(github의 소스)를 실행하는 경우.

HotSpot 용어집을 읽으면 다음과 같이 정의됩니다.

세이프 포인트

프로그램 실행 중 모든 GC 루트가 알려져 있고 모든 힙 객체의 내용이 일치하는 시점입니다.글로벌한 관점에서 모든 스레드는 GC를 실행하기 전에 세이프 포인트에서 차단해야 합니다.(특별한 경우 JNI 코드를 실행하는 스레드는 핸들만 사용하기 때문에 계속 실행할 수 있습니다.세이프 포인트 중에는 핸들 내용을 로드하는 대신 차단해야 합니다.)로컬 관점에서 세이프 포인트는 실행 스레드가 GC를 차단할 수 있는 코드 블록 내의 식별 포인트입니다.대부분의 콜 사이트는 세이프 포인트로 인정됩니다.모든 세이프 포인트에서 true를 유지하는 강력한 불변수가 존재하며, 이는 비세이프 포인트에서는 무시될 수 있습니다.컴파일된 Java 코드와 C/C++ 코드는 모두 세이프 포인트 간에 최적화되지만 세이프 포인트 간에 최적화되지 않습니다.JIT 컴파일러는 각 세이프 포인트에서 GC 맵을 내보냅니다.VM의 C/C++ 코드는 스타일화된 매크로 기반 규칙(TRAPS 등)을 사용하여 잠재적인 안전 지점을 표시합니다.

위의 플래그를 사용하여 실행하면 다음과 같은 출력이 표시됩니다.

Application time: 0.9668750 seconds
Total time for which application threads were stopped: 0.0000747 seconds, Stopping threads took: 0.0000291 seconds
timeElapsed=1015
Application time: 1.0148568 seconds
Total time for which application threads were stopped: 0.0000556 seconds, Stopping threads took: 0.0000168 seconds
timeElapsed=1015
timeElapsed=1014
Application time: 2.0453971 seconds
Total time for which application threads were stopped: 10.7951187 seconds, Stopping threads took: 10.7950774 seconds
timeElapsed=11732
Application time: 1.0149263 seconds
Total time for which application threads were stopped: 0.0000644 seconds, Stopping threads took: 0.0000368 seconds
timeElapsed=1015

세 번째 STW 이벤트에 주의해 주세요.
정지된 시간: 10.7951187초
스레드를 중지하는 데 소요된 시간:10.7950774초

JIT 자체는 거의 시간이 걸리지 않았지만 JVM이 JIT 컴파일을 실행하기로 결정한 후 STW 모드로 이행했지만 컴파일되는 코드(무한 루프)에는 콜 사이트가 없기 때문에 세이프포인트에는 도달하지 못했습니다.

STW는 JIT가 최종적으로 대기를 포기하고 코드가 무한 루프 상태에 있다고 판단했을 때 종료됩니다.

코멘트 스레드나 테스트를 스스로 실행한 결과, 일시정지는 JIT 컴파일러에 의한 것이라고 생각합니다.왜 JIT 컴파일러에 시간이 오래 걸리는지 디버깅을 할 수 없습니다.

하지만, 당신이 이걸 막는 방법만 물어봤기 때문에, 저는 해결책이 있습니다.

무한 루프를 JIT 컴파일러에서 제외할 수 있는 방법으로 끌어당깁니다.

public class TestBlockingThread {
    private static final Logger LOGGER = Logger.getLogger(TestBlockingThread.class.getName());

    public static final void main(String[] args) throws InterruptedException     {
        Runnable task = () -> {
            infLoop();
        };
        new Thread(new LogTimer()).start();
        Thread.sleep(2000);
        new Thread(task).start();
    }

    private static void infLoop()
    {
        int i = 0;
        while (true) {
            i++;
            if (i != 0) {
                boolean b = 1 % i == 0;
            }
        }
    }

다음 VM 인수를 사용하여 프로그램을 실행합니다.

-XX:CompileCommand=module,패키지테스트 블록스레드::infLoop(패키지를 패키지 정보로 바꿉니다)

메서드가 컴파일 다음과 .
## 'Test ' : 'Test Blocking' : ': : infLoop' : infLoop
에 포함되어 .

언급URL : https://stackoverflow.com/questions/39298474/java-thread-executing-remainder-operation-in-a-loop-blocks-all-other-threads

반응형