Java 용 Sizeof

2003 년 12 월 26 일

Q : Java에는 C에서 sizeof ()와 같은 연산자가 있습니까?

A : 피상적 인 대답은 Java가 C와 같은 것을 제공하지 않는다는 것 sizeof()입니다. 그러나 Java 프로그래머가 때때로 원하는 이유를 고려해 보겠습니다 .

AC 프로그래머는 대부분의 데이터 구조 메모리 할당을 직접 관리하며 할당 할 sizeof()메모리 블록 크기를 아는 데 필수적입니다. 또한 C 메모리 할당자는 malloc()객체 초기화에 관한 한 거의 아무것도하지 않습니다. 프로그래머는 추가 객체에 대한 포인터 인 모든 객체 필드를 설정해야합니다. 그러나 모든 것을 말하고 코딩하면 C / C ++ 메모리 할당이 매우 효율적입니다.

이에 비해 Java 객체 할당과 구성은 함께 연결되어 있습니다 (할당되었지만 초기화되지 않은 객체 인스턴스를 사용하는 것은 불가능합니다). Java 클래스가 추가 객체에 대한 참조 인 필드를 정의하는 경우 생성시 설정하는 것도 일반적입니다. 따라서 Java 객체를 할당하면 상호 연결된 수많은 객체 인스턴스 인 객체 그래프가 자주 할당됩니다. 자동 가비지 콜렉션과 결합하면이 모든 것이 너무 편리하며 Java 메모리 할당 세부 사항에 대해 걱정할 필요가 없다고 느낄 수 있습니다.

물론 이것은 단순한 Java 애플리케이션에서만 작동합니다. C / C ++와 비교할 때 동등한 Java 데이터 구조는 더 많은 물리적 메모리를 차지하는 경향이 있습니다. 엔터프라이즈 소프트웨어 개발에서 오늘날의 32 비트 JVM에서 사용 가능한 최대 가상 메모리에 근접하는 것은 일반적인 확장 성 제약입니다. 따라서 Java 프로그래머는 sizeof()자신의 데이터 구조가 너무 커지거나 메모리 병목 현상이 있는지 주시하는 데 도움이 될 수 있습니다 . 다행히 Java 리플렉션을 사용하면 이러한 도구를 매우 쉽게 작성할 수 있습니다.

계속하기 전에이 기사의 질문에 대해 자주 있지만 잘못된 답변을 생략하겠습니다.

오류 : Java 기본 유형의 크기가 고정되어 있으므로 Sizeof ()가 필요하지 않습니다.

예, Java int는 모든 JVM 및 모든 플랫폼에서 32 비트이지만 이는 프로그래머가 인식 할 수있는 이 데이터 유형의 너비에 대한 언어 사양 요구 사항 일뿐 입니다. 이러한는 int본질적으로 추상적 인 데이터 유형이며 64 비트 시스템의 64 비트 물리적 메모리 단어로 백업 할 수 있습니다. 원시 유형이 아닌 경우에도 마찬가지입니다. Java 언어 사양에는 클래스 필드가 물리적 메모리에서 정렬되어야하는 방법 또는 부울 배열이 JVM 내부에서 컴팩트 비트 벡터로 구현 될 수 없다는 내용이 없습니다.

오류 : 객체를 바이트 스트림으로 직렬화하고 결과 스트림 길이를 확인하여 객체의 크기를 측정 할 수 있습니다.

이것이 작동하지 않는 이유는 직렬화 레이아웃이 실제 메모리 내 레이아웃의 원격 반영 일 뿐이 기 때문입니다. 쉽게 확인할 수있는 한 가지 방법은 Strings가 직렬화 되는 방법을 살펴 보는 것입니다 . 메모리에서 every char는 최소 2 바이트이지만 직렬화 된 형식에서는 Strings가 UTF-8로 인코딩되므로 모든 ASCII 콘텐츠는 절반의 공간을 차지합니다.

또 다른 작업 방식

"Java Tip 130 : 데이터 크기를 알고 있습니까?"를 떠 올릴 수 있습니다. 많은 수의 동일한 클래스 인스턴스를 만들고 그에 따른 JVM 사용 힙 크기의 증가를 신중하게 측정하는 기술을 설명했습니다. 적용 가능한 경우이 아이디어는 매우 잘 작동하며 실제로이 아이디어를 사용하여이 기사의 대체 접근 방식을 부트 스트랩합니다.

Java Tip 130의 Sizeof클래스에는 대기 JVM (힙 활동이 측정 스레드에서 요청한 개체 할당 및 가비지 수집으로 인한 것임)이 필요하며 많은 수의 동일한 개체 인스턴스가 필요합니다. 이는 단일 대형 오브젝트의 크기를 지정하려는 경우 (아마도 디버그 추적 출력의 일부로), 특히 실제로 그렇게 크게 만든 것을 조사하려는 경우에는 작동하지 않습니다.

물체의 크기는 얼마입니까?

위의 논의는 철학적 요점을 강조합니다. 일반적으로 객체 그래프를 다루는 경우 객체 크기의 정의는 무엇입니까? 검사하는 개체 인스턴스의 크기일까요 아니면 개체 인스턴스를 기반으로하는 전체 데이터 그래프의 크기일까요? 후자는 일반적으로 실제로 더 중요한 것입니다. 보시다시피 상황이 항상 명확하지는 않지만 우선 다음 접근 방식을 따를 수 있습니다.

  • 객체 인스턴스는 모든 비 정적 데이터 필드 (수퍼 클래스에 정의 된 필드 포함)를 합산하여 (대략) 크기를 조정할 수 있습니다.
  • 예를 들어 C ++와 달리 클래스 메서드 및 해당 가상 성은 개체 크기에 영향을주지 않습니다.
  • 클래스 슈퍼 인터페이스는 객체 크기에 영향을주지 않습니다 (이 목록 끝에있는 참고 참조).
  • 전체 개체 크기는 시작 개체에 뿌리를 둔 전체 개체 그래프에 대한 클로저로 얻을 수 있습니다.
참고 : Java 인터페이스를 구현하는 것은 해당 클래스를 표시 할뿐 정의에 데이터를 추가하지 않습니다. 사실, JVM은 인터페이스 구현이 인터페이스에 필요한 모든 메소드를 제공하는지 확인하지도 않습니다. 이것은 엄격하게 현재 사양에서 컴파일러의 책임입니다.

프로세스를 부트 스트랩하기 위해 기본 데이터 유형의 경우 Java Tip 130의 Sizeof클래스에서 측정 한 물리적 크기를 사용 합니다. 밝혀진 바와 같이, 일반적인 32 비트 JVM의 경우 일반 java.lang.Object은 8 바이트를 차지하고 기본 데이터 유형은 일반적으로 언어 요구 사항을 수용 할 수있는 최소 물리적 크기입니다 ( boolean전체 바이트를 차지하는 경우 제외 ).

// java.lang.Object 쉘 크기 (바이트) : public static final int OBJECT_SHELL_SIZE = 8; public static final int OBJREF_SIZE = 4; public static final int LONG_FIELD_SIZE = 8; public static final int INT_FIELD_SIZE = 4; public static final int SHORT_FIELD_SIZE = 2; public static final int CHAR_FIELD_SIZE = 2; public static final int BYTE_FIELD_SIZE = 1; public static final int BOOLEAN_FIELD_SIZE = 1; public static final int DOUBLE_FIELD_SIZE = 8; public static final int FLOAT_FIELD_SIZE = 4;

(이러한 상수는 영구적으로 하드 코딩되지 않으며 주어진 JVM에 대해 독립적으로 측정되어야 함을 인식하는 것이 중요합니다.) 물론 객체 필드 크기의 순진한 합계는 JVM의 메모리 정렬 문제를 무시합니다. 메모리 정렬은 중요하지만 (예를 들어 Java Tip 130의 기본 배열 유형에 대해 표시된대로) 이러한 낮은 수준의 세부 정보를 추적하는 것은 수익성이 없다고 생각합니다. 이러한 세부 사항은 JVM 공급 업체에 따라 달라질뿐만 아니라 프로그래머의 제어를받지 않습니다. 우리의 목표는 객체의 크기를 잘 추측하고 클래스 필드가 중복 될 때 단서를 얻는 것입니다. 또는 필드가 느리게 채워 져야 할 때; 또는보다 간결한 중첩 데이터 구조가 필요한 경우 등. 절대적인 물리적 정밀도를 위해 항상 SizeofJava Tip 130 의 클래스 로 돌아갈 수 있습니다 .

객체 인스턴스를 구성하는 요소를 프로파일 링하는 데 도움을주기 위해 도구는 크기를 계산할뿐만 아니라 유용한 데이터 구조를 부산물로 구축합니다 IObjectProfileNode.

interface IObjectProfileNode {개체 개체 (); 문자열 이름 (); int 크기 (); int refcount (); IObjectProfileNode 부모 (); IObjectProfileNode [] 자식 (); IObjectProfileNode 쉘 (); IObjectProfileNode [] 경로 (); IObjectProfileNode 루트 (); int 경로 길이 (); 부울 트래버스 (INodeFilter 필터, INodeVisitor 방문자); 문자열 덤프 (); } // 인터페이스 끝

IObjectProfileNodes는 원래 객체 그래프와 거의 동일한 방식으로 상호 연결되어 IObjectProfileNode.object()각 노드가 나타내는 실제 객체 를 반환합니다. IObjectProfileNode.size()해당 노드의 개체 인스턴스를 루트로하는 개체 하위 트리의 총 크기 (바이트)를 반환합니다. 개체 인스턴스가 null이 아닌 인스턴스 필드 또는 배열 필드 내에 포함 된 참조를 통해 다른 개체에 연결되는 경우 IObjectProfileNode.children()크기가 줄어드는 순서로 정렬 된 하위 그래프 노드의 해당 목록이됩니다. 반대로 시작 노드가 아닌 모든 노드에 IObjectProfileNode.parent()대해 부모를 반환합니다. IObjectProfileNode따라서 s 의 전체 컬렉션은 원본 객체를 슬라이스 및 깍둑 썰기하고 데이터 스토리지가 그 안에서 분할되는 방식을 보여줍니다. 또한 그래프 노드 이름은 클래스 필드에서 파생되며 그래프 내에서 노드의 경로를 검사합니다 (IObjectProfileNode.path())를 사용하면 원본 개체 인스턴스에서 내부 데이터로의 소유권 링크를 추적 할 수 있습니다.

이전 단락을 읽으면서 지금까지 아이디어가 여전히 모호하다는 것을 알 수 있습니다. 객체 그래프를 순회하는 동안 동일한 객체 인스턴스가 두 번 이상 발견되면 (즉, 그래프의 어딘가에 둘 이상의 필드가이를 가리키고 있음) 소유권 (상위 포인터)을 어떻게 할당합니까? 다음 코드 스 니펫을 고려하십시오.

 Object obj = new String [] {new String ( "JavaWorld"), new String ( "JavaWorld")}; 

Each java.lang.String instance has an internal field of type char[] that is the actual string content. The way the String copy constructor works in Java 2 Platform, Standard Edition (J2SE) 1.4, both String instances inside the above array will share the same char[] array containing the {'J', 'a', 'v', 'a', 'W', 'o', 'r', 'l', 'd'} character sequence. Both strings own this array equally, so what should you do in cases like this?

If I always want to assign a single parent to a graph node, then this problem has no universally perfect answer. However, in practice, many such object instances could be traced back to a single "natural" parent. Such a natural sequence of links is usually shorter than the other, more circuitous routes. Think about data pointed to by instance fields as belonging more to that instance than to anything else. Think about entries in an array as belonging more to that array itself. Thus, if an internal object instance can be reached via several paths, we choose the shortest path. If we have several paths of equal lengths, well, we just pick the first discovered one. In the worst case, this is as good a generic strategy as any.

그래프 순회 및 최단 경로에 대해 생각하면이 시점에서 종소리가 울려 야합니다. 폭 우선 검색은 시작 노드에서 도달 가능한 다른 그래프 노드까지의 최단 경로를 찾도록 보장하는 그래프 순회 알고리즘입니다.

이러한 모든 예비 단계 이후에 이러한 그래프 순회를 구현 한 교과서가 있습니다. (일부 세부 사항 및 보조 방법은 생략되었습니다. 자세한 내용은이 기사의 다운로드를 참조하십시오.) :