IT박스

Java에서 객체의 크기를 결정하는 가장 좋은 방법은 무엇입니까?

itboxs 2020. 10. 3. 10:12
반응형

Java에서 객체의 크기를 결정하는 가장 좋은 방법은 무엇입니까?


데이터 행 더미가있는 CSV 파일을 읽는 응용 프로그램이 있습니다. 사용자에게 데이터 유형에 따라 행 수에 대한 요약을 제공하지만 너무 많은 데이터 행을 읽고 OutOfMemoryErrors를 발생시키지 않도록하고 싶습니다 . 각 행은 객체로 변환됩니다. 프로그래밍 방식으로 해당 개체의 크기를 쉽게 찾을 수있는 방법이 있습니까? 에 대한 기본 유형 및 객체 참조의 크기를 정의하는 참조가 VM있습니까?

지금은 read up to 32,000 rows 라는 코드가 있지만 32MB 의 메모리를 사용할 때까지 가능한 한 많은 행을 읽는 코드도 갖고 싶습니다 . 다른 질문 일 수도 있지만 여전히 알고 싶습니다.


java.lang.instrument 패키지를 사용할 수 있습니다.

이 클래스를 컴파일하고 JAR에 넣습니다.

import java.lang.instrument.Instrumentation;

public class ObjectSizeFetcher {
    private static Instrumentation instrumentation;

    public static void premain(String args, Instrumentation inst) {
        instrumentation = inst;
    }

    public static long getObjectSize(Object o) {
        return instrumentation.getObjectSize(o);
    }
}

에 다음을 추가하십시오 MANIFEST.MF.

Premain-Class: ObjectSizeFetcher

getObjectSize 사용 :

public class C {
    private int x;
    private int y;

    public static void main(String [] args) {
        System.out.println(ObjectSizeFetcher.getObjectSize(new C()));
    }
}

다음으로 호출 :

java -javaagent:ObjectSizeFetcherAgent.jar C

OpenJDK 프로젝트의 일부로 개발 된 도구 인 jol을 사용해야합니다 .

JOL (Java Object Layout)은 JVM에서 객체 레이아웃 체계를 분석하는 작은 도구 상자입니다. 이러한 도구는 Unsafe, JVMTI 및 SA (Serviceability Agent)를 많이 사용하여 실제 개체 레이아웃, 풋 프린트 및 참조를 디코딩합니다. 따라서 JOL은 힙 덤프, 사양 가정 등에 의존하는 다른 도구보다 훨씬 정확합니다.

프리미티브, 참조 및 배열 요소의 크기를 얻으려면 VMSupport.vmDetails(). 64 비트 Windows에서 실행되는 Oracle JDK 1.8.0_40 (다음 모든 예제에 사용됨)에서이 메서드는

Running 64-bit HotSpot VM.
Using compressed oop with 0-bit shift.
Using compressed klass with 3-bit shift.
Objects are 8 bytes aligned.
Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]

를 사용하여 객체 인스턴스의 얕은 크기를 가져올 수 있습니다 ClassLayout.parseClass(Foo.class).toPrintable()(선택적으로 인스턴스를에 전달 toPrintable). 이것은 해당 클래스의 단일 인스턴스가 사용하는 공간 일뿐입니다. 해당 클래스에서 참조하는 다른 개체는 포함되지 않습니다. 여기 에는 개체 헤더, 필드 정렬 및 패딩에 대한 VM 오버 헤드 포함됩니다. 대상 java.util.regex.Pattern:

java.util.regex.Pattern object internals:
 OFFSET  SIZE        TYPE DESCRIPTION                    VALUE
      0     4             (object header)                01 00 00 00 (0000 0001 0000 0000 0000 0000 0000 0000)
      4     4             (object header)                00 00 00 00 (0000 0000 0000 0000 0000 0000 0000 0000)
      8     4             (object header)                cb cf 00 20 (1100 1011 1100 1111 0000 0000 0010 0000)
     12     4         int Pattern.flags                  0
     16     4         int Pattern.capturingGroupCount    1
     20     4         int Pattern.localCount             0
     24     4         int Pattern.cursor                 48
     28     4         int Pattern.patternLength          0
     32     1     boolean Pattern.compiled               true
     33     1     boolean Pattern.hasSupplementary       false
     34     2             (alignment/padding gap)        N/A
     36     4      String Pattern.pattern                (object)
     40     4      String Pattern.normalizedPattern      (object)
     44     4        Node Pattern.root                   (object)
     48     4        Node Pattern.matchRoot              (object)
     52     4       int[] Pattern.buffer                 null
     56     4         Map Pattern.namedGroups            null
     60     4 GroupHead[] Pattern.groupNodes             null
     64     4       int[] Pattern.temp                   null
     68     4             (loss due to the next object alignment)
Instance size: 72 bytes (reported by Instrumentation API)
Space losses: 2 bytes internal + 4 bytes external = 6 bytes total

를 사용하여 개체 인스턴스의 전체 크기에 대한 요약보기를 얻을 수 있습니다 GraphLayout.parseInstance(obj).toFootprint(). 물론 풋 프린트의 일부 객체는 공유 될 수 있으므로 (다른 객체에서도 참조 됨) 해당 객체가 가비지 수집 될 때 회수 될 수있는 공간의 과대 근사치입니다. Pattern.compile("^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$")( 이 답변 에서 가져옴) 의 결과에 대해 jol은 총 1840 바이트의 풋 프린트를보고하며 그중 72 개만 Pattern 인스턴스 자체입니다.

java.util.regex.Pattern instance footprint:
     COUNT       AVG       SUM   DESCRIPTION
         1       112       112   [C
         3       272       816   [Z
         1        24        24   java.lang.String
         1        72        72   java.util.regex.Pattern
         9        24       216   java.util.regex.Pattern$1
        13        24       312   java.util.regex.Pattern$5
         1        16        16   java.util.regex.Pattern$Begin
         3        24        72   java.util.regex.Pattern$BitClass
         3        32        96   java.util.regex.Pattern$Curly
         1        24        24   java.util.regex.Pattern$Dollar
         1        16        16   java.util.regex.Pattern$LastNode
         1        16        16   java.util.regex.Pattern$Node
         2        24        48   java.util.regex.Pattern$Single
        40                1840   (total)

대신을 사용 GraphLayout.parseInstance(obj).toPrintable()하면 jol이 참조 된 각 개체에 대한 필드 역 참조의 주소, 크기, 유형, 값 및 경로를 알려 주지만 일반적으로 유용하기에는 너무 많은 세부 정보입니다. 진행중인 패턴 예제의 경우 다음을 얻을 수 있습니다. (주소는 실행 사이에 변경 될 수 있습니다.)

java.util.regex.Pattern object externals:
          ADDRESS       SIZE TYPE                             PATH                           VALUE
         d5e5f290         16 java.util.regex.Pattern$Node     .root.next.atom.next           (object)
         d5e5f2a0        120 (something else)                 (somewhere else)               (something else)
         d5e5f318         16 java.util.regex.Pattern$LastNode .root.next.next.next.next.next.next.next (object)
         d5e5f328      21664 (something else)                 (somewhere else)               (something else)
         d5e647c8         24 java.lang.String                 .pattern                       (object)
         d5e647e0        112 [C                               .pattern.value                 [^, [, a, -, z, A, -, Z, 0, -, 9, _, ., +, -, ], +, @, [, a, -, z, A, -, Z, 0, -, 9, -, ], +, \, ., [, a, -, z, A, -, Z, 0, -, 9, -, ., ], +, $]
         d5e64850        448 (something else)                 (somewhere else)               (something else)
         d5e64a10         72 java.util.regex.Pattern                                         (object)
         d5e64a58        416 (something else)                 (somewhere else)               (something else)
         d5e64bf8         16 java.util.regex.Pattern$Begin    .root                          (object)
         d5e64c08         24 java.util.regex.Pattern$BitClass .root.next.atom.val$rhs        (object)
         d5e64c20        272 [Z                               .root.next.atom.val$rhs.bits   [false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, true, false, true, true, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, true, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false]
         d5e64d30         24 java.util.regex.Pattern$1        .root.next.atom.val$lhs.val$lhs.val$lhs.val$lhs.val$lhs.val$lhs (object)
         d5e64d48         24 java.util.regex.Pattern$1        .root.next.atom.val$lhs.val$lhs.val$lhs.val$lhs.val$lhs.val$rhs (object)
         d5e64d60         24 java.util.regex.Pattern$5        .root.next.atom.val$lhs.val$lhs.val$lhs.val$lhs.val$lhs (object)
         d5e64d78         24 java.util.regex.Pattern$1        .root.next.atom.val$lhs.val$lhs.val$lhs.val$lhs.val$rhs (object)
         d5e64d90         24 java.util.regex.Pattern$5        .root.next.atom.val$lhs.val$lhs.val$lhs.val$lhs (object)
         d5e64da8         24 java.util.regex.Pattern$5        .root.next.atom.val$lhs.val$lhs.val$lhs (object)
         d5e64dc0         24 java.util.regex.Pattern$5        .root.next.atom.val$lhs.val$lhs (object)
         d5e64dd8         24 java.util.regex.Pattern$5        .root.next.atom.val$lhs        (object)
         d5e64df0         24 java.util.regex.Pattern$5        .root.next.atom                (object)
         d5e64e08         32 java.util.regex.Pattern$Curly    .root.next                     (object)
         d5e64e28         24 java.util.regex.Pattern$Single   .root.next.next                (object)
         d5e64e40         24 java.util.regex.Pattern$BitClass .root.next.next.next.atom.val$rhs (object)
         d5e64e58        272 [Z                               .root.next.next.next.atom.val$rhs.bits [false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, true, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false]
         d5e64f68         24 java.util.regex.Pattern$1        .root.next.next.next.atom.val$lhs.val$lhs.val$lhs (object)
         d5e64f80         24 java.util.regex.Pattern$1        .root.next.next.next.atom.val$lhs.val$lhs.val$rhs (object)
         d5e64f98         24 java.util.regex.Pattern$5        .root.next.next.next.atom.val$lhs.val$lhs (object)
         d5e64fb0         24 java.util.regex.Pattern$1        .root.next.next.next.atom.val$lhs.val$rhs (object)
         d5e64fc8         24 java.util.regex.Pattern$5        .root.next.next.next.atom.val$lhs (object)
         d5e64fe0         24 java.util.regex.Pattern$5        .root.next.next.next.atom      (object)
         d5e64ff8         32 java.util.regex.Pattern$Curly    .root.next.next.next           (object)
         d5e65018         24 java.util.regex.Pattern$Single   .root.next.next.next.next      (object)
         d5e65030         24 java.util.regex.Pattern$BitClass .root.next.next.next.next.next.atom.val$rhs (object)
         d5e65048        272 [Z                               .root.next.next.next.next.next.atom.val$rhs.bits [false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, true, true, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false]
         d5e65158         24 java.util.regex.Pattern$1        .root.next.next.next.next.next.atom.val$lhs.val$lhs.val$lhs.val$lhs (object)
         d5e65170         24 java.util.regex.Pattern$1        .root.next.next.next.next.next.atom.val$lhs.val$lhs.val$lhs.val$rhs (object)
         d5e65188         24 java.util.regex.Pattern$5        .root.next.next.next.next.next.atom.val$lhs.val$lhs.val$lhs (object)
         d5e651a0         24 java.util.regex.Pattern$1        .root.next.next.next.next.next.atom.val$lhs.val$lhs.val$rhs (object)
         d5e651b8         24 java.util.regex.Pattern$5        .root.next.next.next.next.next.atom.val$lhs.val$lhs (object)
         d5e651d0         24 java.util.regex.Pattern$5        .root.next.next.next.next.next.atom.val$lhs (object)
         d5e651e8         24 java.util.regex.Pattern$5        .root.next.next.next.next.next.atom (object)
         d5e65200         32 java.util.regex.Pattern$Curly    .root.next.next.next.next.next (object)
         d5e65220        120 (something else)                 (somewhere else)               (something else)
         d5e65298         24 java.util.regex.Pattern$Dollar   .root.next.next.next.next.next.next (object)

"(다른 것)"항목 은이 개체 그래프의 일부가 아닌 힙의 다른 개체를 설명합니다 .

최고의 jol 문서는 jol 저장소 jol 샘플 입니다. 샘플은 일반적인 jol 작업을 보여주고 jol을 사용하여 VM 및 가비지 수집기 내부를 분석하는 방법을 보여줍니다.


몇 년 전에 Javaworld는 복합 및 잠재적으로 중첩 된 Java 객체의 크기를 결정하는 기사를 가지고 있었으며 기본적으로 Java 에서 sizeof () 구현을 만드는 과정을 안내합니다. 이 접근 방식은 기본적으로 사람들이 실험적으로 프리미티브와 일반적인 Java 객체의 크기를 식별 한 다음 그 지식을 객체 그래프를 반복적으로 걷는 방법에 적용하여 전체 크기를 집계하는 다른 작업을 기반으로합니다.

클래스의이면에서 일어나는 일 때문에 항상 네이티브 C 구현보다 정확도가 다소 떨어질 수 있지만 좋은 지표가되어야합니다.

또는 sizeof () 구현과 함께 Java5 라이브러리를 제공하는 sizeof 라고하는 SourceForge 프로젝트가 적절 합니다.

추신 직렬화 접근 방식을 사용하지 마십시오. 직렬화 된 개체의 크기와 라이브 상태에서 사용하는 메모리 양간에 상관 관계가 없습니다.


실수로 jdk에있는 Java 클래스 "jdk.nashorn.internal.ir.debug.ObjectSizeCalculator"를 발견했습니다.이 클래스는 사용하기 쉽고 객체의 크기를 결정하는 데 매우 유용합니다.

System.out.println(ObjectSizeCalculator.getObjectSize(new gnu.trove.map.hash.TObjectIntHashMap<String>(12000, 0.6f, -1)));
System.out.println(ObjectSizeCalculator.getObjectSize(new HashMap<String, Integer>(100000)));
System.out.println(ObjectSizeCalculator.getObjectSize(3));
System.out.println(ObjectSizeCalculator.getObjectSize(new int[]{1, 2, 3, 4, 5, 6, 7 }));
System.out.println(ObjectSizeCalculator.getObjectSize(new int[100]));

결과 :

164192
48
16
48
416

첫째, "객체의 크기"는 Java에서 잘 정의 된 개념이 아닙니다. 멤버, Object 및 참조하는 모든 개체 (참조 그래프) 만있는 개체 자체를 의미 할 수 있습니다. 메모리의 크기 또는 디스크의 크기를 의미 할 수 있습니다. 그리고 JVM은 문자열과 같은 것을 최적화 할 수 있습니다.

따라서 유일하게 올바른 방법은 좋은 프로파일 러 (저는 YourKit 사용 )를 사용하여 JVM에 요청 하는 것입니다. 이는 아마도 원하는 것이 아닐 것입니다.

그러나 위의 설명에서 각 행은 독립적이고 큰 종속성 트리가없는 것처럼 들리므로 직렬화 방법은 대부분의 JVM에서 좋은 근사치가 될 것입니다. 이를 수행하는 가장 쉬운 방법은 다음과 같습니다.

 Serializable ser;
 ByteArrayOutputStream baos = new ByteArrayOutputStream();
 ObjectOutputStream oos = new ObjectOutputStream(baos);
 oos.writeObject(ser);
 oos.close();
 return baos.size();

당신은 일반적인 참조가 개체가있는 경우이 것을 기억 하지 않습니다 정확한 결과를 제공하고, 직렬화의 크기는 항상 메모리 크기와 일치하지 않습니다,하지만 좋은 근사이다. ByteArrayOutputStream 크기를 합리적인 값으로 초기화하면 코드가 좀 더 효율적입니다.


JVM에서 사용되는 메모리 양과 사용 가능한 메모리 양을 알고 싶다면 다음과 같이 시도해 볼 수 있습니다.

// Get current size of heap in bytes
long heapSize = Runtime.getRuntime().totalMemory();

// Get maximum size of heap in bytes. The heap cannot grow beyond this size.
// Any attempt will result in an OutOfMemoryException.
long heapMaxSize = Runtime.getRuntime().maxMemory();

// Get amount of free memory within the heap in bytes. This size will increase
// after garbage collection and decrease as new objects are created.
long heapFreeSize = Runtime.getRuntime().freeMemory();

편집 : 질문 작성자가 "32MB의 메모리를 사용할 때까지 가능한 한 많은 행을 읽습니다"를 처리하는 논리를 갖고 싶다고 말했기 때문에 이것이 도움이 될 것이라고 생각했습니다.


트위터에서 일할 때 깊은 물체 크기를 계산하는 유틸리티를 작성했습니다. 다양한 메모리 모델 (32 비트, 압축 된 oops, 64 비트), 패딩, 하위 클래스 패딩을 고려하여 원형 데이터 구조 및 배열에서 올바르게 작동합니다. 이 하나의 .java 파일을 컴파일 할 수 있습니다. 외부 종속성이 없습니다.

https://github.com/twitter/commons/blob/master/src/java/com/twitter/common/objectsize/ObjectSizeCalculator.java


다른 많은 답변은 얕은 크기를 제공합니다. 예를 들어 키나 값이없는 HashMap의 크기는 원하는 것이 아닐 수 있습니다.

jamm 프로젝트는 위의 java.lang.instrumentation 패키지를 사용하지만 트리를 따라 다니므로 깊은 메모리 사용을 제공 할 수 있습니다.

new MemoryMeter().measureDeep(myHashMap);

https://github.com/jbellis/jamm

MemoryMeter를 사용하려면 "-javaagent : /jamm.jar"로 JVM을 시작하십시오.


반사를 사용하여 물체를 걸어야합니다. 다음과 같이주의하십시오.

  • 객체를 할당하는 것만으로도 JVM에서 약간의 오버 헤드가 발생합니다. 양은 JVM에 따라 다르므로이 값을 매개 변수로 만들 수 있습니다. 적어도 그것을 상수 (8 바이트?)로 만들고 할당 된 모든 것에 적용하십시오.
  • 그냥 있기 때문에 byte이론적으로 1 바이트는 메모리에 하나 걸리는 것은 아닙니다.
  • 객체 참조에는 루프가 있으므로 무한 루프를 제거 하려면 객체 같음을 비교기로 사용하여 a HashMap또는 일부 를 유지해야합니다 .

@jodonnell : 솔루션의 단순성이 마음에 들지만 많은 개체가 직렬화 가능하지 않고 (예외가 발생 함) 필드가 일시적 일 수 있으며 개체가 표준 메서드를 재정의 할 수 있습니다.


도구로 측정하거나 손으로 추정해야하며 사용중인 JVM에 따라 다릅니다.

개체 당 고정 된 오버 헤드가 있습니다. JVM에 따라 다르지만 일반적으로 40 바이트를 추정합니다. 그런 다음 학급의 구성원을 봐야합니다. 개체 참조는 32 비트 (64 비트) JVM에서 4 (8) 바이트입니다. 기본 유형은 다음과 같습니다.

  • 부울 및 바이트 : 1 바이트
  • char 및 short : 2 바이트
  • int 및 float : 4 바이트
  • long 및 double : 8 바이트

배열은 동일한 규칙을 따릅니다. 즉, 객체 참조이므로 객체에서 4 (또는 8) 바이트를 취한 다음 길이에 해당 요소의 크기를 곱합니다.

Runtime.freeMemory()가비지 수집기에 대한 비동기 호출 등으로 인해에 대한 호출로 프로그래밍 방식으로 수행하려고 시도 하면 정확도가 크게 향상되지 않습니다. -Xrunhprof 또는 기타 도구를 사용하여 힙을 프로파일 링하면 가장 정확한 결과를 얻을 수 있습니다.


java.lang.instrument.Instrumentation클래스는 Java Object의 크기를 가져 오는 좋은 방법을 제공하지만 premainJava 에이전트를 사용하여 프로그램 을 정의 하고 실행해야합니다. 에이전트가 필요하지 않고 애플리케이션에 더미 Jar 에이전트를 제공해야 할 때 이것은 매우 지루합니다.

그래서 사용하여 대체 솔루션 얻었다 Unsafe으로부터 클래스를 sun.misc. 따라서 프로세서 아키텍처에 따른 객체 힙 정렬을 고려하고 최대 필드 오프셋을 계산하면 Java Object의 크기를 측정 할 수 있습니다. 아래 예제에서는 보조 클래스 UtilUnsafe사용 하여 sun.misc.Unsafe객체에 대한 참조를 얻습니다 .

private static final int NR_BITS = Integer.valueOf(System.getProperty("sun.arch.data.model"));
private static final int BYTE = 8;
private static final int WORD = NR_BITS/BYTE;
private static final int MIN_SIZE = 16; 

public static int sizeOf(Class src){
    //
    // Get the instance fields of src class
    // 
    List<Field> instanceFields = new LinkedList<Field>();
    do{
        if(src == Object.class) return MIN_SIZE;
        for (Field f : src.getDeclaredFields()) {
            if((f.getModifiers() & Modifier.STATIC) == 0){
                instanceFields.add(f);
            }
        }
        src = src.getSuperclass();
    }while(instanceFields.isEmpty());
    //
    // Get the field with the maximum offset
    //  
    long maxOffset = 0;
    for (Field f : instanceFields) {
        long offset = UtilUnsafe.UNSAFE.objectFieldOffset(f);
        if(offset > maxOffset) maxOffset = offset; 
    }
    return  (((int)maxOffset/WORD) + 1)*WORD; 
}
class UtilUnsafe {
    public static final sun.misc.Unsafe UNSAFE;

    static {
        Object theUnsafe = null;
        Exception exception = null;
        try {
            Class<?> uc = Class.forName("sun.misc.Unsafe");
            Field f = uc.getDeclaredField("theUnsafe");
            f.setAccessible(true);
            theUnsafe = f.get(uc);
        } catch (Exception e) { exception = e; }
        UNSAFE = (sun.misc.Unsafe) theUnsafe;
        if (UNSAFE == null) throw new Error("Could not obtain access to sun.misc.Unsafe", exception);
    }
    private UtilUnsafe() { }
}

도있다 메모리 측정기의 (이전에 도구 Google 코드 지금, GitHub의 간단하고 상업 친화적 인 출판이다,) 아파치 2.0 라이선스 (A)에 설명 된대로 비슷한 질문은 .

또한 메모리 바이트 소비를 측정하려면 Java 인터프리터에 대한 명령 줄 인수가 필요하지만 그렇지 않으면 적어도 내가 사용한 시나리오에서는 제대로 작동하는 것 같습니다.


다음은 압축 된 OOP로 32 비트, 64 비트 및 64 비트를 처리하기 위해 링크 된 예제 중 일부를 사용하여 만든 유틸리티입니다. 그것은 sun.misc.Unsafe.

Unsafe.addressSize()네이티브 포인터 Unsafe.arrayIndexScale( Object[].class )의 크기와 Java 참조 의 크기를 가져 오는 데 사용 합니다 .

알려진 클래스의 필드 오프셋을 사용하여 개체의 기본 크기를 계산합니다.

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.IdentityHashMap;
import java.util.Stack;
import sun.misc.Unsafe;

/** Usage: 
 * MemoryUtil.sizeOf( object )
 * MemoryUtil.deepSizeOf( object )
 * MemoryUtil.ADDRESS_MODE
 */
public class MemoryUtil
{
    private MemoryUtil()
    {
    }

    public static enum AddressMode
    {
        /** Unknown address mode. Size calculations may be unreliable. */
        UNKNOWN,
        /** 32-bit address mode using 32-bit references. */
        MEM_32BIT,
        /** 64-bit address mode using 64-bit references. */
        MEM_64BIT,
        /** 64-bit address mode using 32-bit compressed references. */
        MEM_64BIT_COMPRESSED_OOPS
    }

    /** The detected runtime address mode. */
    public static final AddressMode ADDRESS_MODE;

    private static final Unsafe UNSAFE;

    private static final long ADDRESS_SIZE; // The size in bytes of a native pointer: 4 for 32 bit, 8 for 64 bit
    private static final long REFERENCE_SIZE; // The size of a Java reference: 4 for 32 bit, 4 for 64 bit compressed oops, 8 for 64 bit
    private static final long OBJECT_BASE_SIZE; // The minimum size of an Object: 8 for 32 bit, 12 for 64 bit compressed oops, 16 for 64 bit
    private static final long OBJECT_ALIGNMENT = 8;

    /** Use the offset of a known field to determine the minimum size of an object. */
    private static final Object HELPER_OBJECT = new Object() { byte b; };


    static
    {
        try
        {
            // Use reflection to get a reference to the 'Unsafe' object.
            Field f = Unsafe.class.getDeclaredField( "theUnsafe" );
            f.setAccessible( true );
            UNSAFE = (Unsafe) f.get( null );

            OBJECT_BASE_SIZE = UNSAFE.objectFieldOffset( HELPER_OBJECT.getClass().getDeclaredField( "b" ) );

            ADDRESS_SIZE = UNSAFE.addressSize();
            REFERENCE_SIZE = UNSAFE.arrayIndexScale( Object[].class );

            if( ADDRESS_SIZE == 4 )
            {
                ADDRESS_MODE = AddressMode.MEM_32BIT;
            }
            else if( ADDRESS_SIZE == 8 && REFERENCE_SIZE == 8 )
            {
                ADDRESS_MODE = AddressMode.MEM_64BIT;
            }
            else if( ADDRESS_SIZE == 8 && REFERENCE_SIZE == 4 )
            {
                ADDRESS_MODE = AddressMode.MEM_64BIT_COMPRESSED_OOPS;
            }
            else
            {
                ADDRESS_MODE = AddressMode.UNKNOWN;
            }
        }
        catch( Exception e )
        {
            throw new Error( e );
        }
    }


    /** Return the size of the object excluding any referenced objects. */
    public static long shallowSizeOf( final Object object )
    {
        Class<?> objectClass = object.getClass();
        if( objectClass.isArray() )
        {
            // Array size is base offset + length * element size
            long size = UNSAFE.arrayBaseOffset( objectClass )
                    + UNSAFE.arrayIndexScale( objectClass ) * Array.getLength( object );
            return padSize( size );
        }
        else
        {
            // Object size is the largest field offset padded out to 8 bytes
            long size = OBJECT_BASE_SIZE;
            do
            {
                for( Field field : objectClass.getDeclaredFields() )
                {
                    if( (field.getModifiers() & Modifier.STATIC) == 0 )
                    {
                        long offset = UNSAFE.objectFieldOffset( field );
                        if( offset >= size )
                        {
                            size = offset + 1; // Field size is between 1 and PAD_SIZE bytes. Padding will round up to padding size.
                        }
                    }
                }
                objectClass = objectClass.getSuperclass();
            }
            while( objectClass != null );

            return padSize( size );
        }
    }


    private static final long padSize( final long size )
    {
        return (size + (OBJECT_ALIGNMENT - 1)) & ~(OBJECT_ALIGNMENT - 1);
    }


    /** Return the size of the object including any referenced objects. */
    public static long deepSizeOf( final Object object )
    {
        IdentityHashMap<Object,Object> visited = new IdentityHashMap<Object,Object>();
        Stack<Object> stack = new Stack<Object>();
        if( object != null ) stack.push( object );

        long size = 0;
        while( !stack.isEmpty() )
        {
            size += internalSizeOf( stack.pop(), stack, visited );
        }
        return size;
    }


    private static long internalSizeOf( final Object object, final Stack<Object> stack, final IdentityHashMap<Object,Object> visited )
    {
        // Scan for object references and add to stack
        Class<?> c = object.getClass();
        if( c.isArray() && !c.getComponentType().isPrimitive() )
        {
            // Add unseen array elements to stack
            for( int i = Array.getLength( object ) - 1; i >= 0; i-- )
            {
                Object val = Array.get( object, i );
                if( val != null && visited.put( val, val ) == null )
                {
                    stack.add( val );
                }
            }
        }
        else
        {
            // Add unseen object references to the stack
            for( ; c != null; c = c.getSuperclass() )
            {
                for( Field field : c.getDeclaredFields() )
                {
                    if( (field.getModifiers() & Modifier.STATIC) == 0 
                            && !field.getType().isPrimitive() )
                    {
                        field.setAccessible( true );
                        try
                        {
                            Object val = field.get( object );
                            if( val != null && visited.put( val, val ) == null )
                            {
                                stack.add( val );
                            }
                        }
                        catch( IllegalArgumentException e )
                        {
                            throw new RuntimeException( e );
                        }
                        catch( IllegalAccessException e )
                        {
                            throw new RuntimeException( e );
                        }
                    }
                }
            }
        }

        return shallowSizeOf( object );
    }
}

계측 등을 엉망으로 만들 필요없이 객체의 정확한 바이트 크기를 알 필요가없는 경우 다음 접근 방식을 사용할 수 있습니다.

System.gc();
Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();

do your job here

System.gc();
Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();

이렇게하면 사용 전과 후에 사용 된 메모리를 읽고 사용 된 메모리를 얻기 직전에 GC를 호출하여 "노이즈"를 거의 0으로 낮 춥니 다.

보다 안정적인 결과를 얻으려면 작업을 n 번 실행 한 다음 사용 된 메모리를 n으로 나누어 한 번 실행하는 데 필요한 메모리 양을 얻을 수 있습니다. 더욱이 모든 것을 더 많이 실행하고 평균을 낼 수 있습니다.


그것이 당신이 요구하는 것이라면 메소드 호출이 없습니다. 약간의 조사를 통해 직접 작성할 수 있다고 생각합니다. 특정 인스턴스는 참조 수와 원시 값과 인스턴스 부기 데이터에서 파생 된 고정 된 크기를 갖습니다. 당신은 단순히 개체 그래프를 걸을 것입니다. 행 유형이 덜 다양할수록 더 쉽습니다.

너무 느리거나 가치가있는 것보다 문제가 더 많다면, 항상 좋은 구식 행 계산 규칙이 있습니다.


즉석에서 추정하기 위해 빠른 테스트를 한 번 작성했습니다.

public class Test1 {

    // non-static nested
    class Nested { }

    // static nested
    static class StaticNested { }

    static long getFreeMemory () {
        // waits for free memory measurement to stabilize
        long init = Runtime.getRuntime().freeMemory(), init2;
        int count = 0;
        do {
            System.out.println("waiting..." + init);
            System.gc();
            try { Thread.sleep(250); } catch (Exception x) { }
            init2 = init;
            init = Runtime.getRuntime().freeMemory();
            if (init == init2) ++ count; else count = 0;
        } while (count < 5);
        System.out.println("ok..." + init);
        return init;
    }

    Test1 () throws InterruptedException {

        Object[] s = new Object[10000];
        Object[] n = new Object[10000];
        Object[] t = new Object[10000];

        long init = getFreeMemory();

        //for (int j = 0; j < 10000; ++ j)
        //    s[j] = new Separate();

        long afters = getFreeMemory();

        for (int j = 0; j < 10000; ++ j)
            n[j] = new Nested();

        long aftersn = getFreeMemory();

        for (int j = 0; j < 10000; ++ j)
            t[j] = new StaticNested();

        long aftersnt = getFreeMemory();

        System.out.println("separate:      " + -(afters - init) + " each=" + -(afters - init) / 10000);
        System.out.println("nested:        " + -(aftersn - afters) + " each=" + -(aftersn - afters) / 10000);
        System.out.println("static nested: " + -(aftersnt - aftersn) + " each=" + -(aftersnt - aftersn) / 10000);

    }

    public static void main (String[] args) throws InterruptedException {
        new Test1();
    }

}

일반적인 개념은 개체를 할당하고 사용 가능한 힙 공간의 변화를 측정하는 것입니다. 키는 GC 실행getFreeMemory()요청하고보고 된 여유 힙 크기가 안정화 될 때까지 기다립니다 . 위의 출력은 다음과 같습니다.

nested:        160000 each=16
static nested: 160000 each=16

정렬 동작과 가능한 힙 블록 헤더 오버 헤드를 고려할 때 이것이 우리가 기대하는 것입니다.

여기에서 허용되는 답변에 자세히 설명 된 계측 방법이 가장 정확합니다. 내가 설명한 방법은 정확하지만 다른 스레드가 객체를 생성 / 삭제하지 않는 제어 된 조건에서만 가능합니다.


Java Visual VM을 사용하십시오.

메모리 문제를 프로파일 링하고 디버깅하는 데 필요한 모든 것이 있습니다.

또한 많은 유용한 작업을 수행 할 수있는 OQL (Object Query Language) 콘솔이 있습니다. sizeof(o)


다음 요구 사항을 충족하는 개체 크기의 런타임 계산을 찾고있었습니다.

  • 계측을 포함 할 필요없이 런타임에 사용할 수 있습니다.
  • Unsafe에 대한 액세스없이 Java 9 이상에서 작동합니다.
  • 클래스만을 기반으로합니다. 문자열 길이, 배열 길이 등을 고려하는 깊은 sizeOf가 아닙니다.

다음은 원본 자바 전문가 기사 ( https://www.javaspecialists.eu/archive/Issue078.html ) 의 핵심 코드 와이 질문에 대한 또 다른 답변의 안전하지 않은 버전의 몇 비트를 기반으로합니다.

누군가가 유용하다고 생각하기를 바랍니다.

public class JavaSize {

private static final int NR_BITS = Integer.valueOf(System.getProperty("sun.arch.data.model"));
private static final int BYTE = 8;
private static final int WORD = NR_BITS / BYTE;
private static final int HEADER_SIZE = 8;

public static int sizeOf(Class<?> clazz) {
    int result = 0;

    while (clazz != null) {
        Field[] fields = clazz.getDeclaredFields();
        for (int i = 0; i < fields.length; i++) {
            if (!Modifier.isStatic(fields[i].getModifiers())) {
                if (fields[i].getType().isPrimitive()) {
                    Class<?> primitiveClass = fields[i].getType();
                    if (primitiveClass == boolean.class || primitiveClass == byte.class) {
                        result += 1;
                    } else if (primitiveClass == short.class) {
                        result += 2;
                    } else if (primitiveClass == int.class || primitiveClass == float.class) {
                        result += 4;
                    } else if (primitiveClass == double.class || primitiveClass == long.class) {
                        result += 8;
                    }

                } else {
                    // assume compressed references.
                    result += 4;
                }
            }
        }

        clazz = clazz.getSuperclass();

        // round up to the nearest WORD length.
        if ((result % WORD) != 0) {
            result += WORD - (result % WORD);
        }
    }

    result += HEADER_SIZE;

    return result;
}

}


내 대답은 Nick이 제공 한 코드를 기반으로합니다. 이 코드는 직렬화 된 객체가 차지하는 총 바이트 수를 측정합니다. 그래서 이것은 실제로 직렬화 물건 + 일반 객체 메모리 공간을 측정합니다 (예를 들어 직렬화 int하면 직렬화 된 바이트의 총량이 아닌 것을 알 수 있습니다 4). 따라서 객체에 정확히 사용되는 원시 바이트 번호를 얻으려면 해당 코드를 약간 수정해야합니다. 이렇게 :

import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class ObjectSizeCalculator {
    private Object getFirstObjectReference(Object o) {
        String objectType = o.getClass().getTypeName();

        if (objectType.substring(objectType.length()-2).equals("[]")) {
            try {
                if (objectType.equals("java.lang.Object[]"))
                    return ((Object[])o)[0];
                else if (objectType.equals("int[]"))
                    return ((int[])o)[0];
                else
                    throw new RuntimeException("Not Implemented !");
            } catch (IndexOutOfBoundsException e) {
                return null;
            }
        }

        return o;
    } 

    public int getObjectSizeInBytes(Object o) {
        final String STRING_JAVA_TYPE_NAME = "java.lang.String";

        if (o == null)
            return 0;

        String objectType = o.getClass().getTypeName();
        boolean isArray = objectType.substring(objectType.length()-2).equals("[]");

        Object objRef = getFirstObjectReference(o);
        if (objRef != null && !(objRef instanceof Serializable))
            throw new RuntimeException("Object must be serializable for measuring it's memory footprint using this method !");

        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(o);
            oos.close();
            byte[] bytes = baos.toByteArray();

            for (int i = bytes.length - 1, j = 0; i != 0; i--, j++) {
                if (objectType != STRING_JAVA_TYPE_NAME) {
                    if (bytes[i] == 112)
                        if (isArray)
                            return j - 4;
                        else
                            return j;
                } else {
                    if (bytes[i] == 0)
                        return j - 1;
                }
            }
        } catch (Exception e) {
            return -1;
        }

        return -1;
    }    

}

이 솔루션을 기본 유형, 문자열 및 일부 사소한 클래스로 테스트했습니다. 보장되지 않는 경우도있을 수 있습니다.


업데이트 : 배열 객체의 메모리 풋 프린트 계산을 지원하도록 수정 된 예제.


힙 덤프 (예 : jmap 사용)를 생성 한 다음 출력을 분석하여 객체 크기를 찾을 수 있습니다. 이것은 오프라인 솔루션이지만 얕고 깊은 크기 등을 검사 할 수 있습니다.


long heapSizeBefore = Runtime.getRuntime().totalMemory();

// Code for object construction
...
long heapSizeAfter = Runtime.getRuntime().totalMemory();
long size = heapSizeAfter - heapSizeBefore;

size는 객체 생성으로 인한 jvm의 메모리 사용량 증가를 제공하며 일반적으로 객체의 크기입니다.


이 대답은 객체 크기와 관련이 없지만 객체를 수용하기 위해 배열을 사용할 때입니다. 객체에 할당 할 메모리 크기입니다.

따라서 이러한 모든 컬렉션을 배열, 나열 또는 매핑하면 실제로 개체를 저장하지 않고 (기본 요소의 경우에만 실제 개체 메모리 크기가 필요함) 해당 개체에 대한 참조 만 저장합니다.

이제 Used heap memory = sizeOfObj + sizeOfRef (* 4 bytes) in collection

  • (4/8 바이트) (32/64 비트) OS에 따라 다름

기초 요소

int   [] intArray    = new int   [1]; will require 4 bytes.
long  [] longArray   = new long  [1]; will require 8 bytes.

사물

Object[] objectArray = new Object[1]; will require 4 bytes. The object can be any user defined Object.
Long  [] longArray   = new Long  [1]; will require 4 bytes.

모든 개체 REFERENCE에는 4 바이트의 메모리 만 필요합니다. 문자열 참조 또는 이중 개체 참조 일 수 있지만 개체 생성에 따라 필요한 메모리가 달라집니다.

예) 아래 클래스에 대한 객체를 생성하면 ReferenceMemoryTest4 + 4 + 4 = 12 바이트의 메모리가 생성됩니다. 참조를 초기화하려고 할 때 메모리가 다를 수 있습니다.

 class ReferenceMemoryTest {
    public String refStr;
    public Object refObj;
    public Double refDoub; 
}

따라서 객체 / 참조 배열을 만들 때 모든 내용이 NULL 참조로 채워집니다. 그리고 우리는 각 참조에 4 바이트가 필요하다는 것을 알고 있습니다.

마지막으로 아래 코드의 메모리 할당은 20 바이트입니다.

ReferenceMemoryTest ref1 = 새로운 ReferenceMemoryTest (); (4 (ref1) + 12 = 16 바이트) ReferenceMemoryTest ref2 = ref1; (4 (ref2) + 16 = 20 바이트)


다음 Complex과 같은 클래스를 선언한다고 가정합니다 .

public class Complex {

    private final long real;
    private final long imaginary;

    // omitted
}

이 클래스의 라이브 인스턴스에 할당 된 메모리 양을 확인하려면 :

$ jmap -histo:live <pid> | grep Complex

 num     #instances         #bytes  class name (module)
-------------------------------------------------------
 327:             1             32  Complex

JSONObject의 경우 아래 코드가 도움이 될 수 있습니다.

`JSONObject.toString().getBytes("UTF-8").length`

크기를 바이트 단위로 반환합니다.

파일에 작성하여 JSONArray 객체로 확인했습니다. 개체 크기를 제공합니다.


한 번만 수행하고 나중에 사용하기 위해 저장하지 않는 한 프로그래밍 방식으로 수행하고 싶지는 않습니다. 비용이 많이 드는 일입니다. Java에는 sizeof () 연산자가 없으며, 존재하더라도 다른 객체에 대한 참조 비용과 기본 요소의 크기 만 계산합니다.

할 수있는 한 가지 방법은 파일에 직렬화하고 다음과 같이 파일의 크기를 보는 것입니다.

Serializable myObject;
ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("obj.ser"));
oos.write (myObject);
oos.close ();

물론 이것은 각 객체가 구별되고 다른 것에 대한 비 일시적 참조를 포함하지 않는다고 가정합니다.

또 다른 전략은 각 개체를 반사하여 해당 멤버를 검사하고 크기 (부울 & 바이트 = 1 바이트, short & char = 2 바이트 등)를 더하여 멤버십 계층 ​​구조 아래로 작업하는 것입니다. 그러나 이는 지루하고 비용이 많이 들고 결국 직렬화 전략이 수행하는 것과 동일한 작업을 수행하게됩니다.

참고 URL : https://stackoverflow.com/questions/52353/in-java-what-is-the-best-way-to-determine-the-size-of-an-object

반응형