티스토리 뷰

낙서

Java에는 포인터가 없다?

nodeal 2017.10.19 14:02

사실 꽤나 민감한 내용이다. 이전에 KLDP와 같은 커뮤니티에서는 이걸로 열찬 토론하는 장면도 목격할 수 있었다.


어떤 경우에서는 Java의 모든 인스턴스가 포인터라느니, 자료형에 대해서는 call by value가 된다느니 일이 많았다.


조금 엄밀한 방향에서 Java의 포인터에 대해 생각해보도록 하자.


포인터는 특정 개체의 주솟값을 나타내고, 주소를 이용하여 역참조를 할 수 있어야한다.


먼저 주소를 가져올 수 있는지 알아보자.


Java에서 Instance의 주솟값을 가져오는 방법부터 순탄치 않다.


어쩌면 당연하게도 Unsafe 클래스의 기능을 사용하여야 한다.


JDK 9부터는 상당히 까다로워졌는데, 번거로운 고로 JDK 8의 sun.misc.Unsafe를 사용하도록 한다. Deprecated 판정된 방식이기도 하고 JDK 10부터 sun.~~같은 모든 기능이 삭제되므로 JDK 9부터는 jdk.internal의 Unsafe를 활용하는 것이 좋다.


일단 Unsafe는 getUnsafe() 메소드를 통해 static한 Unsafe 인스턴스를 가져올 수 있다. 하지만 미리 JDK가 컴파일 될 때부터 선언되어있기를, 호출한 Class의 ClassLoader가 SystemDomain인지 VM에게 물어본 후 그렇지 않으면 SecurityException을 throw하게 되어있다. CallerSensitive란 annotation도 붙어있고, 모쪼록 JDK 내부에서만 사용할 수 있게끔 되어있는 모양이다. 하지만 theUnsafe라는 이름의 전역 변수가 있기 때문에 우리는 Reflection을 이용하여 Unsafe를 사용해볼 것이다. 물론 JDK를 수정해서 getUnsafe가 SecurityException을 일으키지 않도록 하는 방법도 있겠지만, Java 프로그램을 작성하면서 독자적인 JDK와 JRE를 사용하느니 C나 C++로 작성하는 편이 나을 것이다. 되려 C/C++은 플랫폼 의존적이게만 작성하지 않으면 되는 것이지, 런타임까지 따로 배포할 일은 없지 않은가.


그럼 theUnsafe라는 private 전역변수만 하나 가져오면 Unsafe 문제는 끝나는 것이다.


private Unsafe getUnsafe() {
    Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
    unsafeField.setAccesible(true);
    return (Unsafe) unsafeField.get(null);
}

이런 방식으로 Unsafe를 가져올 수 있다. NoSuchFieldException가 발생할 수 있다고 하는데, 우리는 JDK 소스까지 보고 있을 것을 확신하기에 적당한 try-catch를 해주면 된다. Oracle이 치사하게 이름을 쏙 바꾼다면 어쩔 수 없지만.


이제 이 Unsafe로 무엇을 할지도 문제다. 이 Unsafe에는 당장 주소를 반환하거나 주소로 객체를 불러오는 기능이 포함되어있지 않다. 우리가 직접 주소를 원하는 객체의 배열을 만든 뒤, 그 배열이 할당된 위치를 파악해서 주소를 만들어내야한다.


이 내용과 관련하여 재밌는 Github repository를 찾았다. https://github.com/bramp/unsafe 인데, UnsafeHelper 클래스를 보면 예술이다. 필요한 toAddress, fromAddress 메소드를 전부 구현해놓았다. 사실 이대로만 사용하면 되는 것을 뭐가 문제일까 싶지만, 우리가 만들고자 하는 것은 포인터이고 주소만을 알고 이용할 수 있는 것으로 끝나진 않는다. 작성자도 javadoc으로 명시해놓았듯 저 주소 자체가 포인터는 아니라며 다음 포인터, C/C++에서 *(pointer + 1)과 같은 방법을 사용할 수도 없다고 해놓았다. 기본은 저 UnsafeHelper를 사용한다치고 우리가 원하는 포인터를 만들어보자.


만든다고 해봐야 사실 대단할 것도 없다. 가리키고자 하는 인스턴스의 크기를 알고 나면 다음 인스턴스의 위치는 현재 주소에 크기만큼 더해진 곳일 것이다. UnsafeHelper에는 클래스의 크기까지 구해주는 메소드를 구현해놓았다. 사실 이만 이용해도 long next()와 같은 기능은 충분히 혼자서 만들 수 있으리라.


그럼 이제 C/C++과 같은 문제에 직면한다. 과연 포인터의 크기를 구할 수 있는가. 당연히 불가능하다. 지금 위치의 다음에 뭐가 있을지도 모르는 것이고 포인터가 연속된 위치를 가진다는 보장이 없기도 하다. C/C++에서는 배열처럼 할당된 포인터는 연속된 메모리에 위치해야 한다는 것이 표준이지만 Java는 VM이 알아서 해야할 내용을 굳이 개발자가 손대서 할당중이니 될리도 없는 노릇. 물론 크기를 미리 할당한 다음에 그 위치에 잘 쌓을 수는 있겠지만 글쎄다, 그럴바에야 Iterator보다 이점이 눈곱만큼도 없다.


그저 이러한 구현이 가능할 뿐이지 이를 실전에 이용해서 Java Pointer를 어떻게 해보고자 하지 말자. 그렇게 메모리 할당에 critical한 프로젝트라면 JNI라는 훌륭한 도구 또한 있다. JNI 오버헤드 같은 문제까지 필요할 정도면 그냥 C/C++로 작성하는 것이 맞다.


이런 방법으로 최적화하고자 하는 것 보다 프로그램 로직과 알고리즘을 개선하고 리팩토링하는 것이 훨씬 좋은 방법일 것이라는 것 또한 이 글을 읽을 수준의 사람들이라면 알 것이라 믿는다. 최적화가 맞는지도 모르겠다만.


정리하자면,

1. Java로 Pointer 구현은 가능하다.

2. 근데 Pointer의 산술 연산은 불가능하다.

3. 하지말자.


'낙서' 카테고리의 다른 글

C와 C++에서의 구조체 차이  (0) 2018.01.14
빠른 프로그램, 동적 프로그래밍  (0) 2017.10.19
Java에는 포인터가 없다?  (0) 2017.10.19
C++ #pragma once  (0) 2017.10.19
C++ 포인터 주소값 저장하기  (0) 2017.10.12
C++ 동적할당의 고찰  (0) 2017.08.07
댓글
댓글쓰기 폼