먼저 알아야 할 것
1. C/C++ 프로그래머는 스스로 메모리를 관리하는 반면, Java 메모리는 GC에 의해 자동으로 회수됩니다.
나는 C++에 익숙하지 않지만 아마도 상식적인 실수를 저지르지는 않았을 것입니다.
2. 메모리 누수란 무엇입니까?
메모리 누수는 시스템에 재활용할 수 없는 메모리가 존재하는 것을 의미하며, 메모리 부족이나 시스템 충돌을 일으키는 경우도 있습니다.
C/C++에서는 할당된 메모리가 해제되지 않으면 메모리 누수가 발생합니다.
3. Java에는 메모리 누수가 있습니다. 이에 대해 계속 논의하기 전에 먼저 이를 인정해야 합니다. Java에는 메모리 누수가 있지만 기본적으로는 신경 쓸 필요가 없습니다. 특히 코드 자체에 관심이 없는 사람이라면 더욱 그렇습니다.
Java의 메모리 누수는 확실히 가비지 수집기에서 재활용할 수 없는 쓸모없는 개체가 있음을 의미합니다.
그리고 메모리 누수 문제가 있어도 표시되지 않을 수 있습니다.
4. Java의 매개변수는 모두 값으로 전달됩니다.
기본형에는 기본적으로 이의가 없지만, 참조형에는 이의가 없습니다.
자바 메모리 누수
1. 힙 메모리 오버플로(outOfMemoryError: java 힙 공간)
JVM 사양에서는 힙의 메모리를 사용하여 객체 인스턴스와 배열을 생성합니다.
힙 메모리를 세분화하면 Young Generation과 Old Generation으로 나눌 수도 있습니다. Young Generation에는 eden 영역과 두 개의 Survivor 영역이 포함됩니다.
새로운 객체가 생성되면 메모리 적용 프로세스는 다음과 같습니다.
a. jvm은 먼저 eden 영역의 새 객체에 필요한 메모리를 할당하려고 시도합니다.
b. 메모리 크기가 충분하면 애플리케이션이 종료되고, 그렇지 않으면 다음 단계가 진행됩니다.
c. JVM은 youngGC를 시작하고 Eden 영역에 있는 비활성 객체를 해제하려고 시도합니다. 해제 후에도 Eden 공간이 새 객체를 넣을 만큼 충분하지 않으면 Eden에 있는 활성 객체 중 일부를 Survivor 영역에 넣으려고 합니다.
d.Survivor 영역은 Eden과 Old 사이의 중간 교환 영역으로 사용됩니다. OLD 영역에 충분한 공간이 있으면 Survivor 영역의 개체는 Old 영역으로 이동되고, 그렇지 않으면 Survivor 영역에 유지됩니다.
e. OLD 영역에 공간이 충분하지 않으면 JVM은 OLD 영역에서 전체 GC를 수행합니다.
f. 전체 GC 후에도 Survivor 및 OLD 영역이 여전히 Eden에서 복사된 일부 개체를 저장할 수 없어 JVM이 Eden 영역에 새 개체에 대한 메모리 영역을 생성할 수 없게 되면 "메모리 부족 오류"가 나타납니다.
outOfMemoryError:java 힙 공간
2. 메소드 영역의 메모리 오버플로(outOfMemoryError: permgem space)
JVM 스펙에서 메소드 영역은 주로 클래스 정보, 상수, 정적 변수 등을 저장한다.
따라서 프로그램이 너무 많은 클래스를 로드하거나 리플렉션이나 gclib와 같은 동적 프록시 생성 기술을 사용하는 경우 이 영역에서 메모리 오버플로가 발생할 수 있습니다. 일반적으로 이 영역에서 메모리 오버플로가 발생할 때 나타나는 오류 메시지는 다음과 같습니다.
outOfMemoryError:permgem 공간
3. 스레드 스택 오버플로(java.lang.StackOverflowError)
스레드 스택은 스레드 고유의 메모리 구조이므로 스레드 스택의 문제는 스레드가 실행될 때 발생하는 오류여야 합니다.
일반적으로 스레드 스택 오버플로는 재귀가 너무 깊거나 메서드 호출 수준이 너무 높을 때 발생합니다.
스택 오버플로가 발생할 때 나타나는 오류 메시지는 다음과 같습니다.
자바. 랭. 스택오버플로우 오류
메모리 누수의 여러 시나리오:
1. 수명이 긴 개체는 수명이 짧은 개체에 대한 참조를 보유합니다.
이는 메모리 누수에 대한 가장 일반적인 시나리오이자 코드 디자인에서 흔히 발생하는 문제입니다.
예를 들어, 지역 변수가 전역 정적 맵에 캐시되어 있고 지우기 작업이 없으면 맵은 시간이 지남에 따라 점점 더 커지고 메모리 누수가 발생합니다.
2. hashset에 포함된 객체의 매개변수 값을 수정하며, 매개변수는 해시값을 계산하는 데 사용되는 필드입니다.
개체가 HashSet 컬렉션에 저장된 후에는 해시 값 계산에 참여하는 개체의 필드를 수정할 수 없습니다. 그렇지 않으면 개체의 수정된 해시 값이 원래 HashSet 컬렉션에 저장되었을 때의 해시 값과 달라집니다. . HashSet 컬렉션에서 현재 개체를 삭제하면 메모리 누수가 발생합니다.
3. 기계의 연결 수와 종료 시간을 설정하십시오
리소스를 많이 사용하는 연결을 오랫동안 열면 메모리 누수가 발생할 수도 있습니다.
메모리 누수의 예를 살펴보겠습니다.
공용 클래스 스택 { 개인 개체[] 요소=새 개체[10]; 개인 int 크기 = 0; 공용 void push(객체 e){ verifyCapacity(); 요소[크기++] = e } 공용 개체 팝() size == 0) throw new EmptyStackException(); return elements[--size]; } private void verifyCapacity(){ if(elements.length == size){ Object[] oldElements = 요소; 요소 = 새 객체[2 * 요소. 길이+1]; arraycopy(oldElements,0, 요소, 0, 크기) } }}
위의 원칙은 매우 간단합니다. 스택에 10개의 요소를 추가한 다음 모두 팝아웃하면 스택이 비어 있고 원하는 것이 없지만 이 개체는 메모리 누수에 대한 두 가지 요구 사항을 충족합니다. 상태: 쓸모가 없으며 재활용할 수 없습니다.
하지만 그런 것이 존재한다고 해서 반드시 이 스택을 적게 사용하면 어떤 결과가 발생하지 않을 수도 있습니다.
단지 몇 K의 메모리 낭비일 뿐입니다. 어쨌든 우리의 메모리는 이미 G까지 올라갔으니, 게다가 이게 곧 재활용될 텐데 무슨 상관이겠습니까? 아래에서 두 가지 예를 살펴보겠습니다.
실시예 1
공용 클래스 불량{ 공용 정적 스택 s=Stack() 정적{ s. push(새 객체()); pop(); //여기 객체에 메모리 누수가 있습니다. push(new Object()); //위 객체는 재활용이 가능하며 이는 자가 치유와 같습니다.}}
정적이기 때문에 프로그램이 종료될 때까지 존재하지만, 자가치유 기능도 가지고 있는 것을 볼 수 있습니다.
즉, 스택에 최대 100개의 개체가 있으면 최대 100개의 개체만 재활용할 수 있습니다. 실제로 스택은 내부적으로 100개의 참조를 보유하고 있으므로 모두 쓸모가 없습니다. , 왜냐하면 새로운 진행 상황을 넣으면 이전 참조가 자연스럽게 사라지기 때문입니다!
실시예 2
공용 클래스 NotTooBad{ 공용 void doSomething(){ 스택 s=new Stack() s. push(new Object()); //다른 코드 s. pop();//또한 객체를 재활용할 수 없고 메모리 누수가 발생합니다. }//메소드를 종료하면 s는 자동으로 무효화되고 s는 재활용 가능하며 Stack 내부의 참조도 자연스럽게 사라지므로 //Self-healing도 여기서 할 수 있으며 이 메소드에는 없다고 할 수 있습니다. 메모리 누수 문제가 있지만 나중에 전달됩니다. // GC에만 제공됩니다. 왜냐하면 외부에 공개되지 않고 닫혀 있기 때문입니다. 위와 같이 말할 수 있습니다. Code 99. 9999%의 상황은 아무런 영향을 미치지 않습니다. 물론 이런 코드를 작성하면 나쁜 영향은 없지만 // 확실히 쓰레기 코드라고 할 수 있습니다. 하나 추가하겠습니다. 빈 for 루프는 큰 영향을 미치지 않습니다. 그렇죠?}
위의 두 가지 예는 사소한 것이지만 C/C++의 메모리 누수는 나쁘지는 않지만 최악입니다.
한곳에서 재활용하지 않으면 절대로 재활용할 수 없습니다. 이 메서드를 자주 호출하면 메모리가 소모됩니다!
자바에도 자가치유 기능이 있기 때문에(제가 직접 명명하고 아직 특허를 출원하지 않았습니다) 자바의 메모리 누수 문제는 거의 무시할 수 있지만, 아는 사람은 커밋해서는 안 됩니다.
메모리 누수를 방지하려면 코드를 작성할 때 다음 제안 사항을 참조할 수 있습니다.
1. 쓸모없는 객체에 대한 참조를 가능한 한 빨리 해제하십시오.
2. 문자열 처리를 사용하고, 문자열 사용을 피하고, StringBuffer를 광범위하게 사용하십시오. 각 문자열 개체는 메모리의 독립적인 영역을 차지해야 합니다.
3. 정적 변수는 영구 생성(메소드 영역)에 저장되며, 영구 생성은 기본적으로 가비지 수집에 참여하지 않으므로 정적 변수를 가능한 한 적게 사용하십시오.
4. 루프에서 객체를 생성하지 마십시오.
5. 대용량 파일을 열거나 데이터베이스에서 한 번에 너무 많은 데이터를 가져오면 쉽게 메모리 오버플로가 발생할 수 있으므로 이러한 곳에서는 대략적으로 최대 데이터 양을 계산하고 필요한 최소 및 최대 메모리 공간 값을 설정해야 합니다.