티스토리 뷰

기술

java.lang.Object의 getClass 메서드

야자나무열매 야자나무열매 2015.09.30 16:30

java.lang.Object 클래스는 Java에서 만드는 모든 클래스들의 root 이며, 따라서 모든 클래스들은 java.lang.Object를 superclass로 갖고 태어난다. Object 클래스의 구현을 보면 @since JDK1.0로 주석이 달려있다. JDK의 시작부터 함께해왔던 클래스인만큼, Java로 애플리케이션을 만든다면 Object 클래스에 대해서 파악해둘 필요성이 있을 것이다.

java.lang.Object.getClass(...)

우선 이번 글에서는 java.lang.Object의 getClass()에 대해 알아보자.

getClass() 메서드는 다음과 같이 선언되어 있다.

public final native Class  getClass();

Object 클래스 API 문서의 getClass 메서드 항목를 보면 '이 Object의 runtime class를 돌려준다'고 되어 있다. runtime class에 대해 확인해보기 위해 예제를 만들어보자. 만약 A라는 클래스에 getClass()를 사용하는 b라는 메서드가 있다고 가정하고 A를 상속받은 클래스에서 superclass인 A의 b 메서드를 호출하도록 작성해보자.

package study.hard.javalib.nativelib;

public class GetClassTest {

     public static void main(String[] args) {
          GetClassTest tester = new GetClassTest();
          ExtendedClassA extendedA = tester.new ExtendedClassA();
          extendedA.printClass();
     }

     public class ClassA {
          public void printThisClass() {
               System.out.println(getClass());
          }
     }

     public class ExtendedClassA extends ClassA {
          public void printClass() {
               printThisClass();
          }
     }

}

위 코드의 결과는 아래와 같다.

class study.hard.javalib.nativelib.GetClassTest$ExtendedClassA

inner class로 구현한 클래스를 사용한지라 점 대신 $ 기호 뒤에 클래스 이름이 보인다. 분명히 superclass인 ClassA의 메서드에서 getClass()메서드를 사용했지만, runtime 시에 최초 호출한 ExtendedClassA의 이름이 출력되는 것을 확인할 수 있다. getClass() 메서드가 리턴하는 결과는 java.lang.Class 인데, 이는 실행중인 자바 애플리케이션의 클래스나 인터페이스들의 정보를 담고 있는 클래스이다. java.lang.Class의 toString() 메서드는 아래와 같이 구현되어 있고, 따라서 위와 같이 class라는 문자열 뒤에 getName()의 결과가 출력된다.

public String toString() {
    return (isInterface() ? "interface " : (isPrimitive() ? "" : "class "))
        + getName();
}

java.lang.Class의 getName()메서드는 아래와 같다.

public String getName() {
    String name = this.name;
    if (name == null)
        this.name = name = getName0();
    return name;
}

// cache the name to reduce the number of calls into the VM
private transient String name;
private native String getName0();

getName()도 내부적으로는 JIN로 구현되어 있다. 결국 runtime class에 대한 정보들을 얻어오는 작업은 대부분 JNI를 이용한 native c로 구현되어 있음을 알 수 있다. getClass 메서드의 선언을 보면 final 키워드가 붙어있다. Object 자체는 확장을 가정하고 만들어진 class이기 때문에 대부분의 메서드가 final 키워드가 붙어있지 않지만(equals, hashCode, toString, clone, finalize), getClass는 Java 자체적으로 수행할 수 없는 일을 JNI를 통해서 수행하기 때문에 override할 수 없도록 final 키워드가 붙어있는 것이다.

또한 getClass()메서드 대신 .class라는 키워드를 이용해 Class 객체를 얻어올 수도 있다. 첫 번째 예제를 수정하여 main 메서드 안에 아래와 같은 코드를 작성하고 실행해보면 결과가 true로 나오는 것을 볼 수 있다.

Class clazz1 = extendedA.getClass();
Class clazz2 = ExtendedClassA.class;
System.out.println(clazz1 == clazz2);

java.lang.Object.getClass()를 사용하는 예제

보통 로깅을 Log4J로 처리하는 경우 각 클래스별로 로그를 구분하기 위해 getClass()를 사용하게 된다. 정확히는 static으로 제공되지 않는 getClass() 대신 아래와 같이 class 키워드를 사용한다.

static final Logger logger = LogManager.getLogger(Bar.class.getName());

대부분의 경우, 위 예제와 같은 용도 정도로 사용하는지라 다른 적절한 예제를 찾아 헤맸다. 여기 저기 라이브러리들을 둘러보다가 netty의 io.netty.channel.ChannelHandlerAdapter 클래스에서 재미있는 예제 하나를 발견할 수 있었다.

public boolean isSharable() {
    Class clazz = getClass();
    Map, Boolean> cache = InternalThreadLocalMap.get().handlerSharableCache();
    Boolean sharable = cache.get(clazz);
    if (sharable == null) {
        sharable = clazz.isAnnotationPresent(Sharable.class);
        cache.put(clazz, sharable);
    }
    return sharable;
}

코드를 보면 Sharable이라는 어노테이션이 붙어있는 클래스인지 확인하는 작업이 이루어진다. 의미상으로는 클래스가 race condition 없이 공유 가능한지 확인하는 용도이다. 클래스가 공유 가능한지 체크하기 위해 로컬 캐싱 기법을 사용하는데, 이 때 로컬 캐시로 Map을 사용하며 key로 Class 자체를 사용하고 value로 공유가능여부를 boolean으로 저장한다. 클래스에 대한 정보이니 getClass() 메서드를 활용하여 반환되는 Class 객체를 그대로 key로 사용한 것 같다. 굳이 로컬 캐시를 활용한 이유는 Netty의 이슈를 보면 알 수 있다. JDK7 기준으로 Class.isAnnotationPresent(...) 메서드는 내부적으로 Class.initAnnotationsIfNecessary(...) 메서드를 활용하는데, 이 메서드는 synchronized 키워드가 걸려있다. 이 때문에 쓰레드가 수 천개가 넘어가는 경우 가끔씩 쓰레드가 블럭되는 경우가 발생하는 모양이다. JDK의 버그로까지 등록되어 있는 걸 보니 문제가 있긴 했던 것 같다. 어느 시점인지는 모르겠으나, 최신 JDK8의 경우(update 60의 경우에는 initAnnotationsIfNecessary 가 없었다.) 아얘 이 메서드가 사라지고 다른 방식으로 구현되었다. 이와 같은 문제점 때문에 쓰레드가 블럭당하는 상황을 피하고 더 빠른 속도로 처리할 수 있도록 로컬 캐싱을 활용한 것이다.

저작자 표시 비영리 동일 조건 변경 허락
신고
댓글
댓글쓰기 폼