>  기사  >  Java  >  Java 메모리 할당 및 재활용 전략 분석

Java 메모리 할당 및 재활용 전략 분석

怪我咯
怪我咯원래의
2017-07-02 10:35:351266검색

다음 편집기는 Java 메모리 할당 및 재활용 전략에 대한 심층적인 이해를 제공합니다. 편집자님이 꽤 좋다고 생각하셔서 지금 공유하고 모두에게 참고용으로 드리고자 합니다. 에디터를 따라가서 함께 살펴볼까요

1. 소개

Java 기술 시스템에서 언급되는 자동 메모리 관리는 결국 메모리 할당과 재활용이라는 두 가지 문제에 대해 앞서 Java 재활용에 대해 이야기한 적이 있습니다. 관련 지식이 있는 여러분, 오늘은 메모리 내 java객체 할당에 대해 이야기해 보겠습니다. 평신도의 관점에서 개체의 메모리 할당은 힙에 대한 할당입니다. 개체는 주로 신세대 Eden에 할당됩니다(메모리의 개체 생성은 가비지 수집 중에 보충됩니다. 자세한 내용을 알고 싶으면 참조할 수도 있습니다. "Java Virtual Machine 심층 이해" 참조) 로컬 스레드 할당 버퍼가 시작되면 스레드 우선순위에 따라 TLAB에 할당됩니다. 어떤 경우에는 Old Generation에 직접 할당되기도 합니다.

2. 클래식 할당 전략

1. 객체는 Eden에 먼저 할당됩니다.

일반적으로 Eden에 할당할 공간이 충분하지 않으면 jvm이 a를 시작합니다. 마이너 GC. 할당된 공간이 여전히 충분하지 않은 경우 아래에 언급된 다른 조치가 있습니다.

가상 머신의 홀수 로그 매개변수 -XX:+PrintGCDetails를 설정합니다. 메모리 재활용 로그는 가비지 수집 중에 인쇄되고, 프로세스가 종료될 때 각 메모리 영역의 현재 할당이 출력됩니다. 구체적인 예를 살펴보겠습니다. 먼저 jvm 매개변수 -Xms20m -Xmx20m -Xmn10m을 설정해야 합니다. 이 세 매개변수는 Java 힙 크기가 20M이고 10M이 새 세대에 할당될 수 없으며 나머지 10M임을 나타냅니다. 구세대에 할당됩니다. -XX:SurvivorRatio=8은 새로운 세대의 jvm에서 Eden과 Survivor의 기본 비율입니다. 기본값은 8:1입니다. 그 이유는 다음 세대의 객체 중 98%가 다음 GC에서 재활용되기 때문에 가비지 컬렉션을 위한 복사 알고리즘을 사용하는 것이 매우 적합하기 때문입니다. 따라서 새로운 세대의 10M 메모리 중 8M이 Eden입니다. 1M은 Survivor이고 나머지 1M은 사용되지 않습니다. 복사 알고리즘에 협력하는 메모리 블록도 Survivor입니다.

public class ReflectTest {

  private static final int _1MB = 1024*1024;
  
  public static void testAllocation(){
    byte[] allocation1 , allocation2 , allocation3 , allocation4;
    allocation1 = new byte[2 * _1MB];
    allocation2 = new byte[2 * _1MB];
    allocation3 = new byte[2 * _1MB];
    allocation4 = new byte[6 * _1MB];
  }
  
  public static void main(String[] args) {
    ReflectTest.testAllocation();
  }
  
}

출력은 다음과 같습니다

Heap
 PSYoungGen   total 9216K, used 6651K [0x000000000b520000, 0x000000000bf20000, 0x000000000bf20000)
 eden space 8192K, 81% used [0x000000000b520000,0x000000000bb9ef28,0x000000000bd20000)
 from space 1024K, 0% used [0x000000000be20000,0x000000000be20000,0x000000000bf20000)
 to  space 1024K, 0% used [0x000000000bd20000,0x000000000bd20000,0x000000000be20000)
 PSOldGen    total 10240K, used 6144K [0x000000000ab20000, 0x000000000b520000, 0x000000000b520000)
 object space 10240K, 60% used [0x000000000ab20000,0x000000000b120018,0x000000000b520000)
 PSPermGen    total 21248K, used 2973K [0x0000000005720000, 0x0000000006be0000, 0x000000000ab20000)
 object space 21248K, 13% used [0x0000000005720000,0x0000000005a07498,0x0000000006be0000)

eden이 81%를 차지하는 것을 볼 수 있는데, 이는 Allocation1, Allocation2, Allocation3이 모두 차세대 Eden에 할당되었음을 나타냅니다.

2. Old Generation에서는 대형 객체가 직접 할당됩니다.

대형 객체는 매우 긴 문자열 및 배열과 마찬가지로 저장하기 위해 많은 양의 연속 메모리 공간이 필요한 객체를 말합니다. 큰 객체는 가상 머신의 메모리 분배에 좋지 않습니다. JVM이 한 라운드 동안만 유지되는 많은 큰 객체를 처리하는 것은 더 어렵습니다. 이러한 문제는 코드를 작성할 때 피해야 합니다. -XX:PretenureSizeThreshold 매개 변수는 가상 머신에서 제공되며, 이 값보다 큰 개체는 이전 세대에 직접 할당됩니다. 이는 Eden 영역과 Survivor 영역 사이에 대량의 메모리 복사를 방지하는 것입니다. 앞에서 언급한 재활용 알고리즘과 복사 알고리즘에 대해서는 앞에서 언급한 바 있으므로 자세히 설명하지 않겠습니다.

public class ReflectTestBig {

  private static final int _1MB = 1024*1024;
  
  public static void testAllocation(){
    byte[] allocation2 , allocation3 , allocation4;
    allocation2 = new byte[2 * _1MB];
    allocation3 = new byte[2 * _1MB];
    allocation4 = new byte[6 * _1MB];
  }
  
  public static void main(String[] args) {
    ReflectTestBig.testAllocation();
  }
  
}

출력은 다음과 같습니다

Heap
 PSYoungGen   total 8960K, used 4597K [0x000000000b510000, 0x000000000bf10000, 0x000000000bf10000)
 eden space 7680K, 59% used [0x000000000b510000,0x000000000b98d458,0x000000000bc90000)
 from space 1280K, 0% used [0x000000000bdd0000,0x000000000bdd0000,0x000000000bf10000)
 to  space 1280K, 0% used [0x000000000bc90000,0x000000000bc90000,0x000000000bdd0000)
 PSOldGen    total 10240K, used 6144K [0x000000000ab10000, 0x000000000b510000, 0x000000000b510000)
 object space 10240K, 60% used [0x000000000ab10000,0x000000000b110018,0x000000000b510000)
 PSPermGen    total 21248K, used 2973K [0x0000000005710000, 0x0000000006bd0000, 0x000000000ab10000)
 object space 21248K, 13% used [0x0000000005710000,0x00000000059f7460,0x0000000006bd0000)

allocation4가 -XX:PretenureSizeThreshold=3145728 설정을 초과한 것을 확인할 수 있습니다. Allocation4는 Old Generation에 직접 할당되었으며 Old Generation 점유율은 60%입니다. -XX:PretenureSizeThreshold=3145728 설정은 -XX:PretenureSizeThreshold=3m으로 쓸 수 없습니다. 그렇지 않으면 jvm이 이를 인식하지 못합니다.

3. 장기간 살아남은 객체는 Old Generation으로 들어갑니다

가상 머신은 메모리 관리를 위해 스트립 컬렉션 개념을 채택하므로 메모리 재활용은 어떤 객체를 새로운 세대에 배치해야 하는지 식별하고 노년기에 어떤 물건을 놓아야 하는가. 이 목적을 달성하기 위해 jvm은 각 객체에 대한 연령 카운터(Age)를 정의합니다. 객체가 Eden에서 태어나 첫 번째 Minor GC에서 살아남아 Survivor에 저장할 수 있는 경우 Survivor로 이동되고 객체의 age는 1로 설정됩니다. 개체가 Minor GC를 벗어날 때마다 해당 연령은 1씩 증가합니다. 해당 연령이 1년 임계값을 초과하면 개체는 Old Generation으로 승격됩니다. 이 임계값 jvm의 기본값은 15이며 -XX:MaxTenuringThreshold로 설정할 수 있습니다.

public class JavaTest { 
 
  static int m = 1024 * 1024; 
 
  public static void main(String[] args) { 
    byte[] a1 = new byte[1 * m / 4]; 

     byte[] a2 = new byte[7 * m]; 

     byte[] a3 = new byte[3 * m]; //GC 
  } 
}

출력은 다음과 같습니다

[GC [DefNew: 7767K->403K(9216K), 0.0062209 secs] 7767K->7571K(19456K), 0.0062482 secs]  
[Times: user=0.00 sys=0.00, real=0.01 secs]  
a3 ok 
Heap 
 def new generation  total 9216K, used 3639K [0x331d0000, 0x33bd0000, 0x33bd0000) 
 eden space 8192K, 39% used [0x331d0000, 0x334f9040, 0x339d0000) 
 from space 1024K, 39% used [0x33ad0000, 0x33b34de8, 0x33bd0000) 
 to  space 1024K,  0% used [0x339d0000, 0x339d0000, 0x33ad0000) 
 tenured generation  total 10240K, used 7168K [0x33bd0000, 0x345d0000, 0x345d0000) 
  the space 10240K, 70% used [0x33bd0000, 0x342d0010, 0x342d0200, 0x345d0000) 
 compacting perm gen total 12288K, used 381K [0x345d0000, 0x351d0000, 0x385d0000) 
  the space 12288K,  3% used [0x345d0000, 0x3462f548, 0x3462f600, 0x351d0000) 
  ro space 10240K, 55% used [0x385d0000, 0x38b51140, 0x38b51200, 0x38fd0000) 
  rw space 12288K, 55% used [0x38fd0000, 0x396744c8, 0x39674600, 0x39bd0000)

a2가 한 번 살아남았고 age는 1이며 -XX:MaxTenuringThreshold=1 집합을 충족하므로 a2는 Old Generation에 진입했고 a3는 New Generation에 진입한 것을 볼 수 있습니다. 세대.

4. 동적 개체 수명 결정

다양한 프로그램의 메모리 상태에 더 잘 적응하기 위해 가상 머신은 개체의 수명이 -XX:MaxTenuringThreshold에 의해 설정된 값에 도달해야 한다고 항상 요구하지는 않습니다. Old Age로 승격되기 전에 Survivor 공간에 있는 동일한 Age의 모든 객체의 크기의 합이 Survivor 공간의 절반보다 큰 경우 해당 Age보다 크거나 같은 객체는 -XX:MaxTenuringThreshold에 설정된 값에 도달하지 않고 기존 영역으로 직접 진입합니다.

5. 공간 할당 보장

Minor GC가 발생하면 가상 머신은 Old 세대로의 각 승격의 평균 크기가 Old 세대의 남은 공간보다 큰지 여부를 감지하여 FUll GC를 직접 수행합니다. 미만인 경우에는 HandlerPromotionFailyre 설정이 보장 실패를 허용하는지 확인하고, 허용하는 경우 Minor GC만 수행하고, 허용하지 않는 경우에는 FUll GC도 개선합니다. 즉, 신세대 Eden이 수정된 개체를 저장할 수 없는 경우 해당 개체는 Old 세대에 저장됩니다.

3. 일반적으로 사용되는 jvm 매개변수 설정

1. -Xms: 초기 힙 크기, 기본값(MinHeapFreeRatio 매개변수는 조정 가능) 사용 가능한 힙 메모리가 40% 미만일 때 JVM은 힙을 늘립니다. -Xmx 제한의 최대값입니다.

2.

3. -Xmn: Young Generation 크기(1.4or lator), 여기의 크기는 jmap -heap에 표시된 New gen과 다릅니다.

전체 힙 크기 = Young Generation 크기 + Old Generation 크기 + 영구 세대 크기.

Young Generation을 늘리면 Old Generation의 크기가 줄어듭니다. 이 값은 시스템 성능에 더 큰 영향을 미칩니다. Sun은 공식적으로 전체 힙의 3/8 구성을 권장합니다.

4. -XX:NewSize: Young Generation 크기를 설정합니다(1.3/1.4의 경우).

5. -XX:MaxNewSize: 젊은 세대의 최대값(1.3/1.4의 경우).

6. -XX:PermSize: 영구 생성(perm gen)의 초기 값을 설정합니다.

7. -XX:MaxPermSize: 영구 생성의 최대 크기를 설정합니다.

8, -Xss: 각 스레드의 스택 크기. JDK5.0 이후에는 각 스레드의 스택 크기가 1M입니다. 이전에는 응용 프로그램 스레드에 필요한 메모리 크기가 256K였습니다. 동일한 물리적 Under 메모리에서는 이 값을 줄이면 더 많은 스레드가 생성될 수 있지만 운영 체제에서는 여전히 프로세스의 스레드 수에 제한이 있으며 경험 값은 약 3000~입니다. 5000.

9, -XX:NewRatio: Young Generation(Eden 및 Survivor 영역 2개 포함)과 Old Generation(Persist Generation 제외)의 비율, -XX:NewRatio=4는 Young Generation과 Survivor 영역의 비율을 의미합니다. Old Generation은 1:4이고 Young Generation은 전체 스택의 1/5을 차지합니다. Xms=Xmx 및 Xmn이 설정된 경우 이 매개변수를 설정할 필요가 없습니다.

10. -XX:SurvivorRatio: Eden 영역과 Survivor 영역의 크기 비율을 8로 설정합니다. 그러면 두 개의 Survivor 영역과 하나의 Eden 영역의 비율이 2:8이고 하나의 Survivor 영역이 1/10을 차지합니다. 전체 젊은 세대의.

11. -XX:LargePageSizeInBytes: 메모리 페이지의 크기는 Perm의 크기에 영향을 미치므로 너무 크게 설정할 수 없습니다.

12, -XX:+DisableExplicitGC: System.gc() 끄기

13, -XX:MaxTenuringThreshold: 가비지의 최대 수명을 0으로 설정하면 Young Generation 개체가 Survivor 영역을 통과하지 않고 직접 이동합니다. Old Generation의 수가 많은 애플리케이션의 경우 효율성을 높일 수 있으며, 이 값을 더 크게 설정하면 Survivor 영역에 Young Generation 객체가 여러 번 복사되므로 생존 시간이 늘어날 수 있습니다. Young Generation의 객체 수를 늘리고 Young Generation이 재활용될 확률은 직렬 GC에서만 유효합니다.

14, -XX:PretenureSizeThreshold: 개체가 크기를 초과하면 개체가 이전 세대에서 직접 할당됩니다. 새 세대가 병렬 스캐빈지 GC를 사용하는 경우 단위 바이트가 유효하지 않습니다. 이전 세대에서 직접 할당되는 또 다른 상황은 다음과 같습니다. 큰 배열 개체 및 배열에는 외부 참조 개체가 없습니다.

15, -XX:TLABWasteTargetPercent: 에덴 지역의 TLAB 비율입니다.

4. 보충

Minor GC와 FUll GC의 차이점:

신세대 GC(Minor GC): 는 새로운 세대에서 발생하는 가비지 수집 작업을 의미합니다. Java 객체는 크기가 크며 GC의 첫 번째 라운드에서는 어떤 데이터도 벗어날 수 없으므로 Minor GC가 자주 사용되며 복구 속도가 일반적으로 빠릅니다.

Old Generation GC(FULL GC/Major GC):Old Generation에서 발생하는 GC를 말합니다. Major GC가 나타나면 하나 이상의 Minor GC가 동반되는 경우가 많습니다(절대적인 것은 아니지만 수집 전략에서). ParallelScavenge Collector의 경우) Major GC에 대한 직접 선택 프로세스가 있습니다. Major GC의 속도는 일반적으로 Minor GC보다 10배 이상 느립니다.

위 내용은 Java 메모리 할당 및 재활용 전략 분석의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.