[java] Linux에서 Java의 가상 메모리 사용, 너무 많은 메모리 사용

Linux에서 실행되는 Java 응용 프로그램에 문제가 있습니다.

기본 최대 힙 크기 (64MB)를 사용하여 응용 프로그램을 시작하면 240MB의 가상 메모리가 응용 프로그램에 할당 된 tops 응용 프로그램을 사용하는 것을 볼 수 있습니다. 이로 인해 컴퓨터의 다른 소프트웨어에 문제가 발생하는데, 이는 상대적으로 자원이 제한적입니다.

우리가 힙 한계에 도달하면 a OutOfMemoryError가 발생 하기 때문에 내가 이해하는 한 예약 된 가상 메모리는 어쨌든 사용되지 않습니다 . Windows에서 동일한 응용 프로그램을 실행했는데 가상 메모리 크기와 힙 크기가 비슷하다는 것을 알았습니다.

어쨌든 Linux에서 Java 프로세스에 사용되는 가상 메모리를 구성 할 수 있습니까?

편집 1 : 문제는 힙이 아닙니다. 문제는 예를 들어 128MB의 힙을 설정하면 여전히 Linux가 210MB의 가상 메모리를 할당하므로 필요하지 않습니다. **

편집 2 : 사용 ulimit -v은 가상 메모리의 양을 제한합니다. 설정된 크기가 204MB 미만이면 204MB가 필요하지 않고 64MB 만 있어도 응용 프로그램이 실행되지 않습니다. 그래서 Java에 많은 가상 메모리가 필요한 이유를 이해하고 싶습니다. 변경할 수 있습니까?

편집 3 : 시스템에서 실행중인 다른 여러 응용 프로그램이 있습니다. 그리고 시스템에는 가상 메모리 제한이 있습니다 (주석, 중요한 세부 사항).



답변

이것은 오랫동안 Java에 대한 불만 이었지만, 의미가 없으며 일반적으로 잘못된 정보를보고 있습니다. 일반적인 문구는 “Hello World on Java는 10MB가 필요합니다! 왜 필요한가요?” 자, 여기 64 비트 JVM에서 Hello World가 적어도 한 가지 형태의 측정으로 4 기가 바이트 이상을 차지한다고 주장하는 방법이 있습니다.

java -Xms1024m -Xmx4096m com.example.Hello

메모리를 측정하는 다른 방법

Linux에서 top 명령은 여러 가지 메모리 번호를 제공합니다. Hello World 예제에 대한 내용은 다음과 같습니다.

  PID 사용자 PR NI VIRT RES SHR S % CPU % MEM TIME + 명령
 2120 kgregory 20 4373m 15m 7152 S 0 0.2 0 : 00.10 자바
  • VIRT는 가상 메모리 공간입니다. 가상 메모리 맵의 모든 항목의 합계입니다 (아래 참조). 그렇지 않은 경우를 제외하고는 크게 의미가 없습니다 (아래 참조).
  • RES는 상주 세트 크기 : 현재 RAM에 상주하는 페이지 수입니다. 거의 모든 경우에, 이것은 “너무 큰”이라고 말할 때 사용해야하는 유일한 숫자입니다. 그러나 특히 Java에 관해 이야기 할 때 여전히 좋은 숫자는 아닙니다.
  • SHR은 다른 프로세스와 공유되는 상주 메모리의 양입니다. Java 프로세스의 경우 이는 일반적으로 공유 라이브러리 및 메모리 맵핑 JAR 파일로 제한됩니다. 이 예에서는 Java 프로세스가 하나만 실행되었으므로 7k는 OS에서 사용하는 라이브러리의 결과라고 생각합니다.
  • SWAP는 기본적으로 켜져 있지 않으며 여기에 표시되지 않습니다. 실제로 스왑 공간에 있는지 여부에 관계없이 디스크에 현재 상주하는 가상 메모리의 양을 나타냅니다 . OS는 활성 페이지를 RAM에 보관하는 데 매우 적합하며 (1) 더 많은 메모리를 구매하거나 (2) 프로세스 수를 줄이는 것만으로도이 수를 무시하는 것이 가장 좋습니다.

Windows 작업 관리자의 상황은 좀 더 복잡합니다. Windows XP에는 “메모리 사용”및 “가상 메모리 크기”열이 있지만 공식 설명서 는 그 의미에 대해 침묵합니다. Windows Vista 및 Windows 7은 더 많은 열을 추가하며 실제로 문서화되어 있습니다. 이 중 “작업 세트”측정이 가장 유용합니다. Linux의 RES와 SHR의 합과 거의 같습니다.

가상 메모리 맵 이해

프로세스가 소비하는 가상 메모리는 프로세스 메모리 맵에있는 모든 것의 총합입니다. 여기에는 데이터 (예 : Java 힙)뿐만 아니라 프로그램에서 사용하는 모든 공유 라이브러리 및 메모리 매핑 파일도 포함됩니다. Linux에서는 pmap 명령을 사용하여 프로세스 공간에 매핑 된 모든 항목을 볼 수 있습니다 (여기서부터는 Linux를 참조 할 것입니다. 왜냐하면 내가 사용하는 것이므로 이에 해당하는 도구가 있다고 확신합니다). Windows). 다음은 “Hello World”프로그램의 메모리 맵에서 발췌 한 것입니다. 전체 메모리 맵의 길이는 100 줄을 넘으며 천줄 목록을 갖는 것은 드문 일이 아닙니다.

0000000040000000 36K rx-/usr/local/java/jdk-1.6-x64/bin/java
0000000040108000 8K rwx-/usr/local/java/jdk-1.6-x64/bin/java
0000000040eba000 676K rwx-- [아논]
00000006fae00000 21248K rwx-- [아논]
00000006fc2c0000 62720K rwx-- [아논]
0000000700000000 699072K rwx-- [아논]
000000072aab0000 2097152K rwx-- [anon]
00000007aaab0000 349504K rwx-- [아논]
00000007c0000000 1048576K rwx-- [아논]
...
00007fa1ed00d000 1652K r-xs- /usr/local/java/jdk-1.6-x64/jre/lib/rt.jar
...
00007fa1ed1d3000 1024K rwx-- [anon]
00007fa1ed2d3000 4K ----- [아논]
00007fa1ed2d4000 1024K rwx-- [anon]
00007fa1ed3d4000 4K ----- [아논]
...
00007fa1f20d3000 164K rx-/usr/local/java/jdk-1.6-x64/jre/lib/amd64/libjava.so
00007fa1f20fc000 1020K ----- /usr/local/java/jdk-1.6-x64/jre/lib/amd64/libjava.so
00007fa1f21fb000 28K rwx-/usr/local/java/jdk-1.6-x64/jre/lib/amd64/libjava.so
...
00007fa1f34aa000 1576K rx-/lib/x86_64-linux-gnu/libc-2.13.so
00007fa1f3634000 2044K ----- /lib/x86_64-linux-gnu/libc-2.13.so
00007fa1f3833000 16K rx-/lib/x86_64-linux-gnu/libc-2.13.so
00007fa1f3837000 4K rwx-/lib/x86_64-linux-gnu/libc-2.13.so
...

형식에 대한 간단한 설명 : 각 행은 세그먼트의 가상 메모리 주소로 시작합니다. 그런 다음 세그먼트 크기, 권한 및 세그먼트 소스가 이어집니다. 이 마지막 항목은 파일 또는 “anon”이며 mmap을 통해 할당 된 메모리 블록을 나타냅니다 .

위에서부터 우리는

  • JVM 로더 (즉, 입력 할 때 실행되는 프로그램 java) 이것은 매우 작습니다. 실제 JVM 코드가 저장된 공유 라이브러리에로드하기 만하면됩니다.
  • Java 힙 및 내부 데이터를 보유하는 많은 anon 블록. 이것은 Sun JVM이므로 힙은 여러 세대로 나뉘며 각 세대는 자체 메모리 블록입니다. JVM은 -Xmx값을 기반으로 가상 메모리 공간을 할당 합니다. 이를 통해 연속적인 힙을 가질 수 있습니다. -Xms값은 프로그램 시작 및 트리거 가비지 컬렉션에 그 한계가 접근로 할 때 “사용”얼마나 많은 힙의 말을 내부적으로 사용됩니다.
  • 메모리 맵핑 JAR 파일 (이 경우 “JDK 클래스”를 보유하는 파일) JAR을 메모리 맵핑 할 때, 매번 처음부터 파일을 읽는 것과는 달리 JAR 파일 내의 파일에 매우 효율적으로 액세스 할 수 있습니다. Sun JVM은 클래스 경로의 모든 JAR을 메모리 매핑합니다. 애플리케이션 코드가 JAR에 액세스해야하는 경우 메모리 맵핑 할 수도 있습니다.
  • 두 개의 스레드에 대한 스레드 별 데이터. 1M 블록은 스레드 스택입니다. 4k 블록에 대한 좋은 설명은 없지만 @ericsoe는이를 “guard 블록”으로 식별했습니다. 읽기 / 쓰기 권한이 없으므로 액세스하면 세그먼트 오류가 발생하고 JVM이이를 포착하여 번역합니다. 에 StackOverFlowError. 실제 응용 프로그램의 경우 수백 개의 항목이 메모리 맵을 통해 반복되지 않으면 수십 개가 표시됩니다.
  • 실제 JVM 코드를 보유하는 공유 라이브러리 중 하나입니다. 이 중 몇 가지가 있습니다.
  • C 표준 라이브러리의 공유 라이브러리 이것은 엄밀히 Java의 일부가 아닌 JVM이로드하는 많은 것 중 하나 일뿐입니다.

공유 라이브러리는 특히 흥미 롭습니다. 각 공유 라이브러리에는 라이브러리 코드가 포함 된 읽기 전용 세그먼트와 라이브러리에 대한 전역 프로세스 별 데이터를 포함하는 읽기 / 쓰기 세그먼트가 있습니다. 권한이없는 세그먼트는 x64 Linux에서만 볼 수 있습니다). 라이브러리의 읽기 전용 부분은 라이브러리를 사용하는 모든 프로세스간에 공유 할 수 있습니다. 예를 들어 libc공유 할 수있는 1.5M의 가상 메모리 공간이 있습니다.

가상 메모리 크기는 언제 중요합니까?

가상 메모리 맵에는 많은 것들이 포함되어 있습니다. 일부는 읽기 전용이며, 일부는 공유되며, 일부는 할당되었지만 절대 손대지 않습니다 (예 :이 예에서 거의 모든 4Gb 힙). 그러나 운영 체제는 필요한 것만로드하기에 충분히 영리하므로 가상 메모리 크기는 크게 관련이 없습니다.

가상 메모리 크기가 중요한 곳은 32 비트 운영 체제에서 실행중인 경우 2Gb (또는 경우에 따라 3Gb)의 프로세스 주소 공간 만 할당 할 수 있습니다. 이 경우 부족한 리소스를 처리하고 있으며 대용량 파일을 메모리 매핑하거나 많은 스레드를 만들려면 힙 크기를 줄이는 등의 절충을해야 할 수도 있습니다.

그러나 64 비트 컴퓨터가 어디에나 있기 때문에 가상 메모리 크기가 완전히 관련이없는 통계가되기까지는 오래 걸리지 않을 것이라고 생각합니다.

상주 세트 크기는 언제 중요합니까?

상주 세트 크기는 실제로 RAM에있는 가상 메모리 공간의 해당 부분입니다. RSS가 전체 실제 메모리의 상당 부분으로 성장하면 걱정할 시간입니다. RSS가 모든 실제 메모리를 차지하기 위해 성장하고 시스템이 스와핑을 시작하면 걱정할 시간이 지났습니다.

그러나 RSS는 특히 약간로드 된 시스템에서 오해의 소지가 있습니다. 운영 체제는 프로세스에서 사용하는 페이지를 회수하기 위해 많은 노력을 기울이지 않습니다. 그렇게하면 얻을 수있는 이점이 거의 없으며 프로세스가 나중에 페이지에 닿으면 페이지 오류가 발생할 가능성이 있습니다. 결과적으로 RSS 통계에는 활성화되지 않은 많은 페이지가 포함될 수 있습니다.

결론

교환하지 않는 한 다양한 메모리 통계가 알려주는 것에 지나치게 걱정하지 마십시오. 계속해서 증가하는 RSS는 일종의 메모리 누수를 나타낼 수 있다는 경고가 있습니다.

Java 프로그램을 사용하면 힙에서 발생하는 일에주의를 기울이는 것이 훨씬 중요합니다. 사용 된 총 공간이 중요하며이를 줄이기 위해 수행 할 수있는 몇 가지 단계가 있습니다. 더 중요한 것은 가비지 수집에 소요되는 시간과 힙의 어떤 부분이 수집되는지입니다.

디스크 (즉, 데이터베이스)에 액세스하는 것은 비용이 많이 들고 메모리는 저렴합니다. 서로 교환 할 수 있다면 그렇게하십시오.


답변

Java 및 glibc> = 2.10에 알려진 문제점이 있습니다 (Ubuntu> = 10.04, RHEL> = 6 포함).

치료법은이 환경을 설정하는 것입니다. 변하기 쉬운:

export MALLOC_ARENA_MAX=4

Tomcat을 실행중인 경우이를 TOMCAT_HOME/bin/setenv.sh파일에 추가 할 수 있습니다 .

Docker의 경우 Dockerfile에 추가하십시오.

ENV MALLOC_ARENA_MAX=4

MALLOC_ARENA_MAX 설정에 대한 IBM 기사
https://www.ibm.com/developerworks/community/blogs/kevgrig/entry/linux_glibc_2_10_rhel_6_malloc_may_show_excessive_virtual_memory_usage?lang=en

이 블로그 게시물은 말합니다

상주 메모리는 메모리 누수 또는 메모리 조각화와 유사한 방식으로 크립하는 것으로 알려져 있습니다.

열린 JDK 버그 JDK-8193521 “glibc는 기본 구성으로 메모리를 낭비합니다”도 있습니다

자세한 내용은 Google 또는 SO에서 MALLOC_ARENA_MAX를 검색하십시오.

할당 된 메모리의 낮은 조각화를 최적화하기 위해 다른 malloc 옵션을 조정할 수도 있습니다.

# tune glibc memory allocation, optimize for low fragmentation
# limit the number of arenas
export MALLOC_ARENA_MAX=2
# disable dynamic mmap threshold, see M_MMAP_THRESHOLD in "man mallopt"
export MALLOC_MMAP_THRESHOLD_=131072
export MALLOC_TRIM_THRESHOLD_=131072
export MALLOC_TOP_PAD_=131072
export MALLOC_MMAP_MAX_=65536


답변

Java 프로세스에 할당 된 메모리 양은 내가 기대하는 것과 거의 비슷합니다. 임베디드 / 메모리 제한 시스템에서 Java를 실행하는 데 비슷한 문제가 있습니다. 임의의 VM 제한이 있거나 적절한 양의 스왑이없는 시스템에서 응용 프로그램을 실행 하면 중단되는 경향이 있습니다. 리소스 제한 시스템에서 사용하도록 설계되지 않은 많은 최신 앱의 특성 인 것 같습니다.

JVM의 메모리 사용량을 시도하고 제한 할 수있는 몇 가지 옵션이 더 있습니다. 이렇게하면 가상 메모리 공간이 줄어들 수 있습니다.

-XX : ReservedCodeCacheSize = 32m 예약 코드 캐시 크기 (바이트)-최대 코드 캐시 크기. [Solaris 64 비트, amd64 및 -server x86 : 48m; 1.5.0_06 및 이전 버전의 Solaris 64 비트 및 and64 : 1024m.]

-XX : MaxPermSize = 64m 영구 생성 크기. [5.0 이상 : 64 비트 VM이 30 % 확장되었습니다. 1.4 AMD64 : 96m; 1.3.1-클라이언트 : 32m.]

또한 -Xmx (최대 힙 크기)를 응용 프로그램 의 실제 최대 메모리 사용량 과 가능한 한 가까운 값으로 설정 해야합니다. JVM의 기본 동작은 최대로 확장 할 때마다 힙 크기 를 두 배로 늘리는 것입니다. 32M 힙으로 시작하고 앱이 65M으로 정점에 도달하면 힙이 32M-> 64M-> 128M으로 증가합니다.

힙을 늘리는 데 VM을 덜 공격적으로 만들려고 시도 할 수도 있습니다.

-XX : MinHeapFreeRatio = 40 확장을 피하기 위해 GC 후 힙 사용 가능 최소 백분율입니다.

또한 몇 년 전에이 실험을 통해 기억 한 점에서로드 된 기본 라이브러리 수는 최소 설치 공간에 큰 영향을 미쳤습니다. java.net.Socket을로드하면 올바르게 리콜하면 15M 이상이 추가되었습니다 (아마도 그렇지 않습니다).


답변

Sun JVM에는 HotSpot에 많은 메모리가 필요하며 공유 메모리의 런타임 라이브러리에 매핑됩니다.

메모리가 문제인 경우 임베드에 적합한 다른 JVM을 사용하는 것이 좋습니다. IBM에는 j9가 있으며 GNU 클래스 경로 라이브러리를 사용하는 오픈 소스 “jamvm”이 있습니다. 또한 Sun은 Squeak JVM을 SunSPOTS에서 실행하므로 대안이 있습니다.


답변

그냥 생각,하지만 당신은의 영향을 확인할 수 있습니다 옵션을 .ulimit -v

모든 프로세스에서 사용 가능한 주소 공간을 제한하기 때문에 실제 솔루션은 아니지만 제한된 가상 메모리로 응용 프로그램의 동작을 확인할 수 있습니다.


답변

자원이 제한된 시스템의 힙 영역을 줄이는 한 가지 방법은 -XX : MaxHeapFreeRatio 변수를 사용하는 것입니다. 일반적으로 70으로 설정되며 GC가 줄어들 기 전에 사용 가능한 힙의 최대 백분율입니다. 더 낮은 값으로 설정하면 jvisualvm 프로파일 러에서 일반적으로 프로그램에 더 작은 힙 조각이 사용된다는 것을 알 수 있습니다.

편집 : -XX : MaxHeapFreeRatio에 작은 값을 설정하려면 -XX : MinHeapFreeRatio Eg도 설정해야합니다.

java -XX:MinHeapFreeRatio=10 -XX:MaxHeapFreeRatio=25 HelloWorld

EDIT2 : 동일한 작업을 시작하고 수행하는 실제 응용 프로그램에 대한 예제를 추가했습니다. 하나는 기본 매개 변수가 있고 다른 하나는 매개 변수로 10과 25입니다. 이론상 Java는 후자의 예제에서 힙을 늘리기 위해 더 많은 시간을 사용해야하지만 실제 속도 차이는 눈치 채지 못했습니다.

기본 매개 변수

결국 최대 힙은 905이고 사용 된 힙은 378입니다.

MinHeap 10, MaxHeap 25

결국 최대 힙은 722이고 사용 된 힙은 378입니다.

우리의 응용 프로그램이 원격 데스크톱 서버에서 실행되므로 많은 사용자가 한 번에 실행할 수 있기 때문에 실제로 약간의 손상이 있습니다.


답변

Sun의 java 1.4에는 메모리 크기를 제어하기위한 다음과 같은 인수가 있습니다.

-Xmsn 메모리 할당 풀의 초기 크기 (바이트)를 지정하십시오. 이 값은 1MB보다 1024의 배수 여야합니다. 킬로바이트를 표시하려면 문자 k 또는 K를 추가하고 메가 바이트를 표시하려면 m 또는 M을 추가하십시오. 기본값은 2MB입니다. 예 :

           -Xms6291456
           -Xms6144k
           -Xms6m

-Xmxn 메모리 할당 풀의 최대 크기 (바이트)를 지정하십시오. 이 값은 2MB보다 1024의 배수 여야합니다. 킬로바이트를 표시하려면 문자 k 또는 K를 추가하고 메가 바이트를 표시하려면 m 또는 M을 추가하십시오. 기본값은 64MB입니다. 예 :

           -Xmx83886080
           -Xmx81920k
           -Xmx80m

http://java.sun.com/j2se/1.4.2/docs/tooldocs/windows/java.html

Java 5와 6에는 더 많은 것이 있습니다. http://java.sun.com/javase/technologies/hotspot/vmoptions.jsp를 참조하십시오.