2014-04-22 4 views
5

컬렉션 (예 : 컬렉션)에 대한 배킹 배열을 만들 때 사용자가 만드는 배열의 정확한 크기는 실제로 신경 쓰지 않고 적어도 당신이 계산 한만큼.JVM의 메모리 세분성과 관련하여 배열의 최적 크기 결정

하지만 메모리 할당과 VM의 배열 헤더 덕분에 더 이상 메모리를 사용하지 않고 다소 큰 배열을 생성 할 수 있습니다. Oracle 32 비트 VM의 경우 인터넷 요구), 메모리 입상 성은 8 (모든 메모리 할당이 다음 8 바이트 경계로 올림)을 의미하며, 배열 헤더 오버 헤드는 12 바이트입니다.

즉 Object [2]를 할당 할 때 20 바이트 (12 + 2 * 4)를 사용해야하지만 실제로는 세분화되어 24 바이트를 차지합니다. 동일한 메모리 비용으로 Object [3]를 생성 할 수 있습니다. 즉, 콜렉션이 해당 배킹 배열의 크기를 조금 늦춰야합니다. 같은 원리가 primitve 배열에도 적용될 수 있습니다. 입출력 버퍼에 사용되는 byte [], string builder의 char [] 등이 있습니다.

이러한 최적화는 극단적 인 경우를 제외하고는 실제로 눈에 띄는 효과가 없지만 큰 문제는 아닙니다. 정적 메서드를 호출하여 배열 크기를 "최적화"합니다.

문제는 JDK에서 "메모리 단위까지의 원형 배열 크기"가 없음을 나타냅니다. 그리고 이러한 방법을 직접 작성하는 것은 VM의 중요한 매개 변수 인 메모리 세분성, 배열 헤더 오버 헤드 및 마지막으로 각 유형의 크기 (아키텍처 및 VM 옵션에 따라 크기가 다를 수 있으므로 주로 참조 문제)를 결정해야합니다.

그래서 이러한 매개 변수를 결정하거나 다른 방법으로 원하는 "반올림"을 달성하는 방법이 있습니까?

+0

Java는 세분화 된 메모리 관리를 허용하지 않으므로 배열의 크기를 "반올림"하는 것이 정확히 목적입니까? 나는 당신이 정적 구조 (Array와 같은)로'ArrayList'와 같은 동적 인 구조를 융합하고 있을지도 모른다고 생각합니다. 특히 Java 배열은 동적으로 크기가 지정되지 않습니다.따라서 여러분이 말한 것은 배열의 메모리 사용량을 예측하는 방법입니다 (그리고 배열 주위의 최적화가있을 수 있습니다). 그러나 배열은 여전히 ​​정확하게 요청 된 크기만을 가지고 있습니다. –

+0

@ElliotFrisch 당신은 나의 요점을 놓쳤습니다. 어쩌면 그것의 나의 영어, 그러나 나의 질문의 이해되는 것을 시도하십시오. 나는 정확하게 메모리 세분성을 쓸모없는 오버 헤드없이 할당 할 수있는 배열 크기를 결정하는 방법을 묻습니다. – Durandal

+1

좋습니다. 질문에 직접 대답하려면 아니오. 자바 런타임에 이러한 메소드가 내장되어 있다고 생각하지 않습니다. –

답변

2

흥미로운 아이디어. 나는 이것을 결정하는 더 이식 가능한 방법은 실제로 사용량을 측정하는 것이라고 생각한다. 예제 프로그램 :

public class FindMemoryUsage { 
    public static void main(String[] args) { 
     for (int i=0; i<50; i+=2) { 
      long actual = getActualUsageForN(i); 
      System.out.println(i + " = " + actual); 
      long theoretical = getTheoreticalUsageForN(i); 
      if (theoretical != actual) { 
       throw new RuntimeException("Uh oh! Mismatch!"); 
      } 
     } 
    } 

    private static long getTheoreticalUsageForN(long count) { 
     long optimal = (Unsafe.ARRAY_BYTE_BASE_OFFSET + Unsafe.ARRAY_BYTE_INDEX_SCALE * count); 
     return ((optimal - 1) & ~7) + 8; 
    } 

    private static long getActualUsageForN(int count) { 
     System.gc(); 
     byte[][] arrays = new byte[3000000][]; 
     long begin = usedMemory(); 
     for (int i=0; i<arrays.length; i++) { 
      arrays[i] = new byte[count]; 
     } 
     long end = usedMemory(); 
     return Math.round((end - begin)/(double) arrays.length); 
    } 

    private static long usedMemory() { 
     return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(); 
    } 
} 

이 프로그램은이 정보를 제공합니다

0 = 16 
2 = 16 
4 = 16 
6 = 24 
8 = 24 
10 = 24 
12 = 24 
14 = 32 
16 = 32 
18 = 32 
20 = 32 
22 = 40 
24 = 40 
26 = 40 
28 = 40 
30 = 48 
32 = 48 
34 = 48 
36 = 48 
38 = 56 
40 = 56 
42 = 56 
44 = 56 
46 = 64 
48 = 64 

이 데이터는 sun.misc.Unsafe의 상수와 8 바이트 반올림에 따라 사용의 실제 계산 및 이론적 사용 모두에서입니다 .

private static int roundSizeUp(int from) { 
    long size = (Unsafe.ARRAY_BYTE_BASE_OFFSET + Unsafe.ARRAY_BYTE_INDEX_SCALE * from); 
    long actual = ((size - 1) & ~7) + 8; 
    return (int) (actual - Unsafe.ARRAY_BYTE_BASE_OFFSET)/Unsafe.ARRAY_BYTE_INDEX_SCALE; 
} 

이것은 VM-특정 코드,하지만 당신은 아마 당신이 더 휴대 성이 필요한 경우 getActualUsageForN 전략에 따라이 작업을 수행하는 방법을 찾을 수 : 이것은 당신이 제안처럼 "반올림"이러한 상수를 사용할 수 있음을 의미 .

이것은 프로덕션 수준의 코드가 아닙니다. 오버플로에 대해 신중하게 생각하고 Unsafe 참조를 실제로 작업중인 배열 유형에 적용되는 상수로 변경하려고 할 것입니다.

+0

나는 이것을 sun.misc.Unsafe의 데이터를 사용하는 데 포함하도록 편집했다. –

1

역동적 인 크기의 콜렉션은 보조 배열의 크기를 늘리면 크기에 약간의 양을 추가하지 않고 비례하여 증가합니다. 배가시키는 것은 일반적인 선택입니다. 더 나은 성능을 제공하기 때문에이 작업을 수행합니다. 당신이 제안하는 작은 조정은 노력할만한 가치가 없을 것입니다.