힙 및 메모리 최적화
오늘 저는 프로젝트의 자동 데이터 분류 기능을 테스트하여 데이터베이스에서 수만 개의 레코드와 사진을 분류했습니다. 작업이 끝에 가까워지면 Java.lang.outofMemoryError, Java 힙 공간의 오류가 드러났습니다. 과거에는 Java가 쓰레기 수집기 메커니즘을 가지고 있기 때문에 글쓰기 프로그램에서 그러한 메모리 오류가 거의 발생하지 않았으므로 많은 관심을 기울이지 않았습니다. 오늘 나는 온라인으로 정보를 검색하고 이것을 기반으로 정렬했습니다.
1. 스택과 스택
새로운 쓰레기 수집기로 힙 제작은 재활용을 담당합니다
1. 프로그램이 실행되기 시작하면 JVM은 OS에서 메모리를 가져옵니다. 그 중 일부는 힙 메모리입니다. 힙 메모리는 일반적으로 스토리지 주소의 하단에 위쪽으로 배열됩니다.
2. 힙은 "런타임"데이터 영역이며, 클래스에 인스턴스화 된 물체는 힙에서 공간을 할당합니다.
3. 힙에 공간을 할당하는 것은 "새로운"과 같은 지침을 통해 설정됩니다. 힙은 동적으로 할당 된 메모리 크기이며, 수명은 컴파일러에 미리 알릴 필요가 없습니다.
4. C ++와 달리 Java는 힙과 스택을 자동으로 관리하며 쓰레기 수집기는 더 이상 사용되지 않는 힙 메모리를 자동으로 재활용 할 수 있습니다.
5. 단점은 런타임에 메모리가 동적으로 할당되므로 메모리 액세스 속도가 느리다는 것입니다.
스택 - 기본 유형 및 참조 유형을 빠르게 저장합니다
1. 첫 번째 및 외부 데이터 구조는 일반적으로 방법에서 매개 변수 및 로컬 변수를 저장하는 데 사용됩니다.
2. Java에서는 기본 유형의 모든 변수 (Short, Int, Long, Byte, Float, Double, Boolean, Char) 및 참조 유형의 모든 변수가 스택에 저장됩니다.
3. 스택의 데이터의 생활 공간은 일반적으로 현재 스코프 ({...}로 둘러싸인 영역;
4. 스택의 접근 속도는 힙보다 빠르며 CPU에 직접 위치한 레지스터에 이어 두 번째입니다.
5. 스택의 데이터를 공유 할 수 있으며 여러 참조가 동일한 주소를 가리킬 수 있습니다.
6. 단점은 스택의 데이터 크기와 수명을 결정하고 유연성이 부족하다는 것입니다.
2. 메모리 설정
1. 가상 머신 메모리 상태를 확인하십시오
long maxControl = runtime.getRuntime (). maxMemory (); // 가상 머신이 긴 전류를 제어 할 수있는 최대 메모리의 양을 얻습니다. long currentSure = runtime.getRuntime (). TotalMemory (); // 가상 시스템에서 현재 사용하는 메모리의 양을 얻습니다.
기본적으로 MaxControl = 66650112b = 63.5625m의 Java Virtual Machine;
아무것도하지 않으면 내 컴퓨터에서 측정 된 전류 사용 = 5177344b = 4.9375m;
2. 메모리 크기를 설정하도록 명령
-xms <size> 초기 Java 힙 크기 설정 : JVM 초기화 힙 메모리 크기를 설정합니다. 이 값은 쓰레기 수집이 완료 될 때마다 JVM 재분배 메모리를 피하기 위해 -xmx와 동일하게 설정할 수 있습니다.
-xmx <size> 최대 Java 힙 크기 설정 : JVM의 최대 힙 메모리 크기를 설정합니다.
-xmn <size> : 젊은 세대의 크기, 전체 힙 크기 = 젊은 세대의 크기 + 이전 세대의 크기 + 마지막 세대의 크기를 설정합니다.
-xsss <size> 설정 Java 스레드 스택 크기 : JVM 스레드 스택 메모리 크기를 설정합니다.
3. 특정 작업 (1) JVM 메모리 설정 :
MyClipse (Eclipse) Window-Java-jraS-installed JRES-Edit-Default VM 인수를 엽니 다
입력 : -xmx128m -xms64m -xmn32m -xss16m
(2) IDE 메모리 설정 :
myeclipse.ini (또는 Eclipse 루트 디렉토리의 eclipse.ini)에서 -vmargs에서 구성을 수정하십시오.
(3) Tomcat 메모리 설정
Tomcat의 루트 디렉토리에서 빈 폴더를 열고 Catalina.bat 편집
수정 : java_opts = -xms256m -xmx512m set
3. Java 힙의 OutofMemoryError 오류 분석
JVM이 시작되면 -XMS 매개 변수에 의해 설정된 힙 메모리가 사용됩니다. 프로그램이 계속되고 더 많은 객체를 생성함에 따라 JVM은 더 많은 객체를 유지하기 위해 힙 메모리를 확장하기 시작합니다. JVM은 또한 쓰레기 수집기를 사용하여 메모리를 재활용합니다. -XMX로 설정된 최대 힙 메모리에 거의 도달하면 더 이상 메모리를 새 개체에 할당 할 수 없으면 JVM은 java.lang.outofMemoryError를 던지고 프로그램이 충돌합니다. unfmemoryError를 던지기 전에 JVM은 쓰레기 수집기와 충분한 공간을 확보하려고하지만 공간이 충분하지 않다는 것을 알게되면이 오류를 던질 것입니다. 이 문제를 해결하려면 생성 한 객체, 어떤 객체가 공간의 양을 차지하는지 등 프로그램 객체에 대한 정보에 대해 명확해야합니다. 프로파일 러 또는 힙 분석기를 사용하여 OutofMemoryError 오류를 처리 할 수 있습니다. "java.lang.outofMemoryError : java 힙 스페이스"는 힙에 공간이 충분하지 않고 계속 확장 할 수 없음을 의미합니다. "java.lang.outofMemoryError : Permgen Space"는 영구 생성이 가득 차서 프로그램이 더 이상 클래스를로드하거나 문자열을 할당 할 수 없음을 의미합니다.
4. 힙과 쓰레기 수집
우리는 객체가 힙 메모리에서 생성되며, 쓰레기 수집은 힙 공간에서 죽은 물체를 제거하고 이러한 메모리를 힙으로 반환하는 프로세스입니다. 쓰레기 수집기를 사용하기 위해 힙은 주로 세 가지 영역, 즉 신세대, 구세대 또는 임기 세대 및 파마 공간으로 나뉩니다. 새로운 세대는 새로 생성 된 물체를 저장하는 데 사용되는 공간이며 객체가 새로 만들어 질 때 사용됩니다. 오랫동안 사용되면 쓰레기 수집가가 구식 (또는 임기 세대)으로 옮길 것입니다. Perm Space는 JVM이 클래스, 메소드, 문자열 풀 및 클래스 수준 세부 사항과 같은 메타 데이터를 저장하는 곳입니다.
5. 요약 :
1. Java 힙 메모리는 운영 체제가 JVM에 할당 된 메모리의 일부입니다.
2. 객체를 만들 때 Java 힙 메모리에 저장됩니다.
3. 가비지 수집을 용이하게하기 위해 Java 힙 공간은 새로운 세대, 구식 또는 임기 세대 및 Perm Space라고하는 세 가지 영역으로 나뉩니다.
4. JVM 명령 줄 옵션 -xms, -xmx 및 -xmn을 사용하여 Java 힙 공간의 크기를 조정할 수 있습니다.
5. JCONSOLE 또는 RUNTIME.MAXMEMORY (), RUNTIME.TOTALMEMORY () 및 RUNTIME.FREEMEMORY ()를 사용하여 힙 메모리의 크기를 Java에서 볼 수 있습니다.
6. "jmap"명령을 사용하여 힙 덤프를 얻고 "jhat"을 사용하여 힙 덤프를 분석 할 수 있습니다.
7. Java 힙 공간은 스택 공간과 다릅니다. 스택 공간은 통화 스택 및 로컬 변수를 저장하는 데 사용됩니다.
8. Java Garbage Collector는 죽은 물체 (더 이상 사용되지 않는 물체)가 차지하는 메모리를 회수하고 Java 힙 공간으로 해제하는 데 사용됩니다.
9. java.lang.outofMemoryError를 만나면 걱정할 필요가 없습니다. 때로는 힙 공간을 늘리기 만하면됩니다. 그러나 자주 발생하면 Java 프로그램에 메모리 누출이 있는지 확인해야합니다.
10. 프로파일 러 및 힙 덤프 분석 도구를 사용하여 Java 힙 공간을 보면 각 객체에 얼마나 많은 메모리가 할당되는지 확인할 수 있습니다.
스택 스토리지에 대한 자세한 설명
Java 스택 스토리지에는 다음과 같은 특성이 있습니다.
1. 스택의 데이터 크기와 수명주기를 결정해야합니다.
예를 들어, 기본 유형의 저장 : int a = 1; 이 변수는 문자 그럴 값을 포함하고, a는 문자 유형에 대한 참조이며, 문자 그럴 값 3을 가리키며,이 문자 데이터의 크기와 수명으로 인해,이 문자 값은 프로그램 블록에서 고정으로 정의되며, 프로그램 블록이 종료되면 속도를 추구하기 위해 스택에 존재합니다.
2. 스택에 존재하는 데이터를 공유 할 수 있습니다.
(1) 기본 유형 데이터 저장 :
좋다:
int a = 3; int b = 3;
컴파일러는 먼저 int a = 3을 처리합니다. 먼저 스택의 변수 A에 대한 참조를 작성한 다음 문자 그대로 값이 3 인 주소가 있는지 확인합니다. 찾을 수없는 경우 문자 그대로 값이 3 인 주소를 열고 3의 주소를 가리킨 다음 int b = 3; B의 기준 변수를 작성한 후, 스택에 이미 문자 그대로의 값이 있기 때문에 B는 3의 주소를 직접 가리 킵니다. 이러한 방식으로 A와 B는 동시에 3을 가리 킵니다.
참고 :이 문자 그대로 참조는 클래스 객체와 다릅니다. 두 클래스 객체의 참조가 동시에 객체를 가리킨다고 가정하면, 한 개체 기준 변수가 객체의 내부 상태를 변경하면 다른 객체 참조 변수는이 변경을 즉시 반영합니다. 대신, 문자 그대로 참조를 통해 그 값을 수정해도 다른 값이 그에 따라 변경되지 않습니다. 위의 예에서와 같이, 우리는 a와 b의 값을 정의한 후에, a = 4; 그런 다음 B는 4와 같거나 3과 같지 않습니다. 컴파일러 내부에서 A = 4가 발생하면 스택에 문자 그대로 값이 4인지 다시 검색합니다. 그렇지 않은 경우 주소를 다시 열어 4의 값을 저장하십시오. 이미 존재하는 경우이 주소를 직접 가리 킵니다. 따라서 값 A의 변화는 값에 영향을 미치지 않습니다. b.
(2) 패키징 데이터 저장 :
정수, 이중, 문자열 등과 같은 해당 기본 데이터 유형을 래핑하는 클래스. 이러한 클래스 데이터는 힙에 존재합니다. Java는 새로운 () 문을 사용하여 컴파일러를 표시하고 런타임에 필요한만큼 동적으로 만 생성하므로 더 유연하지만 단점은 더 많은 시간이 걸린다는 것입니다.
예를 들면 다음과 같이 문자열을 예로 들어 보겠습니다.
문자열은 특수 포장 데이터입니다. 즉, String str = new String ( "ABC")의 형태로 만들 수 있습니다. 또는 String str = "abc";.의 형태로 만들 수 있습니다. 전자는 표준화 된 클래스 생성 과정, 즉 Java에서 모든 것이 객체이며 객체는 모두 New () 형태로 생성 된 클래스의 인스턴스입니다. DateFormat 클래스와 같은 Java의 일부 클래스는 클래스의 getInstance () 메소드를 통해 새로 생성 된 클래스를 반환 할 수 있으며,이 원칙을 위반하는 것으로 보입니다. 실제로는 그렇지 않습니다. 이 클래스는 싱글 톤 패턴을 사용하여 클래스의 인스턴스를 반환하지만이 인스턴스는 New ()를 통해 클래스 내부에서 생성되며,이 세부 사항은 외부 에서이 세부 사항을 숨 깁니다.
그렇다면 왜 인스턴스가 String str = "abc"에서 new ()를 통해 생성되지 않는 이유는 무엇입니까? 위의 원칙을 위반 했습니까? 실제로는 없습니다.
String str = "abc"의 내부 작업에 대해. Java는 내부적 으로이 진술을 다음 단계로 변환합니다.
에이. 먼저 String class에 str라는 객체 참조 변수를 정의하십시오 : String str;
비. 스택에 "ABC"값이있는 주소가 있는지 확인하십시오. 그렇지 않은 경우 문자 그대로 "ABC"가있는 주소를 열고 문자열 클래스의 새 개체 O를 작성하고 O의 문자열 값을이 주소에 가리키고 스택 의이 주소 옆에 참조 객체 O를 기록하십시오. "ABC"값의 주소가있는 경우 Object O를 찾아 O의 주소를 반환하십시오.
기음. 오브젝트 O의 주소에 대한 지점
일반적으로 문자열 클래스의 문자열 값이 직접 저장된다는 점에 주목할 가치가 있습니다. 그러나 String str = "abc";와 같은 상황에서 문자열 값은 스택에 존재하는 데이터에 대한 참조를 가지고 있습니다 (예 : String str = "abc"; 스택 스토리지 및 힙 스토리지 모두).
이 문제를 더 잘 설명하기 위해 다음 코드를 통해 확인할 수 있습니다.
문자열 str1 = "abc"; 문자열 str2 = "abc"; System.out.println (str1 == str2); //진실
(진리 값은 두 참조가 동일한 객체를 가리키는 경우에만 반환됩니다. str1과 str2는 동일한 객체를 가리키고 있습니다)
결과는 JVM이 두 개의 참조 STR1과 STR2를 생성했지만 하나의 객체 만 생성되었으며 두 참조는이 객체를 가리 켰습니다.
문자열 str1 = "abc"; 문자열 str2 = "abc"; str1 = "bcd"; System.out.println (str1 + "," + str2); // bcd, abc system.out.println (str1 == str2); //거짓
이는 할당의 변경이 클래스 객체의 참조 변화를 초래하고 STR1은 다른 새로운 객체를 가리키는 반면 STR2는 여전히 원래 객체를 가리 킵니다. 위의 예에서, str1의 값을 "BCD"로 변경하면 JVM은 스택에 값을 저장할 주소가 없다는 것을 발견 하여이 주소를 열고 문자열 값 이이 주소를 가리키는 새 개체를 만들었습니다.
실제로 문자열 클래스는 불변의 클래스로 설계되었습니다. 값을 변경하려면 가능하지만 JVM은 런타임시 새 값을 기반으로 새 객체를 조용히 생성 한 다음 원래 메모리를 기반으로 변경할 수 없습니다. 그런 다음이 객체의 주소를 원래 클래스의 참조로 반환합니다. 이 생성 과정은 완전히 자동이지만 결국 시간이 더 걸립니다. 시간 요구 사항에 더 민감한 환경에서는 특정 부작용이 있습니다.
문자열 str1 = "abc"; 문자열 str2 = "abc"; str1 = "bcd"; 문자열 str3 = str1; System.out.println (str3); // bcd string st4 = "bcd"; System.out.println (str1 == str4); //진실
str3 객체에 대한 참조는 str1을 가리키는 물체를 직접 지적합니다 (str3은 새 객체를 생성하지 않습니다). STR1이 값을 변경 한 후 문자열 참조 STR4를 작성하고 값을 수정하여 str1에 의해 생성 된 새로운 객체를 가리 킵니다. 이번에는 STR4가 새로운 객체를 생성하지 않았으므로 스택에서 데이터를 다시 공유하는 것을 실현할 수 있습니다.
문자열 str1 = 새 문자열 ( "abc"); 문자열 str2 = "abc"; System.out.println (str1 == str2); //거짓
두 개의 참고 문헌이 만들어졌습니다. 두 개의 객체가 생성되었습니다. 두 참조는 두 개의 다른 객체를 가리 킵니다.
문자열 str1 = "abc"; 문자열 str2 = 새 문자열 ( "abc"); System.out.println (str1 == str2); //거짓
두 개의 참고 문헌이 만들어졌습니다. 두 개의 객체가 생성되었습니다. 두 참조는 두 개의 다른 객체를 가리 킵니다.
위의 두 코드는 객체가 new ()로 생성되는 한 힙에 생성되고 문자열이 별도로 저장됨을 나타냅니다. 스택의 데이터와 동일하더라도 스택의 데이터와 공유되지 않습니다.
요약 :
(1) string str = "abc"와 같은 형식을 사용하여 클래스를 정의 할 때, 우리는 항상 문자열 클래스의 객체 str을 만들었다는 것을 당연한 것으로 여깁니다. 함정에 대해 걱정하십시오! 객체가 만들어지지 않았을 수도 있습니다! 확실한 것은 문자열 클래스에 대한 참조가 생성된다는 것입니다. 이 참조가 새 개체를 가리키는 지 여부에 대해서는 새 개체를 명시 적으로 생성하기 위해 새로운 () 메소드를 사용하지 않는 한 컨텍스트에 따라 고려해야합니다. 따라서보다 정확하기 위해 문자열 클래스의 객체에 참조 변수 str을 만듭니다. 이를 인식하는 것은 프로그램에서 어려운 버그를 해결하는 데 도움이됩니다.
(2) String str = "abc"사용; JVM은 스택에서 데이터의 실제 상황에 따라 새 개체를 만들어야하는지 자동으로 결정하기 때문에 프로그램의 실행 속도를 어느 정도 향상시킬 수 있습니다. 문자열 코드 str = new String ( "ABC");
(3) 문자열 클래스의 불변의 특성으로 인해 (래퍼 클래스의 값을 수정할 수 없기 때문에) 문자열 변수를 자주 변환 해야하는 경우 StringBuffer 클래스는 프로그램 효율성을 향상시키기 위해 고려해야합니다.