Java의 중요한 특징은 프로그래머가 스스로 메모리를 확보 할 수 있도록 쓰레기 수집기 (GC)를 통해 메모리 재활용을 자동으로 관리하는 것입니다. 이론적으로, 더 이상 Java에서 사용되지 않는 물체가 차지하는 모든 메모리는 GC에 의해 재활용 될 수 있지만 Java는 메모리 누출도 있지만 성능은 C ++와 다릅니다.
Java의 메모리 관리
Java의 메모리 누출을 이해하려면 먼저 Java의 메모리가 어떻게 관리되는지 알아야합니다.
Java 프로그램에서 우리는 일반적으로 새로운 것을 사용하여 메모리를 물체에 할당하며 이러한 메모리 공간은 힙에 있습니다 (힙).
예는 다음과 같습니다.
public class simple {public static void main (String args []) {object1 = new Object (); // obj1 Object2 = new Object (); // obj2 Object2 = Object1; //... 이번에는 OBJ2를 청소할 수 있습니다}}Java는 메모리 관리를 위해 지시 된 그래프를 사용합니다.
지시 된 그래프에서, 우리는 OBJ1에 액세스 할 수 있도록 호출하고, OBJ2는 도달 할 수 없으며, 도달 할 수없는 분명히 청소할 수 있습니다.
메모리의 출시, 즉 도달 할 수없는 물체를 청소하고 GC에 의해 결정되고 실행되므로 GC는 응용 프로그램, 인용, 인용 및 과제를 포함한 각 객체의 상태를 모니터링합니다. 객체를 공개하는 기본 원칙은 더 이상 객체를 사용하지 않는다는 것입니다.
객체는 널 값이 주어지며 다시는 호출되지 않았습니다.
다른 하나는 메모리 공간을 재 할당하는 객체에 새 값을 할당하는 것입니다.
일반적으로 힙에 물체를 할당하는 비용은 비교적 높지만 GC는이 작업을 최적화합니다. C ++에서는 힙에 메모리를 할당하면 할당 할 적절한 메모리를 찾습니다. 물체가 파괴되면이 메모리 조각을 재사용 할 수 있습니다. 자바에서는 긴 스트립을 원합니다. 새 개체가 할당 될 때마다 Java의 "Heap Pointer"는 할당되지 않은 영역으로 뒤로 이동합니다. 따라서 메모리 할당 Java의 효율은 C ++의 효율과 비슷합니다.
그러나 이러한 작업 방식에는 문제가 있습니다. 메모리가 자주 적용되면 자원이 소진됩니다. 현재 GC는 개입하여 공간을 되찾고 힙의 물체를 더 작게 만듭니다. 이런 식으로, 할당하기에 항상 충분한 메모리 공간이있을 것입니다.
기준 계수 방법 GC 정리시 : 참조가 새 개체에 연결되면 참조 수는 +1입니다. 참조가 범위를 떠나거나 NULL로 설정되면 참조 수는 -1입니다. GC 가이 카운트가 0이라는 것을 알면 소비하는 메모리를 재활용합니다. 이 오버 헤드는 참조 프로그램의 수명 내내 발생하며 원형 참조를 처리 할 수 없습니다. 따라서이 방법은 GC의 작동 방식을 설명하는 데 사용되며 Java 가상 시스템에서는 적용되지 않습니다.
대부분의 GC는 "살아있는"객체를 찾은 다음 "적응 형, 세대, 스톱 카피, 마크 클리언"가비지 수집기를 사용하는 데 기반을 둔 적응 형 청소 방법 (속도 향상을위한 기타 추가 기술)을 채택합니다. 나는 너무 많이 소개하지 않을 것입니다. 이것은이 기사의 초점이 아닙니다.
Java의 메모리 누출
자바의 메모리 누출, 광범위하고 평신도의 용어로는 더 이상 사용되지 않을 객체의 메모리를 재활용 할 수 없으며, 이는 메모리 누출 인 것입니다.
Java의 메모리 누출은 C ++의 메모리 누출과 다릅니다.
C ++에서는 메모리를 할당 한 모든 객체를 프로그래머가 더 이상 사용하지 않은 후에 수동으로 해제해야합니다. 따라서 각 클래스에는 청소 작업을 완료하는 소멸자가 포함됩니다. 특정 객체를 해제하는 것을 잊어 버리면 메모리 누출이 발생합니다.
그러나 Java에서는 스스로 메모리를 자유롭게 할 필요가 없으며 GC에 의해 쓸모없는 물체를 자동으로 정리하여 프로그래밍 작업을 크게 단순화합니다. 그러나 때로는 GC에서 더 이상 사용되지 않는 일부 물체를 해제 할 수 없으므로 메모리 누출이 발생합니다.
우리는 물체의 수명주기가 있고 일부는 길고 일부는 짧다는 것을 알고 있습니다. 긴 수명주기 객체가 짧은 수명 주기로 참조를 보유하면 메모리 누출이 가능합니다. 간단한 예를 들어 봅시다 :
공개 클래스 단순 {객체 객체; public void method1 () {object = new Object (); //... 다른 코드}}실제로, 우리는 그것이 Method1 () 방법에만 사용될 것으로 예상되며 다른 곳에서는 사용되지 않을 것으로 예상됩니다. 그러나 Method1 () 메소드가 실행되면 객체 객체에 의해 할당 된 메모리는 즉시 해제 될 수있는 객체로 간주되지 않으며 단순 클래스에 의해 생성 된 객체가 해제 된 후에 만 해제됩니다. 엄격하게 말하면, 이것은 메모리 누출입니다. 해결책은 메소드 1 () 메소드에서 객체를 로컬 변수로 사용하는 것입니다. 물론, 이것을 작성해야한다면 이것으로 변경할 수 있습니다.
공개 클래스 단순 {객체 객체; public void method1 () {object = new Object (); //... 다른 코드 객체 = null; }}이러한 방식으로 "NewObject ()"에 의해 할당 된 메모리는 GC에 의해 재활용 될 수있다.
이 시점에서 Java 메모리 누출이 더 명확해야합니다. 아래에 더 자세히 설명해 봅시다 :
힙에 할당 된 메모리가 해제되지 않으면이 메모리에 액세스하는 모든 방법이 삭제됩니다 (예 : 포인터 재 할당). 이것은 C ++와 같은 언어를위한 것입니다. Java의 GC는 이러한 상황을 처리하는 데 도움이되므로 관리 할 필요가 없습니다.
메모리 객체가 분명히 필요하지 않은 경우, 모든 언어에서 발생할 수있는 메모리 누출 인이 메모리와 액세스 방법 (참조)을 유지합니다. 프로그래밍시 조심하지 않으면 이런 일이 쉬우 며 심각하지 않으면 간단한 메모리 누출 일 수 있습니다.
메모리 누출이 발생하기 쉬운 몇 가지 예와 솔루션
위의 예와 같은 상황은 쉽게 일어날 수 있으며, 우리가 무시하고 메모리 누출을 일으키는 가장 가능성이 높은 상황이기도합니다. 해결책은 객체의 범위를 최소화하는 것입니다 (예 : AndroidStudio에서 위의 코드는 경고를 발행하고 제공된 제안은 클래스의 멤버 변수를 메소드의 로컬 변수로 다시 작성하고 수동으로 NULL 값을 설정하는 것입니다.
범위는 코드를 작성할 때 더 많은주의를 기울여야합니다. NULL 값을 수동으로 설정하면 Java 컨테이너 LinkedList 소스 코드에서 지정된 노드를 삭제하는 내부 방법을 살펴볼 수 있습니다 (참조 : Java의 LinkedList 소스 코드 해석 (JDK1.8) ) :
// 지정된 노드를 삭제하고 삭제 된 요소 값을 반환합니다 e unlink (node <e> x) {// 현재 값과 전면 및 후면 노드 최종 E 요소 = x.item을 가져옵니다. 최종 노드 <E> 다음 = X.Next; 최종 노드 <e> prev = x.prev; if (prev == null) {첫 번째 = 다음; // 이전 노드가 비어있는 경우 (예 : 현재 노드가 첫 번째 노드 인 경우) 다음 노드는 새 첫 번째 노드가됩니다} else {prev.next = next; // 이전 노드가 비어 있지 않으면 현재 다음 노드 x.prev = null을 가리 킵니다. } if (next == null) {last = prev; // 다음 노드가 비어있는 경우 (예 : 현재 노드가 꼬리 노드 인 경우), 이전 노드의 이전 노드는 새 꼬리 노드가됩니다} else {next.prev = prev; // 다음 노드가 비어 있지 않으면 다음 노드가 현재 노드 x.next = null로 향합니다. } x.item = null; 크기--; 모드 카운트 ++; 리턴 요소; }노드 간의 관계를 수정하는 것 외에도 값을 NULL에 할당하는 것입니다. GC가 청소를 시작할 때에 관계없이 쓸모없는 물체를 정시에 청소 가능한 물체로 표시해야합니다.
Java Container Arraylist는 어레이에서 구현된다는 것을 알고 있습니다 (참조 : Java의 Arraylist 소스 코드 해석 (JDK1.8) ). POP () (POP) 메소드를 작성하려면 다음과 같습니다.
public e pop () {if (size == 0) return null; else return (e) elementData [-size]; }쓰기 방법은 매우 간결하지만 여기서 메모리 오버플로가 발생합니다. ElementData [size-1]는 여전히 E- 타입 객체에 대한 참조를 보유하고 있으며 당분간 GC에 의해 재활용 될 수 없습니다. 다음과 같이 수정할 수 있습니다.
public e pop () {if (size == 0) return null; else {e e = (e) elementData [-크기]; ElementData [size] = null; 반환 e; }}코드를 작성할 때는 맹목적으로 단순성을 추구 할 수 없습니다. 첫 번째는 정확성을 보장하는 것입니다.
컨테이너 사용 중 메모리 누출
많은 기사에서는 다음과 같이 메모리 누출의 예를 볼 수 있습니다.
벡터 v = 새로운 벡터 (); for (int i = 1; i <100; i ++) {object o = new Object (); v.add (o); o = null; }많은 사람들이 처음에는 이해하지 못할 수도 있으므로 위의 코드를 다음과 같이 이해할 수 있습니다.
void method () {vector vector = new vector (); for (int i = 1; i <100; i ++) {Object Object = new Object (); vector.add (객체); 객체 = null; } //..여기서, 메모리 누출은 벡터 작동이 완료된 후 GC 작동이 발생하면이 일련의 객체를 재활용 할 수 없다는 사실을 나타냅니다. 전체 메소드 () 메소드가 실행 된 후에도 해당 객체를 여전히 재활용 할 수 있기 때문에 여기서 메모리 누출은 수명이 짧을 수 있습니다. 여기서 해결하는 것은 매우 간단합니다. 값을 null에 수동으로 할당하십시오.
void method () {vector vector = new vector (); for (int i = 1; i <100; i ++) {Object Object = new Object (); vector.add (객체); 객체 = null; } //.. //... V와 관련이없는 다른 작업위의 벡터는 구식이지만 오래된 예제를 사용한 메모리 누출에 대한 소개 일뿐입니다. 컨테이너를 사용하면 위의 예와 마찬가지로 메모리 누출을 쉽게 유발할 수 있습니다. 그러나 위의 예에서, 컨테이너 시간 동안의 방법의 로컬 변수로 인한 메모리 누출 효과는 크지 않지만 피해야합니다. 그러나이 컨테이너가 클래스의 멤버 변수 또는 정적 멤버 변수 인 경우 메모리 누출에 더 많은주의를 기울여야합니다.
다음은 컨테이너를 사용할 때 발생할 수있는 오류입니다.
public class collectionmemory {public static void main (String s []) {set <myobject> objects = new LinkedHashset <myObject> (); Objects.add (new myObject ()); Objects.add (new myObject ()); Objects.add (new myObject ()); Objects.add (new myObject ()); System.out.println (Objects.size ()); while (true) {objects.add (new myObject ()); }}} class myObject {// 기본 배열 길이를 99999로 설정하고 더 빨리 OutOfMemoryError 목록 <String> List = New ArrayList <> (99999);}이됩니다.위의 코드를 실행하면 오류가 매우 빨리보고됩니다.
3 스레드 "Main"java.lang.outofMemoryError : java.util.arraylist의 java 힙 공간 <init> (arraylist.java:152)의 com.anxpp.memory.myobject. <initmory.java:21) at com.anxpp.memory.collectionmemory.main (CollectionMemory.java:16)
Java 컨테이너에 대해 충분히 알고 있다면 위의 오류는 발생하지 않습니다. 다음은 Java 컨테이너를 소개하는 게시물도 있습니다.
컨테이너 세트는 고유 한 요소 만 저장하며 객체의 equals () 메소드를 통해 비교됩니다. 그러나 Java의 모든 클래스는 객체 클래스에 직간접 적으로 상속됩니다. 객체 클래스의 equals () 메소드는 객체의 주소를 비교합니다. 위의 예에서는 메모리 오버플로가 될 때까지 요소가 추가됩니다.
따라서 위의 예는 엄격하게 말하면, 컨테이너의 잘못된 사용으로 인한 메모리 오버플로입니다.
세트에 관한 한, remove () 메소드는 equals () 메소드를 사용하여 일치하는 요소를 삭제합니다. 객체가 올바른 equals () 메소드를 제공하는 경우이 객체를 수정 한 후 제거 (Objecto)를 사용하지 않으면 메모리 누출이 발생할 수도 있습니다.
Close () 메소드를 제공하는 다양한 객체
예를 들어, 데이터베이스 연결 (DataSourse.getConnection ()), 네트워크 연결 (소켓) 및 IO 연결을 사용하고 다른 프레임 워크를 사용할 때는 Close () 메소드 (또는 유사한 메소드)를 명시 적으로 호출하지 않는 한 GC에 의해 자동으로 재활용되지 않습니다. 실제로, 그 이유는 긴 수명 사이클 객체가 단기 수명주기 객체에 대한 참조를 가지고 있기 때문입니다.
많은 사람들이 최대 절전 모드를 사용했을 수도 있습니다. 데이터베이스를 운영 할 때 세션 factory를 통해 세션을 얻습니다.
세션 세션 = sessionfactory.opensession ();
완료 후 Close () 메소드를 호출하여 닫아야합니다.
session.close ();
SessionFactory는 긴 수명 객체이며 세션은 비교적 짧은 수명 객체이지만 프레임 워크는 이러한 방식으로 설계되었습니다. 세션을 얼마나 오래 사용할 것인지 알지 못하므로 더 이상 사용하지 않을시기를 결정할 수있는 방법 만 제공 할 수 있습니다.
Close () 메소드가 호출되기 전에 예외가 발생할 수 있으므로 메소드를 호출 할 수 없습니다. 우리는 일반적으로 Try Language를 사용한 다음 마지막으로 Close () 및 기타 청소 작업을 실행합니다.
{session = sessionfactory.opensession (); //...other operations} 마침내 {session.close (); }싱글 톤 모드로 인한 메모리 누출
많은 경우에, 우리는 수명주기를 전체 프로그램의 수명주기와 유사하게 간주 할 수 있으므로 수명이 길어진 대상입니다. 이 개체가 다른 객체에 대한 참조를 보유하고 있으면 메모리 누출도 발생하기 쉽습니다.
내부 클래스 및 외부 모듈에 대한 참조
실제로 원칙은 여전히 동일하지만 나타나는 방식은 다릅니다.
청소와 관련된 방법
이 섹션에서는 주로 GC () 및 Finalize () 메소드에 대해 설명합니다.
GC ()
프로그래머의 경우 GC는 기본적으로 투명하고 보이지 않습니다. GC를 실행하는 기능은 System.gc ()이며, 호출 후 쓰레기 수집기를 시작하고 청소를 시작합니다.
그러나 Java Language Specification 정의에 따르면이 기능은 JVM의 쓰레기 수집가가 실행되도록 보장하지 않습니다. 다른 JVM 구현자는 다른 알고리즘을 사용하여 GC를 관리 할 수 있기 때문입니다. 일반적으로 GC의 스레드는 우선 순위가 낮습니다.
JVM이 GC에 전화하기위한 많은 전략이 있습니다. 그들 중 일부는 메모리 사용이 특정 수준에 도달 할 때만 작동하기 시작합니다. 일부는 정기적으로 실행합니다. 일부는 GC를 원활하게 실행하고 일부는 인터럽트 방식으로 GC를 실행합니다. 그러나 일반적으로 말하면, 우리는 이것에 관심을 가질 필요가 없습니다. 일부 특정 상황에서 GC 실행은 응용 프로그램의 성능에 영향을 미칩니다. 예를 들어, 온라인 게임과 같은 실시간 웹 기반 시스템의 경우, 사용자는 GC가 갑자기 응용 프로그램 실행을 방해하고 쓰레기 수집을 수행하는 것을 원하지 않으면 GC의 매개 변수를 조정하여 GC가 세련된 방법으로 메모리를 자유롭게 해제하여 일련의 작은 단계로 분해 할 수 있습니다. Sun에서 제공하는 HotspotJVM 은이 기능을 지원합니다.
finalize ()
finalize ()는 객체 클래스의 메소드입니다.
C ++를 아는 사람들은 파괴자가 있다는 것을 알고 있지만 Finalize ()는 C ++의 소멸자와 결코 같지 않다는 점에 유의하십시오.
이것은 Java 프로그래밍 아이디어로 설명됩니다. GC가 객체가 차지하는 저장 공간을 확보 할 준비가되면 Finalize () 메소드가 먼저 호출되며 객체가 차지하는 메모리는 다음 GC 재활용 동작이 발생할 때만 재활용되므로 일부 청소 작업을 Finalize ()에 넣을 수 있습니다.
이 방법의 중요한 목적은 Java에서 비 Java 코드 (예 : C 및 C ++)를 호출 할 때 해당 메모리 적용 작업 (예 : C 's Malloc () 함수)를 이러한 비 Java 코드에서 사용될 수 있으며 이러한 비 Java 코드에서는 이러한 메모리에서 이러한 메모리가 효과적으로 출시되지 않으므로 Finalize () 메소드 및 호출 메소드를 자유롭게 사용할 수 있습니다 ().
따라서 Finalize ()는 일반 청소에 적합하지 않습니다.
그러나 때로는이 방법이 몇 가지 용도로 사용됩니다.
일련의 객체가있는 경우 객체 중 하나는 허위 상태가 있습니다. 우리 가이 대상을 처리했다면, 상태는 진실이 될 것입니다. 처리하지 않고 누락 된 객체를 피하기 위해 Finalize () 메소드를 사용할 수 있습니다.
클래스 myObject {부울 상태 = 거짓; Public Void Deal () {//... Some 처리 작업 상태 = True; } @override protected void finalize () {if (! state) {system.out.println ( "error :" + "객체가 처리되지 않음!"); }} // ...}그러나 여러 측면 에서이 방법은 사용하지 않고 중복으로 간주되는 것이 좋습니다.
일반적으로 메모리 누출은 인코딩 불량으로 인해 발생합니다. 우리는 더 합리적으로 청소하지 않은 것에 대해 JVM을 비난 할 수 없습니다.
요약
위의 내용은 Java 언어로 메모리 유출 된 코드에 대한 자세한 설명입니다. 모든 사람에게 도움이되기를 바랍니다. 관심있는 친구는이 사이트의 다른 관련 주제를 계속 참조 할 수 있습니다. 단점이 있으면 메시지를 남겨 두십시오. 이 사이트를 지원해 주신 친구들에게 감사드립니다!