JVM 내부 구조 다이어그램
Java 가상 머신은 주로 방법 영역, 힙, Java 스택, PC 레지스터 및 로컬 메소드 스택의 5 가지 영역으로 나뉩니다. JVM 구조에 대한 몇 가지 중요한 문제를 살펴 보겠습니다.
1. 어느 지역이 공유됩니까? 어떤 것이 사적입니까?
Java 스택, 로컬 메소드 스택 및 프로그램 카운터는 사용자 스레드가 시작되고 종료 될 때 설정 및 파괴됩니다.
각 스레드에는 이러한 독립 영역이 있습니다. 방법 영역과 힙은 전체 JVM 프로세스의 모든 스레드에서 공유됩니다.
2. 방법 영역에 무엇이 저장됩니까? 재활용됩니까?
방법 영역은 저장된 방법 정보 및 코드가 아닙니다. 동시에, Runtime Constant Pool이라는 하위 지역에서 클래스 파일의 상수 테이블의 다양한 상징적 참조와 직접 참조를 번역했습니다. 이 정보는 힙의 클래스 객체를 통해 인터페이스로 액세스합니다.
유형 정보는 방법 영역에 저장되지만 재활용되지만 재활용 조건은 비교적 엄격합니다.
(1)이 클래스의 모든 사례는 재활용되었습니다
(2)이 클래스를로드하는 클래스 로더가 재활용되었습니다
(3)이 클래스의 클래스 객체는 어느 곳에서나 참조되지 않습니다 (클래스 .forname 반사 액세스 포함)
3. 방법 영역에서 상수 풀의 내용이 변경되지 않았습니까?
방법 영역의 런타임 상수 풀은 클래스 파일의 정적 상수 풀에 데이터를 저장합니다. 컴파일 타임에 생성 된 다양한 문자 및 상징적 참조를 저장하는 것 외에도 번역 된 직접 참조도 포함되어 있습니다. 그러나 이것이 런타임 중에 상수 풀이 변경되지 않는다는 의미는 아닙니다. 예를 들어, 실행 중에는 문자열의 인턴 메소드를 호출하여 새로운 문자열 상수를 수영장에 넣을 수 있습니다.
패키지 com.cdai.jvm; 공개 클래스 runtimeConstantPool {public static void main (String [] args) {String S1 = new String ( "Hello"); 문자열 s2 = 새 문자열 ( "hello"); System.out.println ( "인턴 전, S1 == S2 :" + (S1 == S2)); s1 = s1.intern (); s2 = s2.intern (); System.out.println ( "인턴 후, S1 == S2 :" + (S1 == S2)); }}
4. 모든 객체 인스턴스가 힙에 할당됩니까?
탈출 분석 기술의 점진적인 성숙함으로 인해 스택 할당 및 스칼라 교체 최적화 기술은 "힙에 할당 된 모든 객체"가 덜 절대적으로 만들어졌습니다.
소위 탈출은 객체의 포인터가 여러 메소드 나 스레드로 참조 될 때이 포인터가 탈출한다고 말합니다.
일반적으로 말하면, 자바 물체는 힙에 할당되며 객체의 포인터 만 스택에 저장됩니다. 메소드 실행 중 (메소드 외부에 노출됨) 로컬 변수가 탈출되지 않는다고 가정하면 스택에 직접 할당 된 다음 통화 스택에서 계속 실행됩니다. 방법을 실행 한 후 스택 공간이 재활용되고 로컬 변수도 재활용됩니다. 이것은 힙에 많은 수의 임시 물체의 할당을 줄이고 GC 재활용의 효율을 향상시킵니다.
또한 탈출 분석은 탈출되지 않은 로컬 변수에 대한 잠금 장치를 생략 하고이 변수에 소유 한 잠금 장치를 생략합니다.
탈출 분석 방법을 활성화 할 때는 JVM 시작 매개 변수를 추가하십시오 : -xx : +discapeanalysis? ESCAPEANALYSISTEST.
5. 힙의 물체에 액세스하는 방법은 몇 개입니까?
(1) 포인터에 직접 액세스 할 수 있습니다
스택의 참조는 힙의 물체에 대한 포인터를 저장하고 액세스 속도가 빠른 한 번에 물체를 찾을 수 있습니다.
그러나 힙에서 물체가 움직일 때 (각 객체가 쓰레기 수집 중에 종종 이동) 스택의 포인터 변수 값도 변경해야합니다. 현재 JVM 핫스팟은이 방법을 채택합니다.
(2) 취급에 대한 간접 접근
스택의 참조는 핸들 풀의 핸들을 가리 며이 핸들의 값을 통해 객체에 액세스됩니다. 따라서, 핸들은 보조 포인터와 같으며, 이는 직접 포인터 위치보다 느리게 객체에 액세스하기 위해 두 개의 포지셔닝이 필요하지만 객체가 힙에서 움직일 때 스택에서 참조 된 값을 변경할 필요가 없습니다.
JVM을 넘치는 방법
Java Virtual Machines의 5 가지 메모리 영역의 역할을 이해 한 후에는 어떤 상황에서 이러한 영역이 넘쳐나는 상황에서 계속 배우겠습니다.
1. 가상 머신 매개 변수 구성
-XMS : 초기 힙 크기, 기본값은 1/64의 물리적 메모리 (<1GB)입니다. 무료 힙 메모리가 40%미만인 경우 기본값 (MinHeapFreeRatio 매개 변수를 조정할 수 있습니다). JVM은 -XMX의 최대 한계까지 힙을 증가시킵니다.
-XMX : 최대 힙 크기, 기본값 (MaxHeapFreeRatio 매개 변수를 조정할 수 있음)이 자유 힙 메모리가 70%보다 크면 JVM은 -XM의 최소 한계까지 힙을 줄입니다.
-XSS : 각 스레드의 스택 크기. JDK5.0 이후, 각 스레드의 스택 크기는 1m이고 과거에는 각 스레드의 스택 크기가 256k입니다. 응용 프로그램 스레드의 필요한 메모리 크기에 따라 적절하게 조정해야합니다. 동일한 물리적 메모리 에서이 값을 줄이면 더 많은 스레드가 생성 될 수 있습니다. 그러나 운영 체제는 여전히 프로세스의 스레드 수에 제한이 있으며 약 3000에서 5000 사이의 경험 값을 사용하여 무한히 생성 할 수 없습니다. 일반적으로 스택이 그다지 깊지 않으면 128k가 충분해야합니다. 대규모 응용 프로그램의 경우 256K가 권장됩니다. 이 옵션은 성능에 큰 영향을 미치며 엄격한 테스트가 필요합니다.
-xx : permsize : 영구 생성 (Perm Gen) 초기 값을 설정합니다. 기본값은 물리적 메모리의 1/64입니다.
-xx : maxpermsize : 영구 생성의 최대 값을 설정하십시오. 물리적 기억의 1/4.
2. 방법 영역 오버 플로우
방법 영역은 클래스의 관련 정보를 보유하고 있기 때문에 너무 많은 클래스를로드하면 메소드 영역이 오버플로됩니다. 여기서 우리는 JDK Dynamic Proxy와 Cglib Proxy의 두 가지 방법을 통해 방법 영역을 오버플로하려고 노력합니다.
2.1 JDK 동적 프록시
패키지 com.cdai.jvm.OverFlow; import java.lang.reflect.invocationHandler; import java.lang.reflect.method; java.lang.reflect.proxy import; public class methodareaoverflow {static interface oomiinterface {} 정적 클래스 oomobject emplements oomiinterface {} 정적 클래스 oomobject2 구현 oomiinterface {} public static void main (string [] args) {최종 oomobject 객체 = new Oomobject (); while (true) {Oomiinterface proxy = (Oomiinterface) proxy.newproxyInstance (thread.currentthread (). getContextClassLoader (), OomObject.class.getInterfaces (), 새로운 invocationHandler () {@Override public avoke (객체 proxy, method, args). System.out.println ( "interceptor1이 작동합니다"); System.out.println (proxy.getClass ()); System.out.println ( "proxy1 :" + proxy); oomiinterface proxy2 = (Oomiinterface) proxy.newproxyInstance (thread.currentthread (). getContextClassLoader (), oomobject.class.getInterfaces (), new invocationHandler () {@override public object (대상 대리, 대상, 대상 [] args) "); return method.invoke (object, args);}}); System.out.println (proxy2.getClass ()); System.out.println ( "proxy2 :" + proxy2); }}} 프록시 클래스를 생성하기 위해 proxy.newinstance () 메소드를 계속 호출하지만 JVM에는 메모리 오버플로가 없습니다.
각 호출은 프록시 클래스의 다른 인스턴스를 생성하지만 프록시 클래스의 클래스 객체는 변경되지 않았습니다. 대리입니까?
클래스에 프록시 클래스의 클래스 객체에 대한 캐시가 있습니까? 구체적인 이유는 후속 "JDK Dynamic Proxy 및 CGLIB"에서 자세히 분석됩니다.
2.2 CGLIB 에이전트
CGLIB는 또한 프록시 클래스의 클래스 객체를 캐시하지만 클래스 객체를 캐시하지 않도록 구성 할 수 있습니다.
이런 식으로, 방법 영역을 넘치는 목적은 대리 클래스를 반복적으로 생성함으로써 달성 될 수있다.
패키지 com.cdai.jvm.OverFlow; import java.lang.reflect.method; import net.sf.cglib.proxy.enhancer; import net.sf.cglib.proxy.methodinterceptor; import net.sf.cglib.proxy.methodproxy; public class methodareaoverflow2 {static class oomobject {} public static void main (string [] args) {while (true) {enhancer enher = new enhancer (); Enhancer.SetSuperClass (OomObject.class); Enhancer.setUsecache (false); Enhancer.setCallback (new MethodInterceptor () {@override public object intercept (Object Obj, Method [] args, methodProxy proxy) 던지기 가능 {return method.invoke (obj, args);}}); oomobject proxy = (oomobject) enhancer.create (); System.out.println (proxy.getClass ()); }}}
3. 힙 오버플로
힙 오버플로는 비교적 간단합니다. 큰 배열 객체를 만들어 힙을 넘칠 수 있습니다.
패키지 com.cdai.jvm.OverFlow; 공개 클래스 heapoverflow {private static final int mb = 1024 * 1024; @SuppressWarnings ( "Unused") public static void main (String [] args) {byte [] bigmemory = new Byte [1024 * mb]; }}
4. 스택 오버플로
스택 오버플로도 일반적입니다. 때때로 우리가 쓰는 재귀 호출에 올바른 종료 조건이 없으면 메소드가 계속 재발하고 스택의 깊이가 계속 증가하고 결국 스택 오버플로가 발생합니다.
패키지 com.cdai.jvm.OverFlow; 공개 클래스 stackoverflow {private static int stackdepth = 1; public static void stackoverflow () {stackdepth ++; stackoverflow (); } public static void main (String [] args) {try {stackoverflow (); } catch (예외 e) {System.err.println ( "스택 깊이 :" + stackDepth); e.printstacktrace (); }}}