이 기사는 Java 메모리 관리의 원리와 메모리 누출의 원인을 자세히 소개합니다.
Java 메모리 관리 메커니즘
C ++에서, 메모리를 동적으로 할당하기 위해 메모리 조각이 필요한 경우, 프로그래머는이 메모리의 전체 수명주기에 책임이 있어야합니다. 할당 신청, 사용, 최종 릴리스까지. 이 과정은 매우 유연하지만 매우 성가신 프로그래머는 과실로 인해 자유 메모리를 잊어 버리기 쉽습니다. Java Language는 쓰레기 수집 메커니즘 인 메모리 관리에 대한 자체 최적화를 만들었습니다. Java의 거의 모든 메모리 객체는 힙 메모리 (기본 데이터 유형 제외)에 할당되며 GC (Gage Collection)는 더 이상 사용되지 않는 자동 재활용 메모리를 담당합니다.
위는 Java 메모리 관리 메커니즘의 기본 상황입니다. 그러나 우리가 이것 만 이해한다면, 우리는 여전히 실제 프로젝트 개발에서 메모리 누출을 만날 것입니다. 어떤 사람들은 Java의 쓰레기 수집 메커니즘이 자동으로 메모리를 재활용 할 수 있기 때문에 왜 메모리 누출이 여전히 있습니까? 이 질문에서, 우리는 GC가 메모리 객체를 재활용하는시기와 GC에서 더 이상 사용되지 않는 것으로 간주 될 것인지 알아야합니다.
Java의 메모리 객체에 대한 액세스는 참조 방법을 사용합니다. Java 코드에서는이 참조 변수의 값을 통해 메모리 객체의 기준 변수를 유지하면 해당 메모리 주소에서 메모리 객체 공간에 액세스 할 수 있습니다. Java 프로그램 에서이 참조 변수 자체는 힙 메모리와 코드 스택 (기본 데이터 유형과 동일)에 저장 될 수 있습니다. GC 스레드는 코드 스택의 참조 변수에서 추적을 시작하여 사용되는 메모리를 결정합니다. GC 스레드가 이런 식으로 힙 메모리를 추적 할 수없는 경우, GC는이 메모리가 더 이상 사용되지 않을 것이라고 생각합니다 (코드가 더 이상이 메모리에 액세스 할 수 없기 때문에).
이 지시 된 그래프 메모리 관리 방법을 통해 메모리 객체가 모든 참조를 잃어 버릴 때 GC는 재활용 할 수 있습니다. 반대로, 객체에 여전히 참조가있는 경우, Java 가상 머신이 unfmemoryerRor를 던지더라도 GC에 의해 재활용되지 않습니다.
자바 메모리 누출
일반적으로 메모리 누출에 대한 두 가지 상황이 있습니다. 한 경우, C/C ++ 언어에서는 힙의 모든 할당 된 메모리가 삭제됩니다 (예 : 포인터 재 할당). 메모리 및 액세스 방법 (참조). 첫 번째 사례는 쓰레기 수집 메커니즘의 도입으로 인해 Java에서 잘 해결되었다는 것입니다. 따라서 Java의 메모리 누출은 주로 두 번째 사례를 나타냅니다.
어쩌면 개념에 대해 이야기하는 것이 너무 추상적 일 수도 있고, 그러한 예를 살펴볼 수 있습니다.
코드 사본은 다음과 같습니다.
벡터 v = 새로운 벡터 (10);
for (int i = 1; i <100; i ++) {
Object o = new Object ();
v.add (o);
o = null;
}
이 예에서는 벡터 객체 v에 대한 참조가 있으며 코드 스택에 객체 개체 O에 대한 참조가 있습니다. FOR 루프에서는 새로운 객체를 지속적으로 생성 한 다음 벡터 객체에 추가 한 다음 O 참조를 비우십시오. 문제는 O 참조가 비어있는 후 GC가 발생하면 GC에 의해 우리가 만든 객체 객체를 재활용 할 것인가? 대답은 아니오입니다. GC가 코드 스택에서 참조를 추적 할 때 V 참조를 찾아 계속 추적하면 v 참조가 가리키는 메모리 공간에 객체 객체에 대한 참조가 있음을 알 수 있습니다. 즉, O 참조가 비어 있었지만 객체 객체에 여전히 다른 참조가 있으며 액세스 할 수 있으므로 GC가 해제 할 수 없습니다. 이 루프 후 객체 객체가 프로그램에 영향을 미치지 않으면이 Java 프로그램에서 메모리 누출이 발생했다고 생각합니다.
Java 메모리 누출은 C/C ++의 메모리 누출에 덜 파괴적이지만 프로그램이 중단되는 몇 가지 경우를 제외하고 대부분의 경우 프로그램은 여전히 정상적으로 실행될 수 있습니다. 그러나 모바일 장치가 메모리 및 CPU에 엄격한 제한이있는 경우 Java 메모리 오버플로가 프로그램의 비 효율성과 많은 양의 원치 않는 메모리의 점유로 이어질 것입니다. 이로 인해 전체 기계의 성능이 악화되고 심각한 경우에는 외부 모시러가 던져져 프로그램이 충돌하게됩니다.
일반적으로 메모리 누출을 피하십시오
일반적으로 복잡한 데이터 구조를 포함하지 않고 메모리 객체의 수명주기가 프로그램이 필요한 시간을 초과함에 따라 Java 메모리 누출이 나타납니다. 우리는 때때로 그것을 "물체가 없음"이라고 부릅니다.
예를 들어:
코드 사본은 다음과 같습니다.
공개 클래스 Filesearch {
개인 바이트 [] 컨텐츠;
개인 파일 mfile;
public filesearch (파일 파일) {
mfile = 파일;
}
공개 부울 하스 트링 (String str) {
int size = getfilesize (mfile);
내용 = 새로운 바이트 [크기];
로드 파일 (mfile, 컨텐츠);
문자열 s = 새 문자열 (컨텐츠);
반환 s.crantains (str);
}
}
이 코드에는 Filesearch 클래스에 기능 Hasstring이 있으며 문서에 지정된 문자열이 포함되어 있는지 확인합니다. 프로세스는 먼저 MFILE을 메모리에로드 한 다음 판단을하는 것입니다. 그러나 여기서 문제는 내용이 로컬 변수가 아닌 인스턴스 변수로 선언된다는 것입니다. 따라서이 기능이 반환 된 후에는 전체 파일의 데이터가 여전히 메모리에 존재합니다. 앞으로 더 이상 이러한 데이터가 필요하지 않으므로 비합리적인 메모리 낭비로 이어집니다.
이 경우 메모리 누출을 피하려면 C/C ++ 메모리 관리 사고로 할당 된 메모리를 관리해야합니다. 먼저, 객체 참조를 선언하기 전에 메모리 객체의 유효 범위를 명확히하는 것입니다. 함수 내에서 유효한 메모리 객체는 로컬 변수로 선언해야하며 클래스 인스턴스와 동일한 수명주기를 가진 개체는 인스턴스 변수로 선언해야합니다. 둘째, 메모리 객체가 더 이상 필요하지 않을 때 수동으로 비우는 것을 잊지 마십시오.
복잡한 데이터 구조에서 메모리 누출 문제
실제 프로젝트에서는 종종 더 복잡한 데이터 구조를 사용하여 프로그램 작동 중에 필요한 데이터 정보를 캐시합니다. 때로는 데이터 구조의 복잡성으로 인해 특별한 요구가 있기 때문에 (예 : 프로그램의 실행 속도를 개선하기 위해 가능한 한 많은 캐시 정보 등) 데이터를 처리하기가 어렵습니다. 데이터 구조에서. 수명주기를 명확하게 정의합니다. 현재 Java의 특수 메커니즘을 사용하여 메모리 누출을 방지 할 수 있습니다.
우리는 Java의 GC 메커니즘이 메모리를 추적하는 참조 메커니즘을 기반으로하기 전에 소개했습니다. 그 전에 우리가 사용한 참고 문헌은 "Object O"의 형태 만 정의했습니다. 실제로 이것은 Java 참조 메커니즘의 기본 상황 일 뿐이며 다른 참조 방법이 있습니다. 이러한 특수 참조 메커니즘을 사용하고 GC 메커니즘과 결합함으로써 필요한 효과 중 일부를 달성 할 수 있습니다.
Java의 여러 참조 방법
Java에는 강력한 인용, 소프트 인용, 약한 인용 및 가상 인용과 같은 몇 가지 다른 방법이 있습니다. 다음으로, 우리는 먼저 이러한 인용 방법의 중요성을 자세히 이해합니다.
강한 인용구
우리가 이전에 도입 한 내용에 사용 된 인용은 모두 강력한 인용으로 사용 된 가장 일반적인 인용입니다. 물체가 강력한 참조를 갖는 경우, 필수 일일 필요성과 유사하며 쓰레기 수집기는 결코 그것을 재활용하지 않습니다. 메모리 공간이 충분하지 않으면 Java 가상 머신은 오히려 메모리 문제를 해결하기 위해 강력한 참조를 가진 개체를 재활용하는 것보다 프로그램이 비정상적으로 종료되도록합니다.
소프트로 회의
Softreference 클래스의 일반적인 사용은 메모리에 민감한 캐시를위한 것입니다. Softreference의 원칙은 JVM이 객체에 대한 참조를 유지할 때 불충분 한 메모리를보고하기 전에 모든 소프트 참조가 지워지도록하는 것입니다. 핵심 요점은 쓰레기 수집가가 런타임에 소프트 액세스 가능한 물체를 방출 할 수 있다는 것입니다. 물체가 해제되는지 여부는 쓰레기 수집기의 알고리즘과 가비지 수집기가 실행될 때 사용 가능한 메모리의 양에 따라 다릅니다.
약한 참조
약한 회의 클래스의 전형적인 사용은 매핑을 정규화하는 것입니다 (정식 매핑). 또한, 약한 참조는 비교적 긴 수명이 길고 레크리에이션 오버 헤드가 낮은 물체에도 유용합니다. 핵심 요점은 쓰레기 수집기 실행 중에 약하게 접근 가능한 물체가 발생하면 약점에 의해 언급 된 객체가 해제된다는 것입니다. 그러나 쓰레기 수집기는 약하게 접근 가능한 물체를 찾아 방출하기 전에 여러 번 실행해야 할 수도 있습니다.
공학적 회의
Phantomreference 클래스는 다가오는 참조 객체 컬렉션을 추적하는 데만 사용될 수 있습니다. 마찬가지로, 사전 사전 청산 작업을 수행하는 데 사용될 수도 있습니다. Phantomreference는 참조 큐 클래스와 함께 사용해야합니다. 참조 큐는 알림 메커니즘 역할을 할 수 있으므로 필요합니다. 쓰레기 수집기가 객체가 가상 액세스 객체라고 판단하면 Phantomreference 객체가 참조 큐에 배치됩니다. phantomreference 객체를 참조 큐에 넣는 것은 Phantomreference 객체에서 참조 된 객체가 종료되었으며 수집 할 수 있음을 나타내는 알림입니다. 이를 통해 물체가 차지하는 메모리가 재활용되기 직전에 작업을 수행 할 수 있습니다. 참조 및 참조 큐는 참조 큐와 함께 사용됩니다.
GC, 참조 및 참조 큐
A. GC는 강력한 참조로 객체의 메모리를 삭제할 수 없습니다.
B. GC는 소프트 참조 만있는 객체 메모리를 찾았습니다.
softreference 객체의 기준 필드는 숫자로 설정되어 객체가 더 이상 힙 객체를 지칭하지 않도록합니다.
softreference에서 언급 한 힙 객체는 최종화 가능한 것으로 선언됩니다.
③ 힙 객체의 최종 () 메소드가 실행되고 객체가 차지하는 메모리가 해제되면 Softreference 객체가 참조 큐에 추가됩니다 (후자가 존재하는 경우).
C. GC는 약한 참조만으로 객체 메모리를 발견합니다.
heare 약점 회의 객체의 기준 필드가 널로 설정되어 객체가 더 이상 힙 객체를 참조하지 않도록합니다.
② 약점에 의해 참조 된 힙 객체는 최종화 가능한 것으로 선언됩니다.
③ 힙 객체의 최종 () 메소드가 실행되고 객체가 차지하는 메모리가 해제되면, 약점 객체가 기준 큐에 추가됩니다 (후자가 존재하는 경우).
D. GC는 가상 참조 만있는 객체 메모리를 발견합니다.
phantomreference에서 언급 한 힙 물체는 최종화 가능한 것으로 선언됩니다.
heap 힙 객체가 해제되기 전에 phantomreference가 참조 큐에 추가됩니다.
다음 사항은 주목할 가치가 있습니다.
1. GC는 일반적으로 소프트 참조 메모리 객체를 찾지 못합니다.
2. GC의 약한 참고 문헌의 발견 및 출시는 즉시 GC가 약한 참조로 메모리 객체를 발견하고 출시하기 전에 여러 번 반복해야합니다.
3. 소프트 참조 및 약한 참조가 참조 참조에 추가되면 실제 메모리에 대한 참조가 비어 있고 관련 메모리가 해제되었습니다. ReferenceQueue에 가상 참조를 추가 할 때 메모리는 아직 출시되지 않았으며 여전히 액세스 할 수 있습니다.
위의 소개를 통해, 나는 당신이 Java 인용 메커니즘과 몇 가지 인용 방법의 유사점과 차이점에 대한 특정 이해를 가지고 있다고 생각합니다. 개념은 너무 추상적 일 수 있습니다.
코드 사본은 다음과 같습니다.
문자열 str = 새 문자열 ( "hello");
referencequeue <string> rq = 새로운 참조 Quesure <string> ();
약점 <string> wf = 새로운 약점 <string> (str, rq);
str = null; null; // "hello"객체의 강력한 참조
String str1 = wf.get (); // "hello"객체가 재활용되지 않은 경우, str1은 "hello"객체를 나타냅니다.
// "hello"객체가 재활용되지 않으면 rq.poll ()가 null을 반환합니다.
참조 <? ref = rq.poll ();
위의 코드에서는 두 곳에주의를 기울이십시오. "hello"객체가 재활용되지 않으면 wf.get ()는 "hello"문자열 객체를 반환하고 rq.poll ()는 null을 반환하고 wf.get ()가 반환됩니다. null, rq.poll ()는 참조 객체를 반환하지만이 참조 객체의 STR 객체에 대한 언급은 없습니다 (PhantomReference는 약점 및 소프트로 회의와 다릅니다).
인용 메커니즘 및 복잡한 데이터 구조의 공동 적용
GC 메커니즘, 기준 메커니즘 및 참조 큐와 결합하여 메모리 오버 플로우를 방지하는 복잡한 데이터 유형을 구현할 수 있습니다.
예를 들어 Softreference에는 캐시 시스템을 구축하는 특성이 있으므로 해시 테이블과 함께 간단한 캐시 시스템을 구현할 수 있습니다. 이를 통해 많은 정보가 캐시 될 수있을뿐만 아니라 메모리 누출로 인해 Java Virtual Machine이 메모리가 발생하지 않도록합니다. 이 캐싱 메커니즘은 메모리 객체가 수명주기가 길고 메모리 객체를 생성하는 시간이 캐시 목록 커버 이미지 등과 같이 비교적 길어지는 상황에 특히 적합합니다. 수명주기가 길지만 메모리 객체를 생성하는 오버 헤드가 크지 않은 경우에는 약점을 사용하면 메모리 관리가 향상 될 수 있습니다.
SofthashMap의 소스 코드 사본이 첨부되어 있습니다.
코드 사본은 다음과 같습니다.
패키지 com. ***.
// : softhashmap.java
java.util을 가져옵니다.
java.lang.ref를 가져옵니다.
android.util.log 가져 오기;
공개 클래스 SOFTHASHMAP 확장 AbstractMap {
/** SOFTREFERENCE을 보유 할 내부 해시 맵.
개인 최종지도 HASH = New Hashmap ();
/** 내부적으로 보유 할 "하드"참조의 수.
개인 최종 int hard_size;
/** 하드 참조의 FIFO 목록, 마지막 액세스 순서.
개인 최종 LinkedList Hardcache = New LinkedList ();
/** 청소 된 SOFTREFERENCE 객체에 대한 참조 대기열.
비공개 참조 큐 큐 = 새로운 참조 Queue ();
// 강한 참조 번호
public softhashmap () {this (100);
public softhashmap (int hardsize) {hard_size = hardsize};
공개 객체 get (Object Key) {
객체 결과 = null;
// 우리는 그 키로 표시되는 softreference를 얻습니다
softreference soft_ref = (softreference) hash.get (키);
if (soft_ref! = null) {
// softreference에서 우리는 값을 얻을 수 있습니다.
//지도에 있지 않은 경우 NULL이거나 제거되었습니다.
// 아래 정의 된 ProcessQueue () 메소드
결과 = soft_ref.get ();
if (result == null) {
// 값이 수집 된 경우
// 해시 맵에서 항목.
해시 (키);
} 또 다른 {
// 이제이 개체를 하드의 시작에 추가합니다.
// 참조 대기열. 하나의 참조가 더 많이 발생할 수 있습니다
// 한 번은 FIFO 대기열의 조회가 느리기 때문에
// 우리는 매번 검색하고 싶지 않습니다.
// 복제.
// 메모리에서 최근 사용 객체를 유지합니다
hardcache.addfirst (결과);
if (hardcache.size ()> hard_size) {
// 하드 _size보다 긴 목록이면 마지막 항목을 제거합니다.
Hardcache.removelast ();
}
}
}
반환 결과;
}
/** 우리는 우리 자신의 서브 클래스를 포함하는 SoftReference를 정의합니다.
가치뿐만 아니라 찾기가 더 쉽게
해시 맵의 입구는 수집 된 쓰레기입니다.
개인 정적 클래스 SoftValue 확장 SOFTREFORENSE {
개인 최종 객체 키; // 항상 데이터 멤버를 최종적으로 만듭니다
/** 외부 클래스가 개인 데이터에 액세스 할 수 있다는 것을 알고 있습니까?
내면의 회원과 방법은 몰랐습니까?
나는 접근 할 수있는 내면의 내부 클래스 일 뿐이라고 생각했다.
외부 클래스의 개인 정보
내면의 내부 클래스의 개인 구성원에 액세스하십시오.
수업. */
private softValue (Object K, Object Key, ReferenceQueue Q) {
슈퍼 (k, q);
이 .key = 키;
}
}
/** 여기서 우리는 참조 큐를 거치고 쓰레기를 제거합니다.
해시 맵에서 SoftValue 객체를 수집하여 보았습니다
SoftValue.key 데이터 멤버 사용 */
public void processqueue () {
SoftValue SV;
while ((sv = (softValue) queue.poll ())! = null) {
if (sv.get () == null) {
log.e ( "processqueue", "null");
} 또 다른 {
log.e ( "Processqueue", "Not Null");
}
해시 (sv.key); // 개인 데이터에 액세스 할 수 있습니다!
log.e ( "softhashmap", "release" + sv.key);
}
}
/** 여기서 우리는 키, 값 쌍을 사용하여 해시 맵에 넣습니다.
소프트 값 객체 */
공개 객체 풋 풋 (객체 키, 객체 값) {
Processqueue (); // 수집 된 값을 먼저 버리십시오
log.e ( "softhashmap", " + 키에 넣음);
return hash.put (키, 새로운 softValue (값, 키, 큐));
}
공개 객체 제거 (객체 키) {
Processqueue (); // 수집 된 값을 먼저 버리십시오
return hash.remove (키);
}
public void clear () {
hardcache.clear ();
Processqueue (); // 수집 된 값을 쓰레기를 버립니다
해시 ();
}
public int size () {
Processqueue (); // 수집 된 값을 먼저 버리십시오
return hash.size ();
}
공개 세트 entryset () {
// 아니요, 당신은 그렇게하지 않을 수 있습니다 !!!
새로운 UnsupportedOperationException ()을 던지십시오.
}
}