자바 니 오의 직접 메모리

10761 단어
Java Nio 시리즈 Java Nio 의 Buffer Java Nio 의 직접 메모리 Java Nio 의 고급 벽돌공 (FileChannel) 1 Java Nio 의 고급 벽돌공 (FileChannel) 2
메모리 쌓 기와 메모리 쌓 기
우선 메모 리 를 쌓 는 것 이 무엇 인지 말씀 드 리 겠 습 니 다. 자바 에 서 는 다음 코드 목록 1 - 1 을 자주 작성 합 니 다.
public class HeapByteBufferDemo {
    public static void main(String[] args) {
        Demo demo = new Demo();//1
        demo.print();
    }
}
class Demo{
     void print() {
        System.out.println("i am a demo");
    }
}

1 곳 에서 new 키 워드 를 사용 하여 데모 대상 을 만 듭 니 다. 구체 적 으로 세 단계 로 나 눌 수 있 습 니 다.
  • jvm 이 실 행 될 때 데이터 구역 의 더미 에 메모리 공간 을 신청 합 니 다
  • 인 스 턴 스 대상 초기 화
  • 데 모 를 인용 하여 분 배 된 메모리 공간
  • 이때 demo 인용 은 쌓 인 메모리 공간 을 가리 키 며 jvm 에서 관리 하 는 것 이자 gc 의 주요 작업 영역 입 니 다.기본적으로 우 리 는 뉴 키 워드 를 사용 하고 뉴 인 스 턴 스 는 메모 리 를 쌓 고 있 습 니 다.쌓 인 메모 리 를 사용 하 는 것 은 대부분의 경우 에 여러분 의 최 우선 선택 입 니 다. 그러나 어떤 경우 에 우 리 는 쌓 인 메모 리 를 사용 하 는 것 이 적당 합 니 다. 쌓 인 메모리 가 무엇 입 니까? jvm 에서 관리 하지 않 는 다른 메모리 입 니 다. 쌓 인 메모리 라 고 부 릅 니 다.외부 메모 리 를 직접 메모리 라 고도 부 릅 니 다. 다음은 직접 메모 리 를 사용 하 는 두 가지 장점 입 니 다.
  • 직접 메모 리 는 쌓 여 있 고 너무 많이 신청 하면 gc 를 일 으 키 지 않 습 니 다.예: 외부 공간 을 신청 하고 메모리 풀 을 사용 할 때 netty 는 바로 이런 체제 입 니 다. 관심 이 있 는 동지 들 은 연 구 를 할 수 있 습 니 다. 이것 도 제 칼럼 이 언급 할 곳 입 니 다.
  • 우리 가 데 이 터 를 쓸 때 데이터 가 쌓 여 있 으 면 쌓 여 있 는 곳 에서 쌓 여 있 는 곳 으로 복사 해 야 운영 체제 가 이 복사 한 데 이 터 를 조작 할 수 있다.만약 에 데이터 가 쌓 여 있 으 면 쌓 여 있 는 곳 에서 쌓 여 있 는 곳 까지 복사 하 는 단계 가 한 번 줄 어 들 고 절약 하 는 시간 이 매우 뚜렷 하 다.왜 우리 의 운영 체제 가 내부 핵 상태 에 속 하 는 지 궁금 하 실 수도 있 습 니 다. ring 0 단계 에서 이치 대로 말 하면 모든 메모리 에 접근 할 수 있 습 니 다. 왜 쌓 인 데 이 터 를 직접 조작 하지 않 습 니까? 자바 에 gc 가 있 기 때문에 gc 는 쓸 데 이 터 를 회수 하지 않 을 수도 있 지만 이동 할 수도 있 습 니 다.운영 체 제 는 메모리 주 소 를 통 해 메모 리 를 조작 합 니 다. 메모리 주소 가 바 뀌 었 습 니 다. 파일 이나 네트워크 에 기 록 된 데 이 터 는 우리 가 쓰 고 싶 은 데이터 가 아 닐 수도 있 고 알 수 없 는 오류 가 많이 발생 할 수도 있 습 니 다.

  • 직접 메모 리 를 어떻게 사용 합 니까?
    코드 목록 2 - 1
    /**
     * @author lzq
     */
    public class DirectBufferDemo {
        public static void main(String[] args) {
            ByteBuffer demoDirectByteBuffer = ByteBuffer.allocateDirect(8);//A
            printBufferProperties("write to demoDirectByteBuffer before ", demoDirectByteBuffer);
            //put to buffer 5 bytes utf-8   
            demoDirectByteBuffer.put("hello".getBytes());
            printBufferProperties("after write to demoDirectByteBuffer ", demoDirectByteBuffer);
            //invoke flip
            demoDirectByteBuffer.flip();
            printBufferProperties("after invoke flip ", demoDirectByteBuffer);
    
            byte[] temp = new byte[demoDirectByteBuffer.limit()];
            int index = 0;
            while (demoDirectByteBuffer.hasRemaining()) {
                temp[index] = demoDirectByteBuffer.get();
                index++;
            }
            printBufferProperties("after read from demoDirectByteBuffer", demoDirectByteBuffer);
    
            System.out.println(new String(temp));
        }
        private static void printBufferProperties(String des, ByteBuffer target) {
            System.out.println(String.format("%s--position:%d,limit:%d,capacity:%s",des,
                    target.position(), target.limit(), target.capacity()));
        }
    }
    

    A 곳 만 nio 의 buffer 의 demo 와 다 릅 니 다. ByteBuffer. allocate (int) 가 Direct 를 추가 하면 DirectByteBuffer 인 스 턴 스 즉 쌓 인 메모리 가 생 성 됩 니 다. 추가 하지 않 으 면 HeapByteBuffer 입 니 다.아래 에서 호출 하 는 방법 이 모두 일치 하고 출력 하 는 것 이 일치 하 는 것 을 볼 수 있 습 니 다. 이런 패 키 징 은 사용자 에 게 일종 의 방출 이 고 인 터 페 이 스 를 대상 으로 프로 그래 밍 하 는 것 이 좋 습 니 다.그러나 작업 더미 밖의 메모리 와 작업 더미 위의 메모리 의 실현 은 약간 다 를 수 있 습 니 다. 다음은 더미 밖의 메모 리 를 어떻게 조작 하 는 지 보 겠 습 니 다.
    DirectByteBuffer put (byte) (간단 합 니 다)
    소스 코드 목록 3 - 1
    public ByteBuffer put(byte x) {
          //unsafe               (       ),               
          unsafe.putByte(ix(nextPutIndex()), ((x)));
          return this;
      }
    final int nextPutIndex() { 
         //              position       limit ,                                  
          if (position >= limit)
              throw new BufferOverflowException();
          //  position ,     buffer  
          return position++;
      }
    private long ix(int i) {
          return address + ((long)i << 0);
       //return address+((long)i);            
       // return address+((long)i<<1); DirectShortBufferU      
      }
    

    DirectByte Buffer 에 데이터 쓰기:
  • position (쓰 거나 읽 을 다음 색인)
  • 그리고 ix 방법 을 통 해 쌓 인 메모리 주 소 를 계산 합 니 다 address 는 DirectByteBuffer 인 스 턴 스 를 새로 만 들 때 신청 한 쌓 인 메모리 의 첫 번 째 주소
  • 입 니 다.
  • unsafe. putByte (long, byte) 는 주어진 주소 에 값 을 저장 하 겠 다 고 밝 혔 습 니 다. 관심 이 있 으 면 관련 native 방법 을 뒤 져 보 세 요
  • jdk 어떻게 DirectByteBuffer 를 신청 합 니까?
    jdk 가 외부 메모 리 를 어떻게 신청 하 는 지 살 펴 보 겠 습 니 다. 어떤 주의 점 이 있 는 지 먼저 DirectByte Buffer 구조 기의 실현 소스 목록 3 - 2 를 살 펴 보 겠 습 니 다.
    DirectByteBuffer(int cap) {                   // package-private
    
            super(-1, 0, cap, cap); //A
            boolean pa = VM.isDirectMemoryPageAligned(); //B
            int ps = Bits.pageSize();
            long size = Math.max(1L, (long)cap + (pa ? ps : 0));//C
            Bits.reserveMemory(size, cap); //D
    
            long base = 0;
            try {
                base = UNSAFE.allocateMemory(size);// E
            } catch (OutOfMemoryError x) {
                Bits.unreserveMemory(size, cap);
                throw x;
            }
            UNSAFE.setMemory(base, size, (byte) 0);
            if (pa && (base % ps != 0)) {
                // Round up to page boundary
                address = base + ps - (base & (ps - 1));
            } else {
                address = base;
            }
            cleaner = Cleaner.create(this, new Deallocator(base, size, cap));//F
            att = null;
        }
    
    
  • A 에서 부모 클래스 의 구조 방법 을 호출 하여 mark, position, limit, capacity 네 가지 속성 을 초기 화 합 니 다.
  • B 곳 과 C 곳 을 연합 하여 메모리 정렬 여 부 를 표시 합 니 다. 기본 값 은 일치 하지 않 습 니 다.
  • D 곳: 예 정 된 메모리, 사용 하지 않 은 메모리 수 (단위: byte) 가 신청 할 메모리 크기 보다 큰 지 판단 합 니 다. 같 으 면 총 사용량 에 신청 할 용량 을 더 하고 작 으 면 System. gc () 를 명시 적 으로 호출 한 다음 에 전체 순환 으로 gc 가 실 행 될 때 까지 기다 리 고 Reference Handler 스 레 드 로 회수 한 가상 인용 Cleaner 를 처리 하도록 합 니 다.Reference Handler 는 Cleaner 를 처리 할 때 clean 방법 을 직접 실행 하고 되 돌려 줍 니 다. 이 clean 방법 은 쌓 인 메모 리 를 회수 하 는 것 입 니 다.
  • E 처: 쌓 아 올 리 는 메모리 신청.
  • F 처: Cleaner 대상 을 만 드 는 것 은 방금 말 한 것 입 니 다. 이것 은 쌓 인 메모리 를 회수 하 는 데 사 용 됩 니 다.다음은 예 정 된 메모리 코드 Bits. reserveMemory (long size, int cap) 의 구체 적 인 방법 은 다음 과 같다. 소스 목록 3 - 3
  • static void reserveMemory(long size, int cap) {
           //             ,        。 
           //              -XX:MaxDirectMemorySize     
          //                 
            if (!memoryLimitSet && VM.isBooted()) {
                maxMemory = VM.maxDirectMemory();
                memoryLimitSet = true;
            }
            //       (         )      cas     
          // totalCapacity   ,     ,   true           size     false 
            if (tryReserveMemory(size, cap)) {
                return;
            }
          //
            final JavaLangRefAccess jlra = SharedSecrets.getJavaLangRefAccess();
            // retry while helping enqueue pending Reference objects
            // which includes executing pending Cleaner(s) which includes
            // Cleaner(s) that free direct buffer memory
            while (jlra.tryHandlePendingReference()) {
                if (tryReserveMemory(size, cap)) {
                    return;
                }
            }
            // trigger VM's Reference processing
            //    gc,          (      ),          
           // gc    ,      DirectByteBuffer         ,     
           //       ,  DirectByteBuffer       ,         
          //    
            System.gc();
         //                gc,             
         //      ,        Cleaner   (    3-2 ⑤ ),   
        //            this,    Deallocator   ,    Runnable 
       //  ,run            Bits.unreserveDirectory,unsafe.freeMemory()
            boolean interrupted = false;
            try {
                long sleepTime = 1;
                int sleeps = 0;
                while (true) {
                    if (tryReserveMemory(size, cap)) {
                        return;
                    }
                    if (sleeps >= MAX_SLEEPS) {
                        break;
                    }
                    if (!jlra.tryHandlePendingReference()) {
                        try {
                            Thread.sleep(sleepTime);
                            sleepTime <<= 1;
                            sleeps++;
                        } catch (InterruptedException e) {
                            interrupted = true;
                        }
                    }
                }
                //             ,     OOM  
                throw new OutOfMemoryError("Direct buffer memory");
            } finally {
                if (interrupted) {
                    // don't swallow interrupts
                    Thread.currentThread().interrupt();
                }
            }
        }
    

    외부 메모리 방출
    우선, 쌓 아 올 리 는 메모리 의 사용 이 GC 를 일 으 키 지 않 는 다 는 것 을 강조 하 겠 습 니 다. 그러나 방금 소스 목록 3 - 3 에서 도 쌓 아 올 리 지 않 으 면 System. gc () 를 명시 적 으로 호출 하 는 것 을 보 았 습 니 다.이것 은 왜 일 까요? 한 마디 더 하면 사용 하지 않 을 수 있 습 니 다. jvm 인자 - XX: - + DisableExplicitGC 를 사용 하여 사용 하지 않 습 니 다. 일부 대형 프로젝트 에 대해 서 는 직접 사용 하지 않 습 니 다.DirectByteBuffer 대상 을 사용 하고 대상 인용 을 비 워 두 고 쓰레기 수 거 기 를 기 다 려 서 이 대상 을 회수 하면 됩 니 다. DirectByteBuffer 대상 이 회수 되 었 을 때 그 인용 은 바로 Cleaner 대상 이 Reference Queue 에 들 어 갑 니 다.그리고 이 대기 열 을 처리 하 는 Reference Handle 스 레 드 가 있 습 니 다. 대기 열 에서 클 리 너 대상 을 꺼 내 면 clean () 방법 을 실행 합 니 다. clean 방법 은 thunk 속성 을 호출 하 는 run 방법 입 니 다. DirectByte Buffer 에 게 이 thunk 은 끼 워 넣 는 대상 입 니 다. 끼 워 넣 는 코드 는 다음 과 같 습 니 다.
    private static class Deallocator
            implements Runnable
        {
            private static Unsafe unsafe = Unsafe.getUnsafe();
    
            private long address;
            private long size;
            private int capacity;
    
            private Deallocator(long address, long size, int capacity) {
                assert (address != 0);
                this.address = address;
                this.size = size;
                this.capacity = capacity;
            }
    
            public void run() {
                if (address == 0) {
                    // Paranoia
                    return;
                }
                //      
                unsafe.freeMemory(address);
               //       ,      
                address = 0;
               
                Bits.unreserveMemory(size, capacity);
            }
        }
    

    주로 run 방법 을 보면 메모리 방출 입 니 다.한 마디 로 하면 직접 메모 리 를 사용 할 때 도 항상 주의 하여 사용 하고 인용 을 비 워 야 한다. 물론 비교적 큰 직접 메모리 자체 관 리 를 신청 하여 메모리 풀 을 만 드 는 것 도 좋 지만 바퀴 를 만 드 는 데 는 공력 이 필요 하 다.
    총결산
    직접 메모리 즉 쌓 아 올 리 는 메모리 입 니 다. 사용 할 때 우 리 는 쌓 아 올 리 는 메모리 의 크기 를 설정 해 야 합 니 다. 기본 값 은 쌓 아 올 리 는 최대 크기 이 고 쌓 아 올 리 는 메모리 도 끝 이 없 는 것 이 아니 기 때문에 사용 한 후에 풀 어야 합 니 다. 그렇지 않 으 면 쌓 아 올 리 는 메모리 의 최대 크기 를 초과 할 때 도 OOM 을 던 집 니 다. 그리고 메모리 가 없 으 면 jdk 는 명시 적 으로 GC 를 촉발 합 니 다.이것 은 직접 메모 리 를 많이 사용 하 는 응용 프로그램 에서 떨 어 뜨리 는 것 을 금지 하 는 것 이 가장 좋 거나, 직접 떨 어 뜨리 는 것 을 금지 하 는 것 이 가장 좋다.그리고 쌓 아 올 리 는 메모리 와 쌓 아 올 리 는 메모리 호출 Api 는 똑 같 습 니 다.
    후기
    '좋아요' 를 누 르 고 관심 을 가 져 보 세 요. 하하.

    좋은 웹페이지 즐겨찾기