[java] 수백만 개의 작은 임시 개체를 만드는 모범 사례

수백만 개의 작은 개체를 만들고 릴리스하기위한 “모범 사례”는 무엇입니까?

Java로 체스 프로그램을 작성하고 있으며 검색 알고리즘은 가능한 각 이동에 대해 단일 “Move”개체를 생성하며 명목 검색은 초당 백만 개 이상의 이동 개체를 쉽게 생성 할 수 있습니다. JVM GC는 내 개발 시스템의로드를 처리 할 수 ​​있었지만 다음과 같은 대체 접근 방식을 탐색하는 데 관심이 있습니다.

  1. 가비지 수집의 오버 헤드를 최소화하고
  2. 저가형 시스템의 최대 메모리 사용량을 줄입니다.

대부분의 개체는 수명이 매우 짧지 만 생성 된 이동의 약 1 %가 지속되고 지속 된 값으로 반환되므로 풀링 또는 캐싱 기술은 특정 개체를 재사용에서 제외 할 수있는 기능을 제공해야합니다. .

완전한 예제 코드를 기대하지는 않지만 추가 읽기 / 연구 또는 유사한 성격의 오픈 소스 예제에 대한 제안에 감사드립니다.



답변

자세한 가비지 콜렉션으로 애플리케이션을 실행하십시오.

java -verbose:gc

그리고 그것이 수집되면 알려줄 것입니다. 빠른 스위프와 전체 스위프의 두 가지 유형이 있습니다.

[GC 325407K->83000K(776768K), 0.2300771 secs]
[GC 325816K->83372K(776768K), 0.2454258 secs]
[Full GC 267628K->83769K(776768K), 1.8479984 secs]

화살표는 크기 전후입니다.

전체 GC가 아닌 GC를 수행하는 한 집에 안전하게 있습니다. 일반 GC는 ‘젊은 세대’의 복사 수집기이므로 더 이상 참조되지 않는 객체는 단순히 잊혀지는 것입니다.

Java SE 6 HotSpot Virtual Machine Garbage Collection Tuning을 읽는 것이 도움이 될 것입니다.


답변

버전 6부터 JVM의 서버 모드는 이스케이프 분석 기술을 사용합니다. 그것을 사용하면 GC를 모두 피할 수 있습니다.


답변

글쎄, 여기에 몇 가지 질문이 있습니다!

1-수명이 짧은 개체는 어떻게 관리됩니까?

앞서 언급했듯이 JVM은 약한 세대 별 가설을 따르기 때문에 엄청난 양의 단기 객체를 완벽하게 처리 할 수 ​​있습니다 .

주 메모리 (힙)에 도달 한 개체에 대해 이야기하고 있습니다. 항상 그런 것은 아닙니다. 생성 한 많은 객체는 CPU 레지스터를 남기지 않습니다. 예를 들어,이 for 루프를 고려하십시오.

for(int i=0, i<max, i++) {
  // stuff that implies i
}

루프 언 롤링 (JVM이 코드에서 많이 수행하는 최적화)에 대해 생각하지 마십시오. 경우 maxIS가 동일 Integer.MAX_VALUE하면 루프가 실행하는 데 시간이 걸릴 수 있습니다. 그러나 i변수는 루프 블록을 벗어나지 않습니다. 따라서 JVM은 해당 변수를 CPU 레지스터에 넣고 정기적으로 증분하지만 주 메모리로 다시 보내지 않습니다.

따라서 수백만 개의 개체를 만드는 것은 로컬에서만 사용되는 경우 큰 문제가 아닙니다. 그들은 Eden에 저장되기 전에 죽을 것이므로 GC는 그들을 알아 채지 못할 것입니다.

2-GC의 오버 헤드를 줄이는 것이 유용합니까?

평소처럼 상황에 따라 다릅니다.

먼저, 무슨 일이 일어나고 있는지 명확하게 볼 수 있도록 GC 로깅을 활성화해야합니다. 다음으로 활성화 할 수 있습니다.-Xloggc:gc.log -XX:+PrintGCDetails .

애플리케이션이 GC주기에서 많은 시간을 소비하는 경우, 예, GC를 조정하십시오. 그렇지 않으면 실제로 가치가 없을 수 있습니다.

예를 들어, 100ms마다 10ms가 걸리는 젊은 GC가있는 경우 GC에서 시간의 10 %를 소비하고 초당 10 개의 컬렉션이 있습니다 (이는 huuuuuge). 이 경우 10 GC / s가 여전히 존재하므로 GC 튜닝에 시간을 소비하지 않습니다.

3-약간의 경험

엄청난 양의 주어진 클래스를 생성하는 응용 프로그램에서 비슷한 문제가 발생했습니다. GC 로그에서 응용 프로그램의 생성 속도가 약 3GB / s라는 것을 알았습니다. 이것은 너무 많은 것입니다 (초당 3GB의 데이터?!).

문제 : 너무 많은 객체가 생성되어 너무 자주 GC가 발생합니다.

필자의 경우 메모리 프로파일 러를 연결하고 클래스가 모든 개체의 막대한 비율을 나타내는 것을 발견했습니다. 인스턴스화를 추적하여이 클래스가 기본적으로 개체에 래핑 된 한 쌍의 부울이라는 것을 알아 냈습니다. 이 경우 두 가지 솔루션을 사용할 수 있습니다.

  • 한 쌍의 부울을 반환하지 않도록 알고리즘을 재 작업하지만 대신 각 부울을 개별적으로 반환하는 두 가지 메서드가 있습니다.

  • 4 개의 다른 인스턴스 만 있다는 것을 알고 객체를 캐시

두 번째는 애플리케이션에 미치는 영향이 가장 적고 도입하기 쉽기 때문에 선택했습니다. 스레드로부터 안전하지 않은 캐시가있는 팩토리를 만드는 데 몇 분이 걸렸습니다.

할당 속도는 1GB / s로 떨어졌고, 젊은 GC의 빈도도 감소했습니다 (3으로 나눈 값).

도움이 되길 바랍니다!


답변

가치 객체 (즉, 다른 객체에 대한 참조가 없음) 만 있고 실제로는 그 수가 엄청나게 많은 경우 ByteBuffers기본 바이트 순서로 직접 사용할 수 있으며 [후자는 중요합니다] 몇 백 줄의 할당 / 재사용 코드 + getter / setter. 게터는 다음과 비슷합니다.long getQuantity(int tupleIndex){return buffer.getLong(tupleInex+QUANTITY_OFFSSET);}

이렇게하면 한 번만 할당하는 한, 즉 거대한 덩어리를 직접 할당 한 다음 객체를 직접 관리하는 한 거의 전적으로 GC 문제를 해결할 수 있습니다. 참조 대신 색인 만있을 것입니다 (즉,int )ByteBuffer 있습니다. 메모리를 직접 정렬해야 할 수도 있습니다.

이 기술은 C and void* 은를 지만 일부 포장을 사용하면 견딜 수 있습니다. 성능 저하는 컴파일러가이를 제거하지 못하는지 확인하는 경계 일 수 있습니다. 주요 장점은 벡터와 같은 튜플을 처리하는 경우 지역성입니다. 객체 헤더가 없으면 메모리 사용량도 줄어 듭니다.

그 외에는 거의 모든 JVM의 젊은 세대가 사소하게 죽고 할당 비용이 포인터 범프에 불과하므로 그러한 접근 방식이 필요하지 않을 것입니다. final일부 플랫폼 (즉, ARM / Power)에서 메모리 울타리가 필요하므로 필드 를 사용하는 경우 할당 비용이 약간 더 높을 수 있지만 x86에서는 무료입니다.


답변

GC가 문제라고 가정하면 (다른 사람들이 그렇지 않을 수도 있다고 지적했듯이) 특별한 경우, 즉 막대한 변동을 겪는 클래스에 대해 자체 메모리 관리를 구현할 것입니다. 개체 풀링을 시도해보십시오. 저는 그것이 아주 잘 작동하는 경우를 보았습니다. 개체 풀을 구현하는 것은 꼼꼼한 방법이므로 여기를 다시 방문 할 필요가 없습니다.

  • 다중 스레딩 : 스레드 로컬 풀을 사용하면 귀하의 경우에 효과적 일 수 있습니다.
  • 백업 데이터 구조 : 제거시 잘 수행되고 할당 오버 헤드가 없으므로 ArrayDeque 사용을 고려하십시오.
  • 수영장 크기 제한 🙂

등 전후 측정


답변

비슷한 문제를 만났습니다. 우선 작은 물체의 크기를 줄이십시오. 각 개체 인스턴스에서이를 참조하는 몇 가지 기본 필드 값을 도입했습니다.

예를 들어 MouseEvent에는 Point 클래스에 대한 참조가 있습니다. 새 인스턴스를 만드는 대신 포인트를 캐시하고 참조했습니다. 예를 들어 빈 문자열에 대해서도 동일합니다.

또 다른 소스는 하나의 int로 대체 된 여러 부울이며 각 부울에 대해 int의 1 바이트 만 사용합니다.


답변

나는 얼마 전에 XML 처리 코드로이 시나리오를 다루었 다. 매우 작고 (일반적으로 문자열) 매우 짧고 ( XPath 실패) 수백만 개의 XML 태그 객체를 생성하는 것을 발견했습니다. 검사 일치하지 않음을 의미하므로 폐기를 의미 함 .

몇 가지 심각한 테스트를 수행 한 결과 새 태그를 만드는 대신 폐기 된 태그 목록을 사용하여 속도를 약 7 % 향상시킬 수 있다는 결론에 도달했습니다. 그러나 일단 구현되면 free queue가 너무 커지면 정리하기 위해 추가 된 메커니즘이 필요하다는 것을 발견했습니다. 이로 인해 내 최적화가 완전히 무효화되어 옵션으로 전환했습니다.

요약하면-아마 그럴 가치가 없지만-당신이 그것에 대해 생각하고 있다는 것을 알게되어 기쁩니다.