머리말
안전하지 않은 클래스는 JDK 소스 코드의 여러 클래스에서 사용됩니다. 이 클래스는 JVM을 우회하는 몇 가지 기본 기능을 제공하며 구현은 효율성을 향상시킬 수 있습니다. 그러나, 그것은 양날의 칼입니다. 이름이 예고가되어 있기 때문에 안전하지 않으며, 할당하는 기억은 수동으로 자유롭게되어야합니다 (GC에 의해 재활용되지 않음). 안전하지 않은 클래스는 JNI의 특정 기능에 대한 간단한 대안을 제공합니다.
이 클래스는 태양의 수업에 속합니다.* API, J2SE의 실제 부분이 아니므로 공식 문서를 찾지 못할 수 있으며 슬프게도 더 나은 코드 문서가 없습니다.
이 기사는 주로 다음 기사의 편집 및 번역에 관한 것입니다.
http://mishadoff.com/blog/java-magic-part-4-sun-dot-misc-dot-unsafe/
1. 안전하지 않은 API의 대부분의 방법은 기본 구현이며, 주로 다음 범주를 포함하여 105 가지 방법으로 구성됩니다.
(1) 정보 관련. 주로 일부 저수준 메모리 정보를 반환합니다.
(2) 관련 개체. 주로 객체 및 도메인 조작 방법을 제공합니다 : AllogeyNstance (), ObjectFieldOffset ()
(3) 수업 관련. 주로 클래스 및 정적 도메인 조작 방법을 제공합니다. staticfieldoffset (), defineclass (), defineanymousclass (), enseereclassinitialized ()
(4) 배열 관련. 배열 조작 방법 : ArrayBaseOffset (), arrayIndexScale ()
(5) 동기화 관련. 주로 저수준 동기화 프리미티브 (예 : CPU 기반 CAS (Compar-and-Swap) 프리미티브) : MoniterEnter (), trymonitorEnter (), MonitoreXit (), CompareAndsWapint (), putorderEdint ()를 제공합니다.
(6) 메모리 관련. 직접 메모리 액세스 방법 (JVM 힙을 우회하고 로컬 메모리를 직접 조작) : allocatememory (), copymemory (), freememory (), getAddress (), getInt (), putint ()
2. 안전하지 않은 클래스 인스턴스를 얻는다
안전하지 않은 클래스 디자인은 JVM Trusted Startup 클래스 로더에만 제공되며 일반적인 싱글 톤 패턴 클래스입니다. 인스턴스 획득 방법은 다음과 같습니다.
공개 정적 안전하지 않은 getUnsafe () {class cc = sun.reflect.reflection.getCallerClass (2); if (cc.getClassLoader ()! = null) Throw New SecurityException ( "안전하지 않은"); Theunsafe를 반환하십시오;}비 시작 클래스 로더는 안전하지 않은 경우 직접 호출하고 보안 예상을 던질 것입니다 (특정 이유는 JVM 클래스의 부모로드 메커니즘과 관련이 있습니다).
두 가지 해결책이 있습니다. 하나는 JVM 매개 변수 -xbootclasspath를 통해 시작 클래스로 사용할 클래스를 지정하는 것입니다. 다른 방법은 Java 반사입니다.
필드 f = unsafe.class.getDeclaredfield ( "theunsafe"); f.setAccessible (true); 안전하지 않은 불안한 = (안전하지 않은) f.get (null);
개인 싱글 톤 인스턴스의 경우 True에 대한 잔인하게 액세스 할 수있는 다음 Field의 Get 메소드를 통해 안전하지 않은 객체를 직접 얻습니다. IDE에서 이러한 방법은 오류로 표시되며 다음 설정으로 해결할 수 있습니다.
환경 설정 -> java-> 컴파일러 -> 오류/경고 -> 감가 상각 및 제한된 API-> 금지 된 참조 -> 경고
3. 안전하지 않은 클래스의 "흥미로운"응용 프로그램 시나리오
(1) 클래스 초기화 방법을 우회합니다. AllotherInstance () 메소드는 공개없이 객체 생성자, 보안 검사기 또는 생성자를 우회 할 때 매우 유용 해집니다.
클래스 A {Private Long A; // 초기화되지 않은 값 공개 a () {this.a = 1; // 초기화} public long a () {return this.a; }}다음은 시공 방법, 반사 방법 및 할당 정체 ()의 비교입니다.
A O1 = 새로운 a (); // constructoro1.a (); // 인쇄 1 a o2 = a.class.newinstance (); // reflectiono2.a (); // 인쇄 1 a o3 = (a) 안전하지 않음. AllocationInstance (a.class); // unsafeo3.a (); // 인쇄 0
AllogyInstance ()는 생성자 메소드에 전혀 들어가지 않으며 싱글 톤 모드에서는 위기가 보이는 것 같습니다.
(2) 메모리 수정
메모리 수정은 C 언어에서 비교적 일반적입니다. Java에서는 보안 검사기를 우회하는 데 사용할 수 있습니다.
다음 간단한 액세스 점검 규칙을 고려하십시오.
클래스 가드 {private int access_allowed = 1; 공개 부울 giveAccess () {return 42 == access_allowed; }}정상적인 상황에서 GiveAccess는 항상 거짓을 반환하지만 항상 일어나는 것은 아닙니다.
가드 가드 = New Guard (); guard.giveAccess (); // false, access no ac // 메모리 손상 가드 .giveAccess (); // true, 액세스 부여
메모리 오프셋을 계산하고 putint () 메소드를 사용하여 클래스의 access_allowed가 수정됩니다. 클래스 구조가 알려진 경우 데이터 오프셋을 항상 계산할 수 있습니다 (C ++ 클래스의 데이터 오프셋 계산과 일치).
(3) C 언어와 유사한 크기 () 함수를 구현합니다
Java 반사 및 ObjectfieldOffset () 함수를 결합하여 C-like sizeof () 함수를 구현하십시오.
공개 정적 긴 크기 (Object O) {안전하지 않은 u = getunsafe (); 해시 필드 = New Hashset (); 클래스 C = O.getClass (); while (c! = object.class) {for (field f : c.getDeclaredFields ()) {if ((f.getModifiers () & modifier.static) == 0) {fields.add (f); }} c = c.getSuperClass (); } // 오프셋 Long MaxSize = 0; for (field f : fields) {long 오프셋 = u.objectfieldoffset (f); if (Offset> maxSize) {maxSize = 오프셋; }} return ((maxsize/8) + 1) * 8; // 패딩}알고리즘의 아이디어는 매우 명확합니다. 기본 서브 클래스에서 시작하여 자체의 비 정적 도메인과 모든 슈퍼 클래스를 차례로 꺼내서 해시 세트에 배치 한 다음 (반복 계산은 한 번만, Java는 단일 상속입니다), ObjectfieldOffset ()을 사용하여 최대 상쇄를 얻습니다.
32 비트 JVM에서 클래스 파일 오프셋이 12로 오래 읽음으로써 크기를 얻을 수 있습니다.
공개 정적 긴 크기 (개체 개체) {return getUnsafe (). getAddress (정상화 (getUnsafe (). getInt (Object, 4L)) + 12L);}Normalize () 함수는 서명 된 int가 서명되지 않은 Long으로 변환하는 메소드입니다.
개인 정적 긴 정규화 (int 값) {if (value> = 0) 반환 값; return (0l >>> 32) & value;}계산 된 두 크기의 크기는 동일합니다. () 구현의 가장 표준적인 크기는 java.lang.instrument를 사용하는 것이지만 명령 줄 매개 변수 -javaagent를 지정해야합니다.
(4) 얕은 Java 복제 구현
표준 얕은 복제 체계는 복제 가능한 인터페이스 또는 자체적으로 구현 된 복제 기능을 구현하는 것이며, 다목적 기능이 아닙니다. Sizeof () 메소드를 결합하여 얕은 복사를 달성 할 수 있습니다.
정적 물체 얕은 얕은 (Object obj) {long size = sizeof (obj); Long Start = Toaddress (OBJ); 긴 주소 = getunsafe (). Allocatememory (size); getunsafe (). copymemory (시작, 주소, 크기); FromAddress (주소);} 반환다음 toaddress () 및 FromAddress ()는 객체를 각각 주소와 역 작동으로 변환합니다.
정적 긴 두꺼비 (Object obj) {object [] array = new Object [] {obj}; long baseoffset = getunsafe (). arraybaseoffset (object []. class); return normalize (getUnsafe (). getInt (array, baseOffset));} 정적 객체 FromAdDress (long address) {object [] array = new Object [] {null}; long baseoffset = getunsafe (). arraybaseoffset (object []. class); getUnsafe (). putlong (배열,베이스 오프, 주소); 반환 배열 [0];}위의 얕은 복사 기능은 모든 Java 객체에 적용될 수 있으며 그 크기는 동적으로 계산됩니다.
(5) 메모리에서 암호를 제거합니다
비밀번호 필드는 문자열에 저장되지만 String Recycling은 JVM에서 관리합니다. 가장 안전한 방법은 비밀번호 필드를 사용한 후에 덮어 쓰는 것입니다.
필드 stringValue = string.class.getDeclaredField ( "value"); StringValue.setAccessible (true); char [] mem = (char []) stringValue.get (password); for (int i = 0; i <mem.length; i ++) {mem [i] = '?';}(6) 클래스의 동적 로딩
동적으로로드 클래스의 표준 방법은 class.forname ()입니다 (JDBC 프로그램을 작성할 때 깊이 기억합니다). 안전하지 않은 경우 Java 클래스 파일을 동적으로로드 할 수 있습니다.
바이트 [] classContents = getClassContent (); 클래스 C = getUnsafe (). defineClass (null, classContents, 0, classContents.length); c.getMethod ( "a"). 호출 (c.newinstance (), null); // 1getCrassContent () 메소드 클래스 파일을 바이트 배열로 읽습니다. private static byte [] getClassContent ()는 예외 {file f = 새 파일 ( "/home/mishadoff/tmp/a.class"); fileInputStream input = 새 FileInputStream (f); 바이트 [] 내용 = 새로운 바이트 [(int) f.length ()]; 입력 (컨텐츠); input.close (); 반환 내용;}동적 하중, 프록싱, 슬라이스 및 기타 기능에 적용 할 수 있습니다.
(7) 패키지 감지 예외는 런타임 예외입니다.
getUnsafe (). ThrowException (new ioException ());
확인 된 예외 (권장되지 않음)를 잡고 싶지 않을 때 수행 할 수 있습니다.
(8) 빠른 직렬화
표준 Java 직렬화 가능한 것은 매우 느립니다. 또한 클래스에 공개 매개 변수가없는 생성자가 있어야합니다. 외부화가 가능합니다. 클래스가 직렬화 될 스키마를 지정해야합니다. 타사 라이브러리에 의존하는 Kryo와 같은 대중적인 효율적인 직렬화 라이브러리는 메모리 소비를 증가시킬 것입니다. getInt (), getLong (), getObject () 및 기타 메소드를 통해 클래스에서 도메인의 실제 값을 얻을 수 있으며 클래스 이름과 같은 정보를 파일에 함께 유지할 수 있습니다. Kryo는 안전하지 않은 사용을 시도했지만 특정 성능 개선 데이터는 없습니다. (http://code.google.com/p/kryo/issues/detail?id=75)
(9) 비 자바 힙에 메모리를 할당합니다
Java를 사용하는 새로운 새로운 것은 힙의 객체에 대한 메모리를 할당하고 객체의 수명주기는 JVM GC에 의해 관리됩니다.
클래스 SuperArray {개인 최종 정적 int 바이 테이트 = 1; 개인 장거리 크기; 개인 긴 주소; public superArray (긴 크기) {this.size = size; address = getunsafe (). Allocatememory (size * byte); } public void set (long i, byte value) {getunsafe (). putbyte (주소 + i * 바이트, 값); } public int get (long idx) {return getUnsafe (). getByte (주소 + idx * byte); } public long size () {반환 크기; }}안전하지 않은 것에 의해 할당 된 메모리는 integer.max_value에 의해 제한되지 않으며, 비 처마 메모리에 할당됩니다. 그것을 사용할 때는 매우 신중해야합니다. 수동으로 재활용하는 것을 잊어 버리면 메모리 누출이 발생합니다. 불법 주소 액세스가 있으면 JVM이 충돌하게됩니다. 넓은 연속 영역, 실시간 프로그래밍 (JVM 대기 시간을 허용하지 않음)을 할당해야 할 때 사용할 수 있습니다. Java.nio는이 기술을 사용합니다.
(10) Java 동시성의 응용
unsafe.compareandswap ()을 사용하면 효율적인 잠금 데이터 구조를 구현하는 데 사용할 수 있습니다.
Casscounter 클래스는 카운터 {개인 휘발성 긴 카운터 = 0; 개인 안전하지 않은 안전하지 않습니다. 개인 장거리 오프셋; public cascounter ()는 예외 {unsafe = getunsafe (); 오프셋 = unsafe.objectfieldoffset (cascounter.class.getDeclaredfield ( "counter")); } @override public void ycrement () {오래 전 = 카운터; while (! unsafe.compareAndswaplong (this, Offset, 이전, + 1)) {prever = counter; }} @override public long getCounter () {반환 카운터; }}테스트를 통해 위의 데이터 구조는 기본적으로 Java 원자 변수의 효율과 동일합니다. Java Atomic 변수는 또한 안전하지 않은 비교의 비교를 사용 하며이 방법은 결국 CPU의 해당 프리미티브에 해당하므로 매우 효율적입니다. 다음은 잠금없는 해시 맵 (http://www.azulsystems.com/about_us/presentations/lock-free-hash를 구현하는 솔루션입니다.이 솔루션의 아이디어는 다음과 같습니다. 각 상태를 분석하고 사본 생성, 사본 수정, CAS 프리미티브 사용, 스핀 잠금 장치). 일반 서버 머신 (Core <32)에서 ConshErthAshMap (JDK8 이전에는 기본 16 채널 분리 잠금이 구현되었으며 ConsurEthashMap이 잠금을 사용하여 구현되었습니다)만으로도 충분합니다.
요약
위는이 기사의 전체 내용입니다. 이 기사의 내용에 모든 사람의 연구 나 작업에 대한 특정 참조 가치가 있기를 바랍니다. 궁금한 점이 있으면 의사 소통을 위해 메시지를 남길 수 있습니다. Wulin.com을 지원 해주셔서 감사합니다.