8000 sun.misc.Unsafe · rangken/rangken.github.com@1f42021 · GitHub
[go: up one dir, main page]

Skip to content

Commit 1f42021

Browse files
committed
sun.misc.Unsafe
1 parent 67c4ac0 commit 1f42021

File tree

1 file changed

+304
-0
lines changed

1 file changed

+304
-0
lines changed
Lines changed: 304 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,304 @@
1+
---
2+
layout: post
3+
title: "Java sun.misc.Unsafe"
4+
date: 2015-02-04 12:30:40 +0900
5+
categories: JAVA
6+
tags: JAVA
7+
description: sun.misc.Unsafe API
8+
---
9+
10+
### java concurreny 는 어떻게 동기화를 구현하고 있을까!?
11+
- sun.misc.Unsafe 의 CAS(Compare-And-Swap) 을 통해 동기화를 구현하고 있다.
12+
- [CAS - http://en.wikipedia.org/wiki/Compare-and-swap](http://en.wikipedia.org/wiki/Compare-and-swap)
13+
14+
### sun.misc.Unsafe
15+
- 자바는 safe 한 프로그래밍 언어이고 프로그래가 메모리와 관련된 이상한 실수를 하지 않도록 해준다. 하지만 의도적으로 그런 실수들을 하도록 만드는 방법이있다. 그것은 Unsafe 클래스를 사용하는 것이다.
16+
- JDK 내부적으로만 사용하지만 안전하지 않기 때문에 Userland 에서는 사용하지 않도록 권장하는 api 인것 같다.
17+
- _sun.misc.Unsafe_ 는 public API 이다.
18+
19+
### Unsafe instantiation
20+
- Unsafe object 는 private 생성자를 가지고 있으므로 _new Unsafe()_ 를 통해 생성할 수가 없다.
21+
- _getUnsafe()_ 라는 static 생성함수가 있지만 getUnSafe 를 호출 하려고 하면 SecurityException 이 일어나게 되있다. 이 메소드는 오직 _trusted code_ 에서만 사용 가능하도록 되어있다.
22+
23+
24+
{% highlight java linenos %}
25+
public static Unsafe getUnsafe() {
26+
Class cc = sun.reflect.Reflection.getCallerClass(2);
27+
if (cc.getClassLoader() != null)
28+
throw new SecurityException("Unsafe");
29+
return theUnsafe;
30+
}
31+
{% endhighlight %}
32+
33+
- bootclasspath 를 지정해줘서 Unsafe 를 사용하도록 해줄수 있다.
34+
35+
> java -Xbootclasspath:/usr/jdk1.7.0/jre/lib/rt.jar:. test
36+
37+
38+
### Unsafe API
39+
- 105 가지의 함수들이 들어있다.
40+
- Info : low-level 의 메모리 정보를 리턴한다.
41+
- addressSize
42+
- pageSize
43+
- Objects : object 들의 메소드와 필드를 수정할수 있도록 해준다.
44+
- allocateInstance
45+
- objectFieldOffset
46+
- Classes : class 들의 메소드와 static 필드를 수정할수 있도록 해준다
47+
- staticFieldOffset
48+
- defineClass
49+
- defineAnonymousClass
50+
- ensureClassInitialized
51+
- Arrays. Arrays 수정
52+
- arrayBaseOffset
53+
- arrayIndexScale
54+
55+
- Synchronization : low-level 의 원자성 도구제공
56+
- monitorEnter
57+
- tryMonitorEnter
58+
- monitorExit
59+
- compareAndSwapInt
60+
- putOrderedInt
61+
- Memory : 직접적으로 메모리에 접근하는 메소드
62+
- allocateMemory
63+
- copyMemory
64+
- freeMemory
65+
- getAddress
66+
- getInt
67+
- putInt
68+
69+
70+
### 재미있는 사용 케이스
71+
72+
#### Avoid initialization
73+
- 생성자 호출 없이 object 생성
74+
75+
76+
{% highlight java linenos %}
77+
class A {
78+
private long a; // 초기화 안된 변수
79+
80+
public A() {
81+
this.a = 1; // 생성자에서 초기화
82+
}
83+
84+
public long a() { return this.a; }
85+
}
86+
87+
A o1 = new A(); // 생성자 호출
88+
o1.a(); // prints 1
89+
90+
A o2 = A.class.newInstance(); // reflection 으로 생성 -> 생성자 호출됨
91+
o2.a(); // prints 1
92+
93+
A o3 = (A) unsafe.allocateInstance(A.class); // unsafe -> 생성자 호출 안됨
94+
o3.a(); // prints 0
95+
{% endhighlight %}
96+
97+
#### Memory Conccuption
98+
- 직접적으로 메모리 수정을 통해 필드값 변경
99+
100+
101+
{% highlight java linenos %}
102+
class Guard {
103+
private int ACCESS_ALLOWED = 1;
104+
105+
public boolean giveAccess() {
106+
return 42 == ACCESS_ALLOWED; // 무조건 false 를 리턴하는 메소드
107+
}
108+
}
109+
Guard guard = new Guard();
110+
guard.giveAccess(); // false
111+
112+
// bypass
113+
Unsafe unsafe = getUnsafe();
114+
Field f = guard.getClass().getDeclaredField("ACCESS_ALLOWED");
115+
unsafe.putInt(guard, unsafe.objectFieldOffset(f), 42); // 강제로 memory corruption
116+
117+
guard.giveAccess(); // true
118+
{% endhighlight %}
119+
- _unsafe.putInt_ 를 통해 직접 메모리에 지정된 offset 을 통해 값을 수정할수 있다. ACCESS_ALLOWED 뒤에 추가적인 필드가 있다면 unsafe.putInt(guard, 16+unsafe.objectFieldOffset(f), 42) 를 통해 똑같이 수정 가능하다.
120+
121+
122+
#### Concurrency
123+
_Unsafe.compareAndSwap_ 메소드는 atmoic 하다. 높은 수준에 lock-free 자료구조를 구현할때 사용된다! JDK 안에 ConcurrentHashMap AtomicInteger 등등이 Unsafe 를 통해 구현 되어있다.
124+
125+
- Test!
126+
127+
{% highlight java linenos %}
128+
interface Counter {
129+
void increment();
130+
long getCounter();
131+
}
132+
133+
// CounterClient.java 간단한 Counter Thread
134+
class CounterClient implements Runnable {
135+
private Counter c;
136+
private int num;
137+
138+
public CounterClient(Counter c, int num) {
139+
this.c = c;
140+
this.num = num;
141+
}
142+
143+
@Override
144+
public void run() {
145+
for (int i = 0; i < num; i++) {
146+
c.increment();
147+
}
148+
}
149+
}
150+
151+
// main.java
152+
int NUM_OF_THREADS = 1000;
153+
int NUM_OF_INCREMENTS = 100000;
154+
ExecutorService service = Executors.newFixedThreadPool(NUM_OF_THREADS);
155+
Counter counter = ... // 테스트할 counter
156+
long before = System.currentTimeMillis();
157+
for (int i = 0; i < NUM_OF_THREADS; i++) {
158+
service.submit(new CounterClient(counter, NUM_OF_INCREMENTS));
159+
}
160+
service.shutdown();
161+
service.awaitTermination(1, TimeUnit.MINUTES);
162+
long after = System.currentTimeMillis();
163+
System.out.println("Counter result: " + c.getCounter());
164+
System.out.println("Time passed in ms:" + (after - before));
165+
{% endhighlight %}
166+
167+
168+
- 동기화가 구현안된 Counter 를 구현한다면 -> 빠르지만 틀린결과가 나온다.
169+
170+
{% highlight java linenos %}
171+
class StupidCounter implements Counter {
172+
private long counter = 0;
173+
174+
@Override
175+
public void increment() {
176+
counter++;
177+
}
178+
179+
@Override
180+
public long getCounter() {
181+
return counter;
182+
}
183+
}
184+
// Counter result: 99542945 -> 100000000 값이 나와야 하는데 틀린값이 나온다. 동기화를 안했기 때문
185+
// Time passed in ms: 679 -> 빠른 결과
186+
{% endhighlight %}
187+
188+
- synchroized 로 동기화 함수 만들면 -> 올바른 결과, 느린속도
189+
190+
{% highlight java linenos %}
191+
// 언어 차원에서 제공되는 synchrozied 키워드 사용
192+
class SyncCounter implements Counter {
193+
private long counter = 0;
194+
195+
@Override
196+
public synchronized void increment() {
197+
counter++;
198+
}
199+
200+
@Override
201+
public long getCounter() {
202+
return counter;
203+
}
204+
}
205+
// Counter result: 100000000 -> 올바른 결과
206+
// Time passed in ms: 10136 -> 동기화 하지 않았을때보다 15배정도 느리다.
207+
{% endhighlight %}
208+
209+
- lock 를 통해 동기화를 조절한다면 -> 올바른 결과, 느르지만 좀더 빠름
210+
211+
{% highlight java linenos %}
212+
// lock 은 AbstractQueuedSynchronizer Queue 를 사용하고있고 AbstractQueuedSynchronizer는 내부적으로 Unsafe 로 구현됨
213+
class LockCounter implements Counter {
214+
private long counter = 0;
215+
private WriteLock lock = new ReentrantReadWriteLock().writeLock();
216+
217+
@Override
218+
public void increment() {
219+
lock.lock();
220+
counter++;
221+
lock.unlock();
222+
}
223+
224+
@Override
225+
public long getCounter() {
226+
return counter;
227+
}
228+
}
229+
// Counter result: 100000000
230+
// Time passed in ms: 8065 -> 좀더 빨리짐
231+
{% endhighlight %}
232+
233+
- Concrruent Atomic 자료 구조인 AtomicLong 사용 -> 좀더 빠름
234+
235+
{% highlight java linenos %}
236+
// AtomicLong 도 내부적으로 Unsafe 를 사용하고 있다.
237+
class AtomicCounter implements Counter {
238+
AtomicLong counter = new AtomicLong(0);
239+
240+
@Override
241+
public void increment() {
242+
counter.incrementAndGet();
243+
}
244+
245+
@Override
246+
public long getCounter() {
247+
return counter.get();
248+
}
249+
}
250+
// Counter result: 100000000
251+
// Time passed in ms: 6454
252+
{% endhighlight %}
253+
254+
- 직접 _CompareAndSwap_ 사용 -> 가장 빠르다.
255+
256+
{% highlight java linenos %}
257+
class CASCounter implements Counter {
258+
private volatile long counter = 0; // 무한 루프에 빠지는것을 방지, 컴파일 체적화를 수행하지않음
259+
private Unsafe unsafe;
260+
private long offset;
261+
262+
public CASCounter() throws Exception {
263+
unsafe = getUnsafe();
264+
offset = unsafe.objectFieldOffset(CASCounter.class.getDeclaredField("counter"));
265+
}
266+
267+
@Override
268+
public void increment() {
269+
long before = counter;
270+
while (!unsafe.compareAndSwapLong(this, offset, before, before + 1)) {
271+
before = counter;
272+
}
273+
}
274+
275+
@Override
276+
public long getCounter() {
277+
return counter;
278+
}
279+
}
280+
// Counter result: 100000000
281+
// Time passed in ms: 6454
282+
{% endhighlight %}
283+
284+
- compareAndSwapLong > Atomic > Lock > synchroized
285+
- compareAndSwapLong (CAS) 의 동작 방식
286+
- 어떤 상태값을 가지고있다
287+
- 그것의 복사본을 생성한다.
288+
- 그것을 수정하려고한다.
289+
- 이미 수정이 되어있지 않다면 수정한다. 수정이 되어있다면 실패한다.
290+
- 실패했다면 반복한다.
291+
292+
- 좀더 직접적으로 CAS 를 사용할수록 성능 향상을 가져올수있다.
293+
294+
295+
#### 결론
296+
- 좀더 성능향상을 가져올수 있지만 **Unsafe 는 직접적으로 사용하지 말자**
297+
- 컴파일 하더라도 warning 메세지가 나온다.
298+
299+
300+
#### Reference
301+
- [Java Magic. Part 4: sun.misc.Unsafe](http://mishadoff.com/blog/java-magic-part-4-sun-dot-misc-dot-unsafe/)
302+
303+
304+

0 commit comments

Comments
 (0)
0