Linux Kernel/Libc 버전에서 Java Runtime.exec ()는 메모리와 관련하여 안전한가요?

StackOverflow https://stackoverflow.com/questions/209875

문제

직장에서 당사의 대상 플랫폼 중 하나는 Linux를 실행하는 리소스 제약 미니 서버 (Kernel 2.6.13, Old Fedora Core를 기반으로하는 사용자 정의 배포)입니다. 응용 프로그램은 Java (Sun JDK 1.6_04)로 작성되었습니다. Linux OOM 킬러는 메모리 사용이 160MB를 초과 할 때 프로세스를 킬로 구성합니다. 고 부하 중에도 응용 프로그램은 120MB 이상으로 이동하지 않으며 활성화 된 다른 기본 프로세스와 함께 OOM 한도 내에서 잘 유지됩니다.

그러나 Java Runtime.getRuntime (). exec () 메소드는 Java에서 외부 프로세스를 실행하는 표준 방법이 특히 Linux에서 불행한 구현 이는 주소 공간이 복사되기 때문에 스폰 된 아동 프로세스가 부모 프로세스와 동일한 양의 메모리를 (일시적으로) 요구하게합니다. 순 결과는 런타임을하자마자 OOM 킬러에 의해 응용 프로그램이 사망 한 것입니다. getRuntime (). exec ().

우리는 현재 별도의 기본 프로그램이 모든 외부 명령 실행을 수행 함으로써이 문제를 해결하고 소켓을 통해 해당 프로그램과 통신합니다. 이것은 최적보다 적습니다.

후에 이 문제에 대해 온라인으로 게시합니다 Copy-on-Write를 사용하여 Posix Fork () 메소드를 구현하기 때문에 "최신"버전의 Linux 버전에서는 발생하지 않아야한다는 피드백이 있습니다. 전체 주소 공간의 즉시.

내 질문은 다음과 같습니다.

  • 이것이 사실입니까?
  • 이것이 커널, LIBC 구현 또는 전적으로 다른 곳에 있습니까?
  • 어떤 버전의 커널/libc/fork ()에 대한 복사기가 무엇입니까?
도움이 되었습니까?

해결책

이것은 *Nix (및 Linux)가 시간의 새벽 이후 (또는 MMU의 새벽을 아끼지 않아) 일한 방식입니다.

*nixes에서 새로운 프로세스를 만들려면 fork ()를 호출합니다. Fork ()는 모든 메모리 매핑, 파일 디스크립터 등으로 호출 프로세스의 사본을 만듭니다. 메모리 매핑은 복사 된 쓰기가 완료되므로 (최적의 경우) 메모리가 실제로 복사되지 않으며 매핑 만 복사하지 않습니다. 다음 exec () 호출은 현재 메모리 매핑을 새 실행 파일의 메모리 매핑으로 대체합니다. 따라서 Fork ()/exec ()는 새로운 프로세스를 만드는 방법이며 이것이 JVM이 사용하는 것입니다.

경고는 바쁜 시스템에서 거대한 과정을 보유하고 있으며, 부모는 아동 exec ()가 엄청난 양의 메모리를 복사 한 원인의 원인으로 만들기 전에 잠시 동안 계속 실행될 수 있습니다. VM에서는 메모리를 많이 움직여서 더 많은 복사를 생성하는 쓰레기 수집기를 용이하게 할 수 있습니다.

"해결 방법"은 이미 수행 한 작업을 수행하거나 새로운 프로세스를 돌보는 외부 경량 프로세스를 만들거나 포크/exect to Spawn 프로세스보다 더 가벼운 접근 방식을 사용하는 것입니다 (Linux가 가지고 있지 않은 것 - 어쨌든. JVM 자체의 변경이 필요합니다). POSIX는 호출 프로세스의 메모리 매핑을 복사하지 않고도 이론적으로 구현 될 수있는 posix_spawn () 함수를 지정합니다. 그러나 Linux에서는 그렇지 않습니다.

다른 팁

글쎄, 나는 Linux의 Fork ()가 복사본을 통해 이루어지기 때문에 개인적으로 이것이 사실이라고 의심합니다.

OOM Killer는 오해로 알려진 다소 조잡한 악기로 여겨지 기 때문에 (FE, 실제로 대부분의 기억을 할당 한 프로세스를 죽일 필요는 없으며) 마지막리스 스포츠로만 사용되어야하는데 명확하지 않습니다. 160m에 발사하도록 구성된 이유.

메모리 할당에 한계를 부과하려면 Ulimit은 OOM이 아닌 친구입니다.

저의 조언은 OOM을 내버려 두거나 ulimits를 구성 하고이 문제를 잊어 버리는 것입니다.

예, 이것은 새로운 버전의 Linux (64 비트 Red Hat 5.2에 있습니다)의 경우입니다. 나는 약 18 개월 동안 느리게 달리는 하위 프로세스에 문제가 있었으며, 질문을 읽고 검증하기 위해 테스트를 실행할 때까지 문제를 해결할 수 없었습니다.

16 개의 코어가있는 32GB 상자가 있으며 -xms4g 및 -xmx8g와 같은 설정으로 JVM을 실행하고 runtime.exec ()를 사용하여 16 개의 스레드를 사용하여 하위 프로세스를 실행하면 약 20보다 빠르게 프로세스를 실행할 수 없습니다. 초당 프로세스 호출.

Linux의 간단한 "날짜"명령을 약 10,000 회 사용하십시오. 진행 상황을보기 위해 프로파일 링 코드를 추가하면 빠르게 시작되지만 시간이 지남에 따라 속도가 느려집니다.

질문을 읽은 후 메모리 설정을 -xms128m 및 -xmx128m로 낮추기로 결정했습니다. 이제 우리의 프로세스는 초당 약 80 개의 프로세스 호출로 실행됩니다. JVM 메모리 설정 만 변경되었습니다.

32 개의 스레드로 시도했을 때에도 기억이 떨어지는 방식으로 기억을 빨아들이는 것 같습니다. 여분의 메모리는 어떤 식 으로든 할당되어야하므로 스타트 업 (및 셧다운) 비용이 발생합니다.

어쨌든,이 동작 Linux 또는 JVM에서도 비활성화 할 설정이 있어야합니다.

1 : 예. 2 : 이것은 두 단계로 나뉩니다. Fork ()와 같은 모든 시스템 호출은 glibc에 의해 커널로 포장됩니다. 시스템 호출의 커널 부분은 커널/포크 3 : 모르겠습니다. 그러나 나는 당신의 커널에 그것을 가지고 있다고 확신합니다.

32 비트 박스에서 낮은 메모리가 위협받을 때 OOM 킬러가 시작됩니다. 나는 이것에 문제가 없었지만 OOM을 막을 수있는 방법이 있습니다. 이 문제는 일부 OOM 구성 문제 일 수 있습니다.

Java 응용 프로그램을 사용하고 있으므로 64 비트 Linux로 이동하는 것을 고려해야합니다. 그것은 확실히 그것을 고칠 것입니다. 대부분의 32 비트 앱은 관련 라이브러리가 설치되는 한 문제없이 64 비트 커널에서 실행될 수 있습니다.

32 비트 Fedora의 Pae 커널을 사용해 볼 수도 있습니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top