2018년 11월 6일 화요일

[java][thread][unsafe] 자바가 숨겨놓은 악마의 열매 sun.misc.Unsafe - 1

sun.misc.Unsafe

이름부터 강력함을 뽐내는 이 클래스는 아무나 쓸 수 없는 녀석이다.
사실 이 강력함은 우리를 다치게 할 수 있다. 그 어떤 강력한 것도 항상 제물이 필요하다.

열정을 더 불태우기 위해 내 몸을 불 속에 몸을 던지는 행위와 같이
이 세상 모든 뜨거운 것에는 장작이 필요하다.

Unsafe가 그러하다. 자바를 더욱 핫하게 만들어줄 이 녀석은 사실 우리는 쓰지 못하도록 되어 있다.
이 강력한 힘은 사용자가 쓰지는 못하도록 만든 것이다.
더 나아가, 필자의 Eclipse에서는 아예 존재하지 않는 것처럼 보인다.

너무 강력하고 위험하기 때문에, 아예 import조차 못하도록 막은 것이다. (해당 설정을 바꾸면 가능하다)

자바에서도 C와 같은 강력한 기능을 사용하려면 JNI를 이용하여 C언어 개발을 해야 할 것이다.
하지만 C를 모르고 알고 싶지도 않은 사람이 있을 것이다. 하지만 java.misc.Unsafe를 이용하면 Java API를 이용하여
low-level 프로그래밍을 할 수 있게 해준다.

일단 Unsafe를 내가 찾게 된 연유를 보자. 나는 AtomicInteger라는 녀석의 구현을 보고 싶었다.
어느때와 같이 AtomicInteger에서 숫자를 변경하는 메소드를 보았으나. 내 눈앞에 보이는건 Unsafe클래스였다.
안으로 들어가도 바이트코드만 보이는 것을 보고. 뭔가 심각한 녀석임을 눈치챘다.

녀석은 생성자가 private으로 만들어져 있다. 그리고 싱글턴 인스턴스가 private으로 존재한다. 그러므로 getUnsafe라는 녀석이 있는 것으로 봐서
녀석을 실행시키면 된다고 생각했다. 하지만 예외만 던져질 뿐이었다. 왜 그러한지 한 번 둘러보자.
public static Unsafe getUnsafe() {
  Class cc = sun.reflect.Reflection.getCallerClass(2);
  if (cc.getClassLoader() != null)
    throw new SecurityException("Unsafe");
  return theUnsafe;
}
여기서 볼 수 있듯이 일단 theUnsafe라는 녀석이 싱글턴 인스턴스다.
그리고 눈에 보이는 이상한 것들이 있는데 바로 sun.reflection.Reflection 이다.
이녀석은 현재 스레드의 메소드 스택을 확인하여 주어진 숫자 만큼 call-stack-frame 밑으로 내려가서 해당 메소드의 클래스를 리턴한다.
(이 구현을 변경될 수도 있다) 지금 숫자에는 2가 들어있으니까 콜스택의 3번째 값을 가져온다.

index 0. Reflection (getCallerClass)
index 1. Usnafe (getUnsafe)
index 2. Unsafe를 실행하는 나의 어플 내에 있는 메소드

이게 index 2 값을 가져와서 getClassLoader를 실행시킨다. 소스 상에서는 null이어야 한다.
null 레퍼런스는 HotSpot virtual machine 위에 bootstrap class loader를 가리킨다. (이 내용은 {@link Class#getClassLoader()}에 문서화 되어 있다.

아니면 예외를 던진다. (좀 더 자세한 내용은 참고 깃허브 참고)


하지만 트릭은 언제나 존재한다.

바로 이렇게 콜스택으로 체크 하는 방식이 아주 대충 디자인 된 것이라고 한다.
바로 리플렉션의 사용을 막지 않았기 때문에 바로 가져오면 된다.
public void getUnsafe() throws Exception {
  Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
  theUnsafe.setAccessible(true);
  Unsafe unsafe = (Unsafe) theUnsafe.get(null);
  System.out.println(unsafe);
}
이렇게 하면 되지만 안드로이드에서는 Unsafe클래스의 이름이 "theUnsafe"가 아니라. THE_ONE이라는 이름으로 불린다고 하니 이런 코드는 플랫폼에 의존적이다.
다르게 가져와보자.
public Unsafe getUnsafeInstance() throws Exception {
  Constructor unsafeConstructor = Unsafe.class.getDeclaredConstructor();
  Unsafe unsafe = unsafeConstructor.newInstance();
  System.out.println(unsafe);
  return unsafe;
}

좋다. 이렇게 가져와 봤다.
이게 얼마나 위험하기에 이렇게 힘겹게(?) 가져와야 하는 것일까?

해당 내용은 다음에 만들 이야기

[java][thread][unsafe] 자바가 숨겨놓은 악마의 열매 sun.misc.Unsafe - 2

에서 확인해보자.

댓글 없음 :

댓글 쓰기