리플렉션을 통한 혁신적인 JIT 최적화


9

동시성이 높은 싱글 톤 클래스에 대한 단위 테스트를 할 때 나는 다음과 같은 이상한 행동을 발견했습니다 (JDK 1.8.0_162에서 테스트 됨).

private static class SingletonClass {
    static final SingletonClass INSTANCE = new SingletonClass(0);
    final int value;

    static SingletonClass getInstance() {
        return INSTANCE;
    }

    SingletonClass(int value) {
        this.value = value;
    }
}

public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {

    System.out.println(SingletonClass.getInstance().value); // 0

    // Change the instance to a new one with value 1
    setSingletonInstance(new SingletonClass(1));
    System.out.println(SingletonClass.getInstance().value); // 1

    // Call getInstance() enough times to trigger JIT optimizations
    for(int i=0;i<100_000;++i){
        SingletonClass.getInstance();
    }

    System.out.println(SingletonClass.getInstance().value); // 1

    setSingletonInstance(new SingletonClass(2));
    System.out.println(SingletonClass.INSTANCE.value); // 2
    System.out.println(SingletonClass.getInstance().value); // 1 (2 expected)
}

private static void setSingletonInstance(SingletonClass newInstance) throws NoSuchFieldException, IllegalAccessException {
    // Get the INSTANCE field and make it accessible
    Field field = SingletonClass.class.getDeclaredField("INSTANCE");
    field.setAccessible(true);

    // Remove the final modifier
    Field modifiersField = Field.class.getDeclaredField("modifiers");
    modifiersField.setAccessible(true);
    modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);

    // Set new value
    field.set(null, newInstance);
}

main () 메소드의 마지막 두 줄은 INSTANCE 값에 동의하지 않습니다. 필자는 JIT가 필드가 정적 최종이기 때문에 메소드를 완전히 제거했다고 생각합니다. 최종 키워드를 제거하면 코드 출력이 올바른 값이됩니다.

싱글 톤에 대한 동정심 (또는 그 부족)을 버리고 이와 같은 반사를 사용하는 것이 문제를 요구한다는 것을 잊어 버립니다. 그렇다면 정적 최종 필드로만 제한됩니까?


1
싱글 톤은 하나의 인스턴스 만 존재할 수있는 클래스입니다. 따라서 싱글 톤이 없으며 static final필드 가있는 클래스 만 있습니다 . 게다가 JIT 또는 동시성으로 인해이 반사 핵이 중단되는지 여부는 중요하지 않습니다.
Holger

@Holger이 해킹은 단일 테스트를 사용하는 클래스의 여러 테스트 사례에 대한 싱글 톤을 조롱하려는 시도로만 단위 테스트에서 수행되었습니다. 동시성이 어떻게 발생했는지 알 수 없으며 (위의 코드에는 없음) 실제로 어떤 일이 있었는지 알고 싶습니다.
Kelm

1
글쎄, 당신은 당신의 질문에“고동시 싱글 톤 클래스”라고 말했고 나는 그것이 무엇이 문제가 되는지“ 문제가되지 않습니다 ”라고 말합니다 . 따라서 JIT로 인해 특정 예제 코드가 중단되고 그에 대한 해결 방법을 찾은 다음 실제 코드가 JIT로 인한 중단에서 동시성으로 인한 중단으로 변경되면 무엇을 얻었습니까?
Holger

@Holger 좋아요, 문구가 너무 강해서 죄송합니다. 내가 의미하는 바는-왜 그렇게 끔찍하게 잘못되었는지 이해하지 못하면 앞으로도 같은 일에 물리는 경향이 있으므로 "그냥 일어났다"고 가정하는 것보다 그 이유를 알고 싶습니다. 어쨌든 대답 해 주셔서 감사합니다!
Kelm

답변:


7

문자 그대로“당신의 JIT 최적화가 책임을 져야한다는 가정이 맞습니까? ”, 대답은 그렇습니다.이 특정 예에서 JIT 최적화가이 동작을 담당 할 가능성이 큽니다.

그러나 static final필드를 변경하는 것은 사양을 완전히 벗어 났기 때문에 비슷한 방식으로 벗어날 수있는 다른 요소가 있습니다. 예를 들어, JMM은 그러한 변경 사항의 메모리 가시성에 대한 정의가 없으므로 다른 스레드가 이러한 변경 사항을인지하는지 여부는 완전히 지정되지 않습니다. 심지어 동기화 프리미티브가 존재하더라도 새로운 값을 사용할 수 있으며, 이전 값을 다시 사용할 수도 있습니다.

그럼에도 불구하고 JMM과 최적화 프로그램은 어쨌든 분리하기가 어렵습니다.

귀하의 질문“ … 정적 최종 필드로만 제한됩니까? 최적화는 물론 static final필드에 국한되지 않고 정적 final필드와 같은 동작은 동일하지 않으며 이론과 실제에도 차이가 있기 때문에 대답하기가 훨씬 어렵습니다 .

비 정적 final필드의 경우 특정 상황에서 Reflection을 통한 수정이 허용됩니다. 이것은 내부 필드 를 변경하기 setAccessible(true)위해 Field인스턴스를 해킹하지 않고도 그러한 수정을 가능하게하기에 충분 하다는 사실로 표시됩니다 modifiers.

사양 은 다음과 같이 말합니다.

17.5.3. 후속 final필드 수정

역 직렬화와 같은 일부 경우 시스템은 final시공 후 객체 의 필드 를 변경해야합니다 . final필드는 리플렉션 및 기타 구현 종속적 수단을 통해 변경 될 수 있습니다. 이것이 합리적인 의미론을 갖는 유일한 패턴은 객체가 구성된 다음 final객체 의 필드가 업데이트되는 패턴입니다. 객체 필드에 대한 final모든 업데이트 final가 완료 될 때까지 객체를 다른 스레드에 표시하거나 필드를 읽지 않아야합니다 . final필드 고정은 final필드가 설정된 생성자의 끝 과 final리플렉션 또는 기타 특수 메커니즘을 통해 필드가 수정 될 때마다 발생 합니다.

또 다른 문제는 사양이 final필드를 적극적으로 최적화 할 수 있다는 것 입니다. 스레드 내 에서 생성자에서 발생하지 않는 필드 final수정 사항을 사용하여 final필드 읽기를 재정렬 할 수 있습니다.

예 17.5.3-1. final필드의 적극적인 최적화
class A {
    final int x;
    A() { 
        x = 1; 
    } 

    int f() { 
        return d(this,this); 
    } 

    int d(A a1, A a2) { 
        int i = a1.x; 
        g(a1); 
        int j = a2.x; 
        return j - i; 
    }

    static void g(A a) { 
        // uses reflection to change a.x to 2 
    } 
}

d메소드에서 컴파일러는 읽기 x및 호출을 g자유롭게 재정렬 할 수 있습니다. 따라서, new A().f()반환 할 수 -1, 0또는 1.

실제로, 위에서 설명한 법적 시나리오를 위반하지 않고 공격적인 최적화가 가능한 적절한 장소를 결정하는 것은 미해결 문제 이므로 -XX:+TrustFinalNonStaticFields, 지정 하지 않으면 HotSpot JVM은 비 정적 final필드를 필드와 같은 방식으로 최적화하지 않습니다 static final.

물론 필드를로 선언하지 않으면 finalJIT는 스레드 동기화 프리미티브가없는 경우 필드 가 절대 변경되지 않는다고 가정 할 수 없지만, 최적화 된 코드 경로에서 발생하는 실제 수정 사항 ( 반사되는 것). 따라서 여전히 적극적으로 액세스를 최적화 할 수 있지만 실행 스레드 내에서 프로그램 순서대로 읽기 및 쓰기가 여전히 발생하는 경우 에만 가능합니다. 따라서 적절한 동기화 구성없이 다른 스레드에서 볼 때만 최적화를 알 수 있습니다.


많은 사람들 이이 악용을 시도하는 final것처럼 보이지만 일부는 더 우수한 성능을 입증했지만 일부는 ns다른 많은 코드를 손상시킬 가치가 없습니다. Shenandoah가 일부 플래그를지지하는
Eugene
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.