2009-03-09 4 views
36

이전 개발자로부터 일부 응용 프로그램을 인계받습니다. 이클립스를 통해 응용 프로그램을 실행할 때 메모리 사용량과 힙 크기가 많이 증가하는 것을 볼 수 있습니다. 추가 조사를 통해, 나는 그들이 반복적으로 객체를 반복적으로 생성한다는 것을 알았습니다.Java 메모리 관리에 대한 유용한 정보가 있습니까?

나는 약간의 청소를하고 시작했다. 그러나 내가 끝까지 갈수록 더 많은 질문을 할 수 있습니다. "이것은 실제로 무엇을 할 것인가?"

예를 들어 위에서 언급 한 루프 외부에서 변수를 선언하고 루프에서 해당 값을 설정하는 대신 ... 루프에서 개체를 만들었습니다. 내 말은있다 : 잘못된

for(int i=0; i < arrayOfStuff.size(); i++) { 
    String something = (String) arrayOfStuff.get(i); 
    ... 
} 

String something = null; 
for(int i=0; i < arrayOfStuff.size(); i++) { 
    something = (String) arrayOfStuff.get(i); 
} 

대 암의 나는 바닥 루프가 더 말을? 아마도 나는 틀렸다.

위의 두 번째 루프 이후에도 "무언가"를 다시 null로 설정해야합니까? 기억이 나지 않을까요?

두 경우 모두 내가 수행 할 수있는 좋은 메모리 관리 모범 사례는 내 응용 프로그램에서 메모리 사용량을 낮추는 데 도움이 될 것입니다.

업데이트 : 지금까지 모든 사람의 의견을 보내 주셔서 감사합니다

. 그러나, 위의 루프에 대해 실제로 묻지는 않았습니다. (귀하의 충고에 따라 첫 번째 루프로 돌아 갔지만). 나는 눈을 뗄 수없는 모범 사례를 얻으려고 노력하고있다. "당신이 콜렉션을 사용하여 끝났을 때, 그것을 지워라."라는 라인에 뭔가가 있습니다. 난 그냥 정말 많은 메모리가 이러한 응용 프로그램에 의해 촬영되고 있는지 확인해야합니다.

+0

수정 : 세 명이 지금까지 말했듯이 프로필을 작성하십시오. –

+0

몇 가지 구체적인 사례를 찾고 있으므로 프로필을 작성할 필요가 없습니다. 코드 작성, 작동, 프로파일 링, 수정을하기보다는 처음부터 적절하게 개발하고 싶습니다. – Ascalonian

+2

"구체적인 방법"은 조기 최적화를 수행하지 않고 잘 구조화 된 코드를 작성한 다음이를 최적화하여 필요한 것을 찾아내는 것입니다. –

답변

36

VM을 능가하려고 시도하지 마십시오. 첫 번째 루프는 성능 및 유지 관리 측면에서 권장되는 모범 사례입니다. 루프 이후에 참조를 다시 null로 설정해도 메모리가 즉시 릴리스되지는 않습니다. GC는 가능한 최소 범위를 사용할 때 가장 잘 수행 할 것입니다.

(사용자 관점에서 자세히)이 책들은 Effective Java 2Implementation Patterns입니다.

VM의 성능과 내부에 대해 자세히 알고 싶으면 Brian Goetz에서 대화를 보거나 책을 읽어야합니다.

+0

"가능한 최소 범위 사용 "일반적으로 가장 좋은 방법으로 간주됩니까? – KillBill

+0

일반적으로 그렇다고 생각합니다. 물론 일반화에 대한 예외가 있어야합니다. – cherouvim

9

이 두 루프는 something의 범위를 제외하고는 동일합니다. 자세한 내용은 this question을 참조하십시오.

일반적인 모범 사례? 음, 보자. 정당한 이유가없는 한 많은 양의 데이터를 정적 변수에 저장하지 마십시오. 컬렉션에서 큰 개체를 제거하면 작업을 완료 할 수 있습니다. 그리고 아 맞아, "측정, 추측하지 마라." 프로파일 러를 사용하여 메모리가 할당되는 위치를 확인하십시오.

+3

+1 "측정, 추측하지 마세요." –

5

두 루프는 기본적으로 동일한 양의 메모리를 사용하지만 차이는 무시할 수 있습니다. "String something"은 객체에 대한 참조 만 만들고 새로운 객체는 생성하지 않으므로 사용되는 추가 메모리는 작습니다. 또한 JVM과 결합 된 컴파일러는 어쨌든 생성 된 코드를 최적화 할 가능성이 높습니다.

메모리 관리 관행의 경우 병목 현상이 실제로 어디에 있는지 파악하기 위해 메모리를 더 잘 프로파일 링해야합니다.특히 대량의 메모리를 가리키는 정적 참조는 수집되지 않을 것입니다.

약한 참조 및 기타 특수 메모리 관리 클래스를 볼 수도 있습니다.

마지막으로 .... 응용 프로그램이 메모리를 많이 사용하는 경우, 그것은 이유가있을 수 있음을 염두에

업데이트를 유지 메모리 관리의 핵심은 데이터 구조뿐만 아니라 얼마나 많은 그대로 필요한 성능. 종종 메모리와 CPU 사이클 사이의 절충안입니다.

예를 들어 비싼 작업을 피하기 위해 성능 향상을 위해 많은 메모리를 캐싱 할 수 있습니다.

데이터 구조를 통해 생각한 것보다 오랫동안 물건을 메모리에 보관하지 않도록하십시오. 웹 응용 프로그램이라면 많은 양의 데이터를 세션 변수에 저장하지 말고 거대한 메모리 풀에 대한 정적 참조를 피하십시오.

4

첫 번째 루프가 더 좋습니다.

  • 때문에 변수 뭔가 명확 빠른 (이론적)
  • 프로그램이 읽기 더있을 것입니다.

그러나 이것은 메모리와 관련이 없습니다.

메모리 문제가있는 경우 소비되는 곳을 프로필해야합니다.

+0

죄송합니다, 제가 틀렸을 때 제발 제발하지만 첫 번째 루프는 각 루프에 새 변수에 대한 메모리를 할당합니다. 루프가 100 번 실행되는 경우 어떻게 동일한 문자열을 오버라이드하는 대신 100 개의 문자열을 만드는 것이 더 낫습니다. 두 번째 루프와 같이 한 문자열에만 메모리를 할당하는 것이 어떻습니까? – Mapisto

+0

메서드의 모든 로컬 변수에 대한 스택의 메모리는 코드의 선언 지점이 아니라 메서드 항목에 할당됩니다. 그러한 변수의 크기는 4 바이트입니다. (64 비트 VM에서 가능한 8 바이트). String의 크기는 스택이 아닌 힙에 할당됩니다. 이 할당은 "new"키워드에서 발생합니다. 문자열의 할당에는 차이가 없습니다. – Horcrux7

8

두 코드 샘플 모두에서 만들어진 개체가 없습니다. 이미 arrayOfStuff에있는 문자열에 대한 객체 참조 만 설정하면됩니다. 따라서 메모리 적으로 차이점은 없습니다.

1

뭔가의 범위가 더 작기 때문에 실제로 첫 번째 루프가 더 좋습니다. 메모리 관리와 관련하여 큰 차이는 아닙니다.

대부분의 Java 메모리 문제는 컬렉션에 객체를 저장할 때 발생하지만 제거하는 것을 잊지 마십시오. 그렇지 않으면 GC가 그의 일을 아주 잘합니다.

1

첫 번째 예는 괜찮습니다. 루프를 통해 스택 변수 할당 및 할당 해제 (매우 저렴하고 빠름) 이외에는 메모리 할당이 없습니다.

그 이유는 '할당 됨'이 4 바이트 스택 변수 (어쨌든 대부분의 32 비트 시스템에서) 인 참조이기 때문입니다. 스택 변수는 스택의 상단을 나타내는 메모리 주소에 추가하여 '할당'되므로 매우 빠르고 저렴합니다. 과

for (int i = 0; i < some_large_num; i++) 
{ 
    String something = new String(); 
    //do stuff with something 
} 

실제로 메모리 allocatiton을하고있다 : 당신이 조심해야 할 필요가 무엇

같은 루프입니다.

+0

각 반복마다 스택에 할당조차 없습니다. 이것들은 메소드 로컬 변수 테이블 (한 번에)에 할당되지만 루프가 끝나면 변수는 자동으로 범위를 벗어납니다. –

+0

그래,하지만 새로운 String 객체는 스택 할당보다 비싸다. – workmad3

5

JVM은 수명이 짧은 개체를 해제하는 것이 가장 좋습니다. 필요없는 개체를 할당하지 마십시오. 그러나 작업 부하, 객체 수명 및 객체 크기를 이해할 때까지는 메모리 사용을 최적화 할 수 없습니다. 프로파일 러는 이것을 알려줄 수 있습니다.

마지막으로, 최종 사용자는 사용하지 말아야합니다.Finalizers는 가비지 콜렉션을 방해합니다. 오브젝트는 해제 할 수 없지만 발생할 수 있거나 없을 수도있는 최종화를 위해 대기열에 있어야하기 때문입니다. finalizers를 사용하지 않는 것이 가장 좋습니다.

Eclipse에서 볼 수있는 메모리 사용량과 관련해서는 반드시 관련이있는 것은 아닙니다. GC는 사용 가능한 메모리의 양에 따라 작업을 수행합니다. 사용 가능한 메모리가 많은 경우 앱이 종료되기 전에 GC가 하나도 표시되지 않을 수 있습니다. 앱이 메모리가 부족한 경우 실제 프로필러 만 누출이나 비 효율성이있는 곳을 알려줍니다.

+0

가비지 수집 언어의 객체 지향 언어에서 성능이 떨어지는 이유는 많은 수의 수명이 긴 객체가 원인 일 때가 많습니다. 특히 많은 수의 레이어가있는 경우 특히 그렇습니다. 예를 들어, 셀 객체의 2D 배열로 스프레드 시트 객체를 만들면 각 객체에는 Color 객체, Value 객체 및 Formatting 객체가 포함되어 있으며 각 Formatting 객체에는 ..... 포함되어 있습니다. GC는 객체 그래프를 처리하는 데 오랜 시간이 걸립니다. 또한 메모리 간접 지정의 계층은 지연을 유발합니다 (어쨌든 Smalltalk에서 그렇습니다. Java에 대해서는 확실하지 않습니다). – ahoffer

1

아직 읽어 본 적이 없다면 Eclipse Test & Performance Tools Platform (TPTP)을 설치하는 것이 좋습니다. 힙을 덤프하고 검사하려면 SDK jmap and jhat 도구를 확인하십시오. 또한 Monitoring and Managing Java SE 6 Platform Applications을 참조하십시오.

+1

TPTP가 현재 (5 년 후) 현재와 마찬가지로 실제로 죽었던 것처럼 보이지만 JDK와 함께 제공되는 jvisualvm으로 이동하여 gc 로그를 분석하십시오 (http://www.tagtraum.com/gcviewer.html 또는 https://github.com)./chewiebug/GCViewer). –

4

제 생각에는 이러한 미세 최적화는 피해야합니다. 그들은 많은 뇌 싸이클을 소비하지만, 대부분의 경우에는 거의 영향을 미치지 않습니다.

응용 프로그램에는 아마도 몇 가지 중앙 데이터 구조가 있습니다. 그것들은 당신이 걱정해야만하는 것들입니다. 예를 들어, 기본 구조를 반복하여 크기 조정하지 않으려면 크기를 미리 채우는 것이 좋습니다. 이것은 특히 StringBuffer, ArrayList, HashMap 등에 적용됩니다. 이러한 구조에 대한 액세스를 잘 설계하여 많은 정보를 복사 할 필요가 없습니다.

적절한 알고리즘을 사용하여 데이터 구조에 액세스하십시오. 언급 한 루프와 같이 가장 낮은 레벨에서 Iterator을 사용하거나 적어도 항상 .size()을 피하십시오. (예, 크기가 변할 때마다 목록을 요구하고 있습니다. 대부분 시간이 변하지 않습니다.) BTW, 나는 종종 Map과 비슷한 오류를 보았습니다. 사람들은 처음에 entrySet()을 반복하는 대신 keySet()get 이상의 각 값을 반복합니다. 메모리 관리자가 여분의 CPU주기를 주셔서 감사합니다.

2

위의 포스터 중 하나로서, 추측하기보다는 프로그램의 특정 부분에 대한 메모리 (및/또는 CPU) 사용량을 측정하기 위해 프로파일 러를 사용하십시오. 당신이 찾은 것에 놀랄지도 모릅니다.

추가 이점이 있습니다. 프로그래밍 언어와 응용 프로그램에 대해 더 잘 이해하게 될 것입니다.

프로파일 링을 위해 VisualVM을 사용하고이를 크게 권장합니다. 그것은 jdk/jre 배포와 함께 제공됩니다.

0

"하단 루프가 더 좋다고 말할 수 없습니까?"대답은 아니오입니다. 같은 경우 필요한 경우 ... 변수 정의 (내용이 아님)는 메모리에서 이루어집니다. 힙이 있고 제한적입니다. 첫 번째 예제에서 각 루프는이 메모리에 인스턴스를 만들고 "arrayOfStuff"의 크기가 클 경우 "메모리 부족 오류 : Java 힙 공간"이 발생할 수 있습니다 ....

0

나는 당신이보고있는 것으로부터, 아래쪽 루프가 더 좋지 않다. 그 이유는 하나의 참조 (Ex-)를 재사용하려고해도, 실제로는 List (arrayOfStuff)에서 객체 (Ex-arrayOfStuff.get (i))가 참조되고 있기 때문입니다. 개체를 수집 할 수 있도록하려면 어느 위치에서나 참조해야합니다. 이 시점 이후 List의 수명이 확실하다면 별도의 루프 내에서 목록의 객체를 제거/해제 할 수 있습니다.

정적 관점에서 수행 할 수있는 최적화 (즉, 다른 스레드에서이 목록에 수정이 발생하지 않음), size() 호출을 반복적으로 피하는 것이 좋습니다. 즉, 크기가 변하기를 기대하지 않는다면, 왜 그것을 반복해서 계산해야합니까? 결국 배열이 아닙니다. 길이는 list.size()입니다.

관련 문제