C 언어와 달리 Java 메모리(힙 메모리)의 할당 및 재활용은 JVM 가비지 수집기에 의해 자동으로 완료되며 이 기능은 프로그래머가 코드를 더 잘 작성하는 데 도움이 됩니다. Java GC에 대해 설명하기 위해 HotSpot 가상 머신을 예로 들었습니다.
JVM 메모리에 대한 기사에서 우리는 Java 힙이 모든 객체 인스턴스 및 배열을 포함하여 모든 스레드가 공유하는 메모리 영역이라는 것을 이미 알고 있습니다. 모든 메모리 할당은 힙에서 수행됩니다. 효율적인 가비지 수집을 수행하기 위해 가상 머신은 힙 메모리를 Young Generation, Old Generation 및 Permanent Generation의 세 영역으로 나눕니다.
New Generation은 Eden과 Survivor Space(S0, S1)로 구성되며 크기는 다음과 같이 지정됩니다. -Xmn 매개변수 Eden과 Survivor Space 사이의 메모리 크기 비율은 기본적으로 8:1이며, 이는 -XX:SurvivorRatio 매개변수를 통해 지정할 수 있습니다. 예를 들어, 새로운 세대가 10M이면 Eden에는 8M이 할당되고 S0은 S1에는 각각 1M이 할당됩니다.
에덴: 그리스어로 에덴동산을 의미합니다. 성경에서 에덴동산은 구약 창세기의 기록에 따르면 하나님께서 첫 번째 세계를 창조하셨습니다. 남자 아담이 아담의 갈비뼈 중 하나로 여자 하와를 창조하고 그들을 에덴동산에 두었습니다.
대부분의 경우 개체는 Eden에 할당됩니다. Eden에 공간이 충분하지 않으면 가상 머신이 -XX:+PrintGCDetails 매개변수를 제공하여 언제 인쇄할지 알려줍니다. 가비지 수집이 발생합니다.
생존자 : 생존자를 의미하며 신세대와 구세대 사이의 완충지대이다.
새 세대에서 GC(Minor GC)가 발생하면 살아남은 객체는 S0 메모리 영역으로 이동하고 Eden 영역은 지워집니다. S0이 S1 메모리 영역으로 이동됩니다.
살아남은 객체는 S0과 S1 사이를 반복적으로 이동합니다. 객체가 Eden에서 Survivor로 또는 Survivor 사이를 이동할 때 객체의 GC age가 자동으로 누적됩니다. GC age가 기본 임계값인 15를 초과하면 개체를 이전 세대로 이동하려면 -XX:MaxTenuringThreshold 매개 변수를 통해 GC 연령 임계값을 설정할 수 있습니다.
Old Generation의 공간 크기는 아직 살아 있는 객체를 저장하는 데 사용되는 두 매개 변수 -Xmx와 -Xmn의 차이입니다. 여러 개의 마이너 GC 이후. Old Generation에 공간이 부족할 경우 Major GC/Full GC가 실행되는데, 이는 일반적으로 Minor GC보다 10배 이상 느립니다.
JDK8 이전의 HotSpot 구현에서는 메소드 데이터, 메소드 정보(바이트코드, 스택 및 변수 크기), 런타임 등 클래스의 메타데이터가 상수 풀, 결정된 심볼 참조 및 가상 메소드 테이블은 영구 생성에 저장됩니다. 영구 생성의 기본 크기는 32비트의 경우 64M이고 -XX:MaxPermSize 매개변수를 통해 설정할 수 있습니다. 클래스는 다음과 같습니다. 메타데이터가 영구 생성 크기를 초과하면 OOM 예외가 발생합니다.
JDK8의 HotSpot에서 가상 머신 팀은 Java 힙에서 영구 생성을 제거하고 클래스의 메타데이터를 메타스페이스라고 불리는 로컬 메모리 영역(오프 힙 메모리)에 직접 저장했습니다.
이렇게 하면 어떤 이점이 있나요?
숙련된 학생들은 영구 세대의 조정 과정이 매우 어렵다는 것을 알게 될 것입니다. 영구 세대의 규모는 전체 수업 수, 규모 등 너무 많은 요소를 포함하기 때문입니다. 상수 풀과 메소드 수, 영구 생성의 데이터는 각 Full GC와 함께 이동할 수 있습니다.
JDK8에서는 클래스의 메타데이터가 로컬 메모리에 저장됩니다. 메타공간의 최대 할당 가능 공간은 시스템의 사용 가능한 메모리 공간이므로 영구 생성의 메모리 오버플로 문제를 피할 수 있지만 메모리 소비가 발생합니다. 모니터링이 필요합니다. 메모리 누수가 발생하면 많은 양의 로컬 메모리를 차지하게 됩니다.
ps: JDK7 이전의 HotSpot에서는 문자열 상수 풀의 문자열이 영구 생성에 저장되어 일련의 성능 문제와 메모리 오버플로 오류가 발생할 수 있습니다. JDK8에서는 문자열 참조만 문자열 상수 풀에 저장됩니다.
GC Action이 발생하기 전에 힙 메모리에 어떤 객체가 살아 있는지 확인하는 것이 일반적으로 두 가지가 있습니다. 방법: 참조 카운팅 및 사용 가능한 표현 분석 방법.
1. 참조 카운팅 방법
객체에 참조 카운터를 추가합니다. 객체가 참조할 때마다 카운터가 1씩 증가합니다. , 카운터는 1만큼 감소하고 카운터 값이 0인 개체는 더 이상 사용할 수 없음을 나타냅니다.
참조 카운팅 방법은 구현이 간단하고 효율적이지만 객체 간 상호 참조 문제를 해결할 수는 없습니다.
public class GCtest { private Object instance = null; private static final int _10M = 10 * 1 << 20; // 一个对象占10M,方便在GC日志中看出是否被回收 private byte[] bigSize = new byte[_10M]; public static void main(String[] args) { GCtest objA = new GCtest(); GCtest objB = new GCtest(); objA.instance = objB; objB.instance = objA; objA = null; objB = null; System.gc(); } }
-XX:+PrintGC 매개변수를 추가하면 실행 결과는 다음과 같습니다.
[GC (System.gc()) [PSYoungGen: 26982K->1194K(75776K)] 26982K->1202K(249344K), 0.0010103 secs]
从GC日志中可以看出objA和objB虽然相互引用,但是它们所占的内存还是被垃圾收集器回收了。
2、可达性分析法
通过一系列称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,搜索路径称为 “引用链”,以下对象可作为GC Roots:
本地变量表中引用的对象
方法区中静态变量引用的对象
方法区中常量引用的对象
Native方法引用的对象
当一个对象到 GC Roots 没有任何引用链时,意味着该对象可以被回收。
在可达性分析法中,判定一个对象objA是否可回收,至少要经历两次标记过程:
1、如果对象objA到 GC Roots没有引用链,则进行第一次标记。
2、如果对象objA重写了finalize()方法,且还未执行过,那么objA会被插入到F-Queue队列中,由一个虚拟机自动创建的、低优先级的Finalizer线程触发其finalize()方法。finalize()方法是对象逃脱死亡的最后机会,GC会对队列中的对象进行第二次标记,如果objA在finalize()方法中与引用链上的任何一个对象建立联系,那么在第二次标记时,objA会被移出“即将回收”集合。
看看具体实现
public class FinalizerTest { public static FinalizerTest object; public void isAlive() { System.out.println("I'm alive"); } @Override protected void finalize() throws Throwable { super.finalize(); System.out.println("method finalize is running"); object = this; } public static void main(String[] args) throws Exception { object = new FinalizerTest(); // 第一次执行,finalize方法会自救 object = null; System.gc(); Thread.sleep(500); if (object != null) { object.isAlive(); } else { System.out.println("I'm dead"); } // 第二次执行,finalize方法已经执行过 object = null; System.gc(); Thread.sleep(500); if (object != null) { object.isAlive(); } else { System.out.println("I'm dead"); } } }
执行结果:
method finalize is running I'm alive I'm dead
从执行结果可以看出:
第一次发生GC时,finalize方法的确执行了,并且在被回收之前成功逃脱;
第二次发生GC时,由于finalize方法只会被JVM调用一次,object被回收。
当然了,在实际项目中应该尽量避免使用finalize方法。
Java GC 的那些事(1)
Java GC的那些事(2)
以上就是Java GC 的那些事(1)的内容,更多相关内容请关注PHP中文网(www.php.cn)!