자바 1.8 - WeakHashMap 소스 코드 분석

13309 단어
개술
    Weak HashMap 을 배우 기 전에 자바 의 4 가지 인용 유형 을 간단하게 말 해 보 자. 이들 은 강 인용 (Strong Reference), 소프트 인용 (Soft Reference), 약 인용 (Weak Reference), 유령 인용 또는 허위 인용 (Phantom Reference) 이다.
  • 강 한 인용: 강 한 인용 은 자바 에서 가장 보편적 인 응용 이다. 예 를 들 어 new Object, 새로운 object 대상 은 강 한 인용 유형 에 속한다.대상 이 강 한 인용 형식 이 라면 자바 가상 컴퓨터 의 메모리 공간 이 부족 하 더 라 도 GC 는 이 대상 을 회수 하지 않 고 메모리 가 넘 칩 니 다. 예 를 들 어 우리 가 흔히 볼 수 있 는 OutOf Memory Error 오류 등 입 니 다.
  • 소프트 인용: 소프트 인용 은 강도 가 강 한 인용 에 버 금 가 는 유형 으로 소프트 Reference 를 사용 하여 표시 합 니 다.가상 컴퓨터 메모리 가 충분 할 때 이 소프트 참조 대상 을 회수 하지 않 습 니 다.가상 컴퓨터 메모리 가 부족 할 때 GC 는 소프트 참조 대상 을 회수 합 니 다.이 대상 들 을 풀 어 준 후에 도 가상 컴퓨터 가 메모리 가 부족 하면 OutOf Memory Error 오 류 를 던 질 수 있 습 니 다.따라서 소프트 인용 은 캐 시 를 만 드 는 데 적합 하 다. 캐 시 에 있 는 대상 이 다른 대상 에 비해 메모리 가 부족 할 때 방출 할 수 있 고 Mybatis 에 있 기 때문이다.
  • 약 한 인용: 약 한 인용 은 강도 적 으로 부 드 러 운 인용 보다 약 하 며, 클래스 Weak Reference 를 사용 하여 표시 합 니 다.그것 은 부 드 러 운 인용 에 비해 더 짧 은 생명 주 기 를 가지 고 있다.대상 을 인용 할 수 는 있 지만 대상 이 GC 에 회수 되 는 것 을 막 지 는 않 는 다.쓰레기 를 회수 할 때 메모리 가 충분 하 든 상 관 없 이 한 대상 의 모든 인용 이 약 한 인용 이 라면 이 대상 은 회수 된다.따라서 약 인용 대상 의 생명 주 기 는 두 번 의 GC 사이 의 이 시간 이다. 즉, 그 생명 주 기 는 하나의 쓰레기 회수 주기 에 만 존재 하고 다음 GC 이전에 만 생존 할 수 있다 는 것 이다.
  • 유령 인용: 허 인용, 허 설 된 인용, 다른 몇 가지 인용 과 달리 허 인용 은 대상 의 생명 주 기 를 결정 하지 않 는 다.만약 한 대상 이 대상 을 허위 로 인용 했다 면, 이 인용 은 유 무 와 차이 가 많 지 않 으 며, 언제든지 쓰레기 회수 기 에 의 해 수 거 될 수 있다.한편, 가상 인용 은 주로 대상 이 쓰레기 회수 기 에 의 해 회수 되 는 과정 을 추적 하 는 데 사용 된다. 예 를 들 어 프로그램 은 대상 이 회수 되 어야 하 는 지 확인 한 후에 메모리 에 새로운 대상 을 만 들 도록 신청 할 수 있다.이런 방식 을 통 해 프로그램 이 소모 하 는 메모 리 를 상대 적 으로 낮은 수량 으로 유지 할 수 있다.가상 인용 은 인용 대기 열 과 함께 사용 해 야 합 니 다.
  • 인용 대기 열: 인용 대기 열 은 감청 성질 에 속 하 는 구조 이다.예 를 들 어 한 대상 의 상태 가 바 뀌 었 고 강 한 인용 에서 약 한 인용 으로 바 뀌 었 으 며 인용 대기 열 은 이러한 인용 정 보 를 가 져 오 는 대기 열 이 며 적당 할 때 이 인용 을 처리 하 는 것 이다.

  • 『 8195 』 는 자바 중의 4 가지 인용 을 간단하게 말 했다. 이것 은 이 문장의 중점 이 아니 기 때문에 나중에 시간 이 있 으 면 이 몇 가지 인용 을 자세히 연구 해 보 자.지금부터 우 리 는 Weak HashMap 을 배우 기 시작한다.
    Weak HashMap 은 자바 의 약 한 인용 을 바탕 으로 하 는 해시 표 입 니 다.일반적인 맵 과 는 목적 이 다 릅 니 다. 주로 JVM 을 최적화 시 켜 JVM 이 쓰레기 수 거 를 할 때 쓸모없는 대상 을 스마트 하 게 회수 할 수 있 도록 하 는 데 사 용 됩 니 다.
    속성
    public class WeakHashMap
        extends AbstractMap
        implements Map {
    

        사실 Weak HashMap 의 계승 체계 와 대부분의 상수 가 HashMap 과 다 르 지 않 습 니 다.저장 소 에 있 는 차이 점 은 Weak HashMap 이 index 충돌 을 해결 하 는 데 사 용 된 것 일 수도 있 습 니 다. 빨간색 과 검은색 나 무 를 사용 하지 않 았 습 니 다.대략 다음 과 같은 특성 이 있 습 니 다.
  • API 문서 에 따라 맵 의 키 가 더 이상 사용 되 지 않 으 면 키 에 대응 하 는 키 값 도 자동 으로 Weak HashMap 에서 삭 제 됩 니 다.Weak HashMap 의 키 는 약 한 키 로 다른 Map 인터페이스의 실현 과 약간 다르다.
  • HashMap 과 유사 하 며 key 와 value 를 null 로 지원 합 니 다.
  • 역시 스 레 드 가 안전 하지 않 으 므 로 Collections. synchronizedMap 을 사용 하여 스 레 드 를 안전 하 게 할 수 있 습 니 다.
  • Cloneable, Serializable 인 터 페 이 스 를 실현 하지 못 했다.
  • //  HashMap      ,            
    private final ReferenceQueue queue = new ReferenceQueue<>();
    

        이 인용 대기 열 은 가상 컴퓨터 에서 회수 한 Entry 의 인용 을 저장 하 는 데 사 용 됩 니 다. 즉, GC 이후 key 가 제거 되면 key 에 대응 하 는 인용 은 인용 대기 열 에 들 어 갑 니 다.
    정적 내부 클래스 Entry 를 보 실 수 있 습 니 다.
    private static class Entry extends WeakReference implements Map.Entry {
        V value;
        final int hash;
        Entry next;
        Entry(Object key, V value,
              //       
              ReferenceQueue queue,
              int hash, Entry next) {
            super(key, queue);
            this.value = value;
            this.hash  = hash;
            this.next  = next;
        }
    }
    

        보시 다시 피 Entry 는 WeakReference 를 계승 하기 때문에 Entry 는 약 한 인용 유형 입 니 다.Entry 가 생 성 될 때 Reference Queue 와 연결 합 니 다. 그러면 약 한 인용 에 대한 감청 을 실현 할 수 있 습 니 다. JVM 이 회수 하면 해당 하 는 인용 은 인용 대기 열 에 추 가 됩 니 다.
    방법.
        Weak HashMap 의 대부분 방법 은 HashMap 과 유사 하 다. 붉 은 검 은 나무의 존재 가 없 기 때문에 대부분의 방법 은 매우 간단 하 다. 오늘 은 주로 expunge StaleEntries 라 는 방법, 즉 Weak HashMap 의 약 한 인용 실현 의 관건 적 인 방법 을 살 펴 보 자.
    /**
     * expungeStaleEntries                   key   ,
     *    ,  table           。
     */
    private void expungeStaleEntries() {
        //     ,     poll         ,     GC   ,     map      
        for (Object x; (x = queue.poll()) != null; ) {
            //        
            synchronized (queue) {
                @SuppressWarnings("unchecked")
                    //         Entry
                    Entry e = (Entry) x;
                //            
                int i = indexFor(e.hash, table.length);
                //          
                Entry prev = table[i];
                Entry p = prev;
                //         
                while (p != null) {
                    // p     
                    Entry next = p.next;
                    //   p      
                    if (p == e) {
                        if (prev == e)
                            table[i] = next;
                        else
                            prev.next = next;
                        // Must not null out e.next;
                        // stale entries may be in use by a HashIterator
                        //  value  null,  GC  
                        e.value = null; // Help GC
                        size--;
                        break;
                    }
                    prev = p;
                    p = next;
                }
            }
        }
    }
    

    그 중에서 상기 순환 문 구 는 간단하게 두 마디 한다.
  • 색인 이 있 는 링크 를 가 져 옵 니 다.링크 옮 겨 다 니 기;
  • 만약 에 이 링크 의 머리 노드 가 현재 노드 라면 링크 의 다음 노드 를 머리 노드 로 옮 긴 다음 에 value 를 null 로 설정 하여 후속 작업 을 한다.
  • 만약 에 체인 헤더 노드 가 현재 노드 가 아니라면 다음 에 next 에 따라 옮 겨 다 니 며 하나씩 판단 한다.현재 노드 를 조회 하면 value 를 null 로 설정 하고 후속 작업 을 합 니 다.

  •     는 Weak HashMap 에서 대부분의 방법 이 이 방법 을 직접 또는 간접 적 으로 호출 하여 회수 한 key 의 맵 을 제거 합 니 다.
    이제 우 리 는 WeakashMap 약 인용 의 대략적인 원 리 를 정리 할 수 있다.
  • Weak HashMap 을 만 들 고 해당 하 는 키 값 대 정 보 를 추가 합 니 다. 바 텀 은 하나의 배열 로 해당 하 는 키 값 대 정보 Entry 를 저장 합 니 다. Entry 가 생 성 될 때 참조 대기 열 Reference Queue 와 연 결 됩 니 다.
  • 한 약 한 키 키 가 다른 대상 에 게 사용 되 지 않 고 JVM 에 회수 되 었 을 때 이 약 한 키 에 대응 하 는 Entry 는 인용 대기 열 에 동시에 추 가 됩 니 다.
  • 현재 우리 가 Weak HashMap 을 조작 할 때 (예 를 들 어 get 방법 을 호출) 인용 대기 열 에 있 는 이 부분 데 이 터 를 먼저 처리 합 니 다. 그러면 이 약 한 키 쌍 은 자동 으로 Weak HashMap 에서 삭 제 됩 니 다.

  • 그렇다면 GC 에 의 해 제 거 된 인용 은 언제 인용 대기 열 에 들 어 갔 느 냐 는 또 다른 문제 가 있다.
    ReferenceHandler 스 레 드
    Entry 의 구 조 를 통 해 알 수 있 듯 이 Entry 는 Weak Reference 에서 계승 되 고 Weak Reference 는 자체 Reference 에서 계승 된다.우 리 는 Entry 의 구조 방법 부터 본다.
    public WeakReference(T referent, ReferenceQueue super T> q) {
        super(referent, q);
    }
    Reference(T referent, ReferenceQueue super T> queue) {
        this.referent = referent;
        this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
    }
    

    결국 Reference 추상 류 에 들 어간 것 을 볼 수 있다.
    * 8195: 8195: Reference 문 서 를 읽 으 면 Reference 대상 이 쓰레기 회수 기와 직접적인 관 계 를 가 진 다 는 것 을 알 수 있 습 니 다.이러한 직접적인 관 계 는 Reference Handler 라 는 스 레 드 를 통 해 이 루어 진다.Reference Handler 스 레 드 는 JVM 이 main 스 레 드 를 만 든 후 만 든 스 레 드 로 우선 순위 가 10 이 며 인용 대상 의 쓰레기 회수 문 제 를 처리 하 는 데 사 용 됩 니 다.
    Reference 의 변수 와 방법 을 소개 합 니 다.
    public abstract class Reference {
       
        // GC            
        static private class Lock { }
        private static Lock lock = new Lock();
        //           ,          
        private static Reference pending = null;
        //     ,  pending
        volatile ReferenceQueue super T> queue;
        
        //      ,ReferenceHandler  ,          。
        //  static       。        ,       ,
        private static class ReferenceHandler extends Thread {
        }
    }
    

    JVM 이 GC 를 진행 할 때 Reference Handler 스 레 드 가 하 는 일 을 대충 볼 수 있 습 니 다.
  • JVM 은 GC 를 진행 할 때 ConcurrentMarkSweetThread 스 레 드 (CMST 로 약칭) 를 만들어 GC 를 실행 하고 SurrogateLocker Thread 스 레 드 (SLT 로 약칭) 를 동시에 생 성 합 니 다.CMST 가 GC 를 시작 할 때 SLT 에 자바 의 Reference 대상 의 전역 잠 금 을 가 져 오 라 는 메 시 지 를 보 냅 니 다: Lock.CMS GC 가 끝 날 때 까지 JVM 은 WeakHashMap 에서 회수 대상 이 속 한 WeakReference 용기 대상 을 Reference 의 pending 속성 에 넣 고 SLT 방출 을 알 리 고 notify 전역 잠 금: Lock.이때 Reference Handler 스 레 드 의 run 방법 을 활성화 하여 wait 상태 에서 벗 어 나 작업 을 시작 합 니 다.
  • Reference Handler 이 스 레 드 는 pending 의 모든 Weak Reference 대상 을 각자 의 줄 로 이동 합 니 다. 예 를 들 어 현재 이 Weak Reference 는 특정한 Weak HashMap 대상 에 속 하면 해당 하 는 Reference Queue 줄 에 넣 습 니 다 (이 줄 은 링크 구조 입 니 다).
  • 그리고 우리 가 Weak HashMap 을 조작 할 때 인용 대기 열 에 있 는 이 부분 데 이 터 를 처리 합 니 다.

  • Reference 의 정적 코드 블록 을 살 펴 보 겠 습 니 다.
    static {
        //      
        ThreadGroup tg = Thread.currentThread().getThreadGroup();
        for (ThreadGroup tgn = tg;
             tgn != null;
             tg = tgn, tgn = tg.getParent());
        //     ReferenceHandler    
        Thread handler = new ReferenceHandler(tg, "Reference Handler");
        /* If there were a special system-only priority greater than
         * MAX_PRIORITY, it would be used here
         */
        //        
        handler.setPriority(Thread.MAX_PRIORITY);
        //       
        handler.setDaemon(true);
        //       
        handler.start();
    
        // provide access in SharedSecrets
        SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() {
            @Override
            public boolean tryHandlePendingReference() {
                return tryHandlePending(false);
            }
        });
    }
    

    Reference Handler 에서 다시 불 러 오 는 run 방법 은 다음 과 같 습 니 다.
    public void run() {
        while (true) {
            tryHandlePending(true);
        }
    }
    static boolean tryHandlePending(boolean waitForNotify) {
        // pending  , GC   ,JVM       key     ,     key     ,         Entry   pending 。
    // pending           ,  pending  ,       
        Reference r;
        Cleaner c;
        try {
            synchronized (lock) {
                if (pending != null) {
                    r = pending;
                    c = r instanceof Cleaner ? (Cleaner) r : null;
                    // unlink 'r' from 'pending' chain
                    pending = r.discovered;
                    r.discovered = null;
                } else {
                    if (waitForNotify) {
                        lock.wait();
                    }
                    // retry if waited
                    return waitForNotify;
                }
            }
        } catch (OutOfMemoryError x) {
            Thread.yield();
            // retry
            return true;
        } catch (InterruptedException x) {
            // retry
            return true;
        }
    
        // Fast path for cleaners
        if (c != null) {
            c.clean();
            return true;
        }
        //  pending       
        ReferenceQueue super Object> q = r.queue;
        if (q != ReferenceQueue.NULL) q.enqueue(r);
        return true;
    }
    

    여기까지 약 한 인용 대상 이 어떤 방식 으로 인용 대열 에 들 어 갔 는 지 알 게 되 었 다.
    예시
    우 리 는 간단 한 예 를 통 해 Weak HashMap 의 실현 을 살 펴 보 자.
    public static void main(String[] args) {
        Map weakMap = new WeakHashMap<>();
        weakMap.put(new String("1"), "1");
        weakMap.put(new String("2"), "2");
        weakMap.put(new String("3"), "3");
        weakMap.put("4", "4");
        String five = new String("5");
        weakMap.put(five, "5");
        System.out.println("weakMap.size:" + weakMap.size());
        //     GC
        System.gc();
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("=============");
        System.out.println("weakMap:" + weakMap);
        System.out.println("weakMap.size:" + weakMap.size());
    }
    

    모두 맵 에 5 개의 대상 을 넣 었 습 니 다. 결 과 를 실행 해 보 겠 습 니 다.
    weakMap.size:5
    =============
    weakMap:{4=4, 5=5}
    weakMap.size:2
    

    지도 에는 두 명의 상대 만 남 았 다.다음은 코드 를 조금 바 꾸 겠 습 니 다.
    //    put 5     , five   null,       
    weakMap.put(five, "5");
    five = null;
    
    weakMap.size:5
    =============
    weakMap:{4=4}
    weakMap.size:1
    

    다섯 번 째 대상 도 수 거 된 것 으로 보인다.위 에서 알 수 있 듯 이 데이터 1, 2, 3 은 다른 인용 이 없 기 때문에 GC 에 의 해 회수 되 고 데이터 4 는 상수 이 며 상수 탱크 에 놓 여 있 으 며 일반적으로 GC 에 의 해 회수 되 지 않 는 다.데이터 5 에 대해 서 는 가리 키 는 인용 이 null 이기 때문에 회수 되 었 습 니 다.
    필드 사용
        Weak HashMap 의 사용 장면 은 현재 tomcat 의 ConcurrentCache 에서 사용 되 고 있 습 니 다.다른 상황 에 서 는 많이 사용 되 지 않 지만 이 대상 을 알 게 된 후에 우리 가 앞으로 문제 에 부 딪 혔 을 때 해결 방안 이 아 닌 것 은 아 닙 니 다.
    총결산
        이상 은 Weak HashMap 에 대한 간단 한 인식 입 니 다. 시간 이 있 으 면 다시 깊이 연구 하고 간단하게 정리 하 겠 습 니 다.
  • 약 한 인용 대상 은 Reference Handler 수호 스 레 드 로 enqueue 작업 (입대) 을 계속 진행 합 니 다.
  • 우리 가 Weak HashMap 을 조작 할 때 Weak HashMap 이 인용 대기 열의 인용 을 자동 으로 삭제 하 는 것 이 아니 라 expunge StaleEntries 방법 을 간접 적 으로 호출 하여 실현 하 는 것 입 니 다.

  •     마지막 으로 인터넷 에서 면접 문 제 를 던 졌 는데 저 는 이 면접 문제 가 매우 재 미 있 었 다 고 생각 합 니 다. Weak HashMap 의 사용 도 살 펴 보 았 고 try - catch - finally - return 이라는 점 에 대한 파악 도 살 펴 보 았 습 니 다.
    //        
    private static String test(){
        String a = new String("a");
        WeakReference b = new WeakReference(a);
        WeakHashMap weakMap = new WeakHashMap();
        weakMap.put(b.get(), 1);
        a = null;
        System.gc();
        String c = "";
        try{
            c = b.get().replace("a", "b");
            return c;
        }catch(Exception e){
            c = "c";
            return c;
        }finally{
            c += "d";
            return c + "e";
        }
    }
    

    본 고 는 자바 내부 스 레 드 자바 의 Weak HashMap 실현 분석 을 참고 하 였 다.
    면접 문제 주소:\# 자바 에서 WeakReference 와 WeakHashMap 에 대한 이해

    좋은 웹페이지 즐겨찾기