소위 메모리 누수란 프로그램에서 더 이상 사용하지 않는 객체나 변수가 메모리에 점유되어 있는 것을 의미합니다.
Java에는 개체가 더 이상 참조되지 않을 때, 즉 개체가 고아가 될 때 가비지 수집기에 의해 해당 개체가 메모리에서 자동으로 지워지는 것을 보장할 수 있는 가비지 수집 메커니즘이 있습니다.
Java에는 가비지 수집 메커니즘이 있는데 왜 여전히 메모리 누수 문제가 발생합니까?
일부 개체는 가비지 수집기에서 처리할 수 없으므로 이러한 개체가 항상 JVM 메모리를 차지하게 되어 메모리 누수가 발생합니다.
Java는 가비지 수집 관리에 방향성 그래프를 사용하므로 참조 순환 문제를 제거할 수 있습니다. 예를 들어 서로 참조하는 두 개체가 있는 경우 루트 프로세스에 액세스할 수 없는 한 GC는 다음을 수행할 수 있습니다. 예를 들어 다음 코드는 이 경우 메모리 회수를 볼 수 있습니다.
import java. io.IOException; public class GarbageTest { public static void main(String[] args) throws IOException { try { // TODO Auto-generated method stub gcTest(); } catch (IOException e) { e.printStackTrace(); } System.out.println("has exited gcTest!"); System.in.read(); System.in.read(); System.out.println("out begin gc!"); for (int i = 0; i < 100; i++) { System.gc(); System.in.read(); System.in.read(); } } private static void gcTest() throws IOException { System.in.read(); System.in.read(); Person p1 = new Person(); System.in.read(); System.in.read(); Person p2 = new Person(); p1.setMate(p2); p2.setMate(p1); System.out.println("before exit gctest!"); System.in.read(); System.in.read(); System.gc(); System.out.println("exit gctest!"); } private static class Person { byte[] data = new byte[20000000]; Person mate = null; public void setMate(Person other) { mate = other; } } }
Java의 메모리 누수: 수명이 긴 개체가 수명이 짧은 개체에 대한 참조를 보유할 때 메모리 누수가 발생할 가능성이 있습니다. 수명이 긴 개체는 참조로 인해 더 이상 필요하지 않습니다. 이는 재활용될 수 없습니다. 이는 Java에서 메모리 누수가 발생하는 시나리오입니다. 프로그래머가 객체를 생성하고 다시는 사용하지 않을 수 있지만 객체는 항상 참조됩니다. 즉, 이 객체는 쓸모가 없지만 재활용될 수 없습니다. 이는 가비지 컬렉터에 의해 Java에서 메모리 누수가 발생할 수 있는 상황입니다.
예를 들어 캐시 시스템에서는 객체를 로드하여 캐시(예: 전역 지도 객체)에 넣은 다음 다시는 사용하지 않습니다. 이 객체의 값은 캐시에서 참조되지만, 더 이상 사용되지 않습니다.
Java에서 메모리 누수를 확인하려면 프로그램이 끝날 때까지 모든 분기를 실행하게 한 다음 객체가 사용되었는지 확인해야 합니다. 그러면 해당 객체가 메모리 누수라고 판단할 수 있습니다.
외부 클래스의 인스턴스 객체의 메서드가 내부 클래스의 인스턴스 객체를 반환하면 외부 클래스 인스턴스 객체가 더 이상 사용되지 않더라도 내부 클래스 객체를 오랫동안 참조하지만, 내부 클래스 외부 클래스의 인스턴스 개체를 유지하는 경우 이 외부 클래스 개체는 가비지 수집되지 않으며 이로 인해 메모리 누수가 발생합니다.
다음 내용은 인터넷에서 가져온 내용입니다(주된 기능은 스택의 요소를 지우는 것이지 배열에서 완전히 제거하는 것이 아니라 전체 저장 공간을 줄이는 것입니다. 이보다 더 잘 쓸 수 있습니다. element 그런데 요소가 있으면 배열에서 사라지고 해당 요소의 위치 값이 null로 설정됩니다.)
정말 저 스택보다 더 고전적인 예가 생각나지 않네요. 그래서 다른 분들의 예를 인용해야 하는데, 다음 예들은 제가 생각한 것이 아니고, 책에서 본 것입니다. 물론, 책에서 보지 못했다면 제가 직접 생각했을 수도 있습니다. 그런데 그 당시에는 내가 그런 생각을 했다고 하면 아무도 믿지 않았죠.
public class Stack { private Object[] elements = new Object[10]; private int size = 0; public void push(Object e) { ensureCapacity(); elements[size++] = e; } public Object pop() { if (size == 0) throw new EmptyStackException(); return elements[--size]; } private void ensureCapacity() { if (elements.length == size) { Object[] oldElements = elements; elements = new Object[2 * elements.length + 1]; System.arraycopy(oldElements, 0, elements, 0, size); } } }
위의 원리는 매우 간단합니다. 돈더미에 10개의 요소를 더한 다음 모두 팝업되면 돈더미가 비어 있고 원하는 것이 없지만 그것은 있을 수 없는 개체입니다. 재활용되므로 이는 일관성이 있습니다. 메모리 누수에 대한 두 가지 조건은 쓸모가 없으며 재활용될 수 없습니다. 하지만 그런 것이 존재한다고 해서 반드시 어떤 결과가 발생하는 것은 아닙니다. 이 돈 더미를 덜 사용하면 메모리는 몇 K만 낭비하게 됩니다. 어쨌든 우리의 메모리는 이미 G를 초과하므로 어떤 영향을 미칠까요? ? 게다가 이 물건도 곧 재활용될 텐데 그게 무슨 상관이겠는가? 아래에서 두 가지 예를 살펴보겠습니다.
class Bad { public static Stack s = new Stack(); static { s.push(new Object()); s.pop(); //这里有一个对象发生内存泄露 s.push(new Object());//上面的对象可以被回收了,等于是自愈了 } }
정적이기 때문에 프로그램이 종료될 때까지 존재하지만 자가 치유 기능도 있음을 알 수 있습니다. 즉, 스택에 최대 100개의 개체가 있으면 최대 100개만 가능합니다. 실제로 100개의 객체는 재활용할 수 없습니다. 스택은 내부적으로 100개의 참조를 보유하고 있습니다. 최악의 경우 새로운 객체를 추가하면 이전 참조가 자연스럽게 사라지기 때문입니다.
또 다른 메모리 누수 상황: 개체가 HashSet 컬렉션에 저장되면 해시 값 계산에 참여하는 개체의 필드를 수정할 수 없습니다. 그렇지 않으면 개체의 수정된 해시 값이 원래 저장된 것과 달라집니다. 이 경우 HashSet 컬렉션에 넣을 때 해시 값이 다릅니다. 이 경우 포함 메서드가 개체의 현재 참조를 매개 변수로 사용하여 HashSet 컬렉션에서 개체를 검색할 수 없는 결과가 발생합니다. 이는 또한 HashSet 컬렉션에서 현재 개체를 개별적으로 삭제할 수 없게 만들어 메모리 누수를 유발합니다.
(1) 데이터 구조로 인해 발생하는 단기 메모리 누수 문제는 아래 코드를 보세요
public class Stack{ private Object[] element=new Object[10]; private int size=0; public void push(Object ele){ ensureCapacity(); element[size++]=ele; } public Object pop(){ if(size==0) throw new EmptyStackException(); return element[--size]; //短暂造成内存泄露 } private void ensureCapacity(){ if(element.length==size){ Object[] oldElement=element; element=new Object[size*2+1]; System.arraycopy(oldElement,0,element,0,size); } } }
위 코드에서 pop()을 사용할 때마다 Stack이 팝업됩니다. 새 요소 앞에 추가되지 않은 경우 실제로는 팝된 개체를 가리키는 참조 요소[x]가 있으므로 GC는 이를 가비지 수집하지 않습니다. push() 시 element[x]=newObject를 설정해야만 이전에 생성된 객체를 재활용할 수 있습니다. 위의 pop() 메소드를 다음 코드로 변경하는 것이 훨씬 안전할 것입니다:
public Object pop(){ if(element.length==size) throws EmptyStackException(); Object o=element[--size]; elements[size]=null; //使得GC有机会回收这个对象 return o; }
위 내용은 Java 메모리 누수 문제 사례 분석의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!