Jvm의 메모리 구조

Jvm 메모리 모델

1. JDK 아키텍처

이미지-20230328211704374

위의 JDK 아키텍처 다이어그램에서 볼 수 있듯이:

  • JDK에는 Java의 공통 도구 개발 키트 및 jre가 포함되어 있습니다.
  • jre는 jvm 및 Java 코어 클래스 라이브러리를 포함하는 Java 런타임 환경입니다.
  • jvm은 바이트코드 파일을 운영 체제가 인식할 수 있는 기계 코드로 해석하는 역할을 합니다.

2. 자바 언어의 크로스 플랫폼 특성

이미지-20230328211946472

자바 파일을 컴파일하여 클래스 파일을 만든 후 jdk가 장착된 운영 체제에서 실행할 수 있는 이유는 무엇입니까? 이는 운영 체제마다 다른 jvm 구현이 있고 jvm이 컴파일된 바이트 코드 파일을 운영 체제가 인식하고 실행할 수 있는 기계 코드로 변환하는 일을 담당하기 때문입니다.

3. Jvm 메모리 모델

수학 클래스 선언

 
 

자바

코드 복사

public class Math { ​    public static final int initData = 10; ​    public static User user = new User(); ​    public static String s = new String(); ​    public int compute(){        int a = 1;        int b = 2;        int c = (a + b) *10;        return c;   } ​    public static void main(String[] args) {        Math math = new Math(); ​        math.compute(); ​        System.out.println(s.getClass().getClassLoader());   } }

이미지-20230328212341950

Jvm의 메모리 모델은 위 그림과 같이 주로 메소드 영역, 힙 공간, 가상 머신 스택, 로컬 메소드 스택, 프로그램 카운터로 구분되며, 다음으로 각 영역에 대해 자세히 설명하겠습니다.

3.1 가상 머신 스택

스택 공간은 스레드 전용입니다.새 스레드를 생성할 때마다 jvm은 전용 스택 메모리를 할당합니다.스레드가 메서드를 실행할 때 메서드의 각 실행은 스택 프레임을 형성합니다.입력된 메서드 선입선출의 원칙에 따라 스택의 맨 아래로 푸시됩니다.

스택 프레임에는 무엇이 있습니까? 주로 로컬 변수 테이블, 피연산자 스택, 동적 링크 및 메소드 종료를 저장합니다. 먼저 math.class 파일에서 디스어셈블리 파일을 생성하여 이러한 메모리 영역을 이해합니다.

 
 

자바스크립트

코드 복사

javap -c Math.class > Math.txt

이 지침 후 Math 클래스에서 생성된 디스어셈블리 파일의 compute 메서드 내용은 다음과 같습니다.

이미지-20230719090732174

여기서 계산 방법의 지침을 주로 살펴보십시오. iconst_1은 피연산자 스택 공간에 1을 넣는 것이고, istore_1은 로컬 변수 테이블의 인덱스 1 위치에 숫자 1을 저장하는 것입니다. 즉, a=1, iconst_2 2를 피연산자 스택에 넣는 것입니다. istore_2는 로컬 변수 테이블의 인덱스 2에 2를 저장합니다. 즉, b=2, iload_1은 로컬 변수 테이블의 인덱스 1에서 피연산자를 가져오고, iload_2는 iadd는 두 개의 숫자를 더하고, bipush 10은 8비트 부호 있는 정수 10을 피연산자 스택에 푸시하고, imul은 곱셈 연산을 수행하고, istore_3은 i의 곱셈 결과를 로컬 변수 테이블의 인덱스 3.

iload_3은 로컬 변수 인덱스 3에서 값을 로드하고 ireturn은 결과를 반환합니다.

iconst_1 int 유형 상수 1을 피연산자 스택에 푸시합니다.

istore_1은 지역 변수 1에 int 유형 값을 저장합니다.

iconst_2 int 유형 상수 2를 피연산자 스택에 푸시합니다.

iload_1은 로컬 변수 1에서 int 유형 값을 로드합니다.

iload_2는 로컬 변수 2에서 int 유형 값을 로드합니다.

iadd는 int 유형 추가를 수행합니다.

bipush는 8비트 부호 있는 정수를 스택에 푸시합니다.

imul은 int 유형의 곱셈을 수행합니다.

istore_3은 int 유형 값을 지역 변수 인덱스 3에 저장합니다.

ireturn은 메서드에서 int 유형의 데이터를 반환합니다.

3.2 네이티브 메서드 스택

로컬 메소드 스택은 쓰레드가 로컬 메소드를 실행할 때 가상머신이 열어주는 공간인 가상머신 스택과 동일하지만 여기서의 메소드는 로컬 메소드이고 메소드 구현은 c나 C++로 구현된다. .

3.3 프로그램 카운터

프로그램 카운터도 스레드 전용입니다.주요 목적은 스레드 컨텍스트가 전환될 때 코드의 실행 위치를 기록하는 것입니다.바이트코드 인터프리터가 작동 중일 때 이 카운터의 값을 변경하여 실행할 다음 바이트코드를 선택합니다. 명령, 분기, 루핑, 점핑, 예외 처리, 스레드 복구와 같은 기본 기능은 모두 이 카운터에 의존하여 완료해야 합니다.

3.4 방법 영역

JDK1.8과 JDK1.7은 메서드 영역의 개념이 달라서 JDK1.7에서는 우리 메서드 영역을 영구 생성(permanent generation)이라고 하고 메모리 영역은 JVM에서 제공하는 반면 JDK1.8에서는 메서드 영역이라고 합니다. metaspace , 물리적 머신의 직접 메모리를 사용하고 주로 메서드 영역에 상수, 정적 변수 및 클래스 정보를 저장합니다.

3.5힙

힙은 가상 머신이 시작될 때 생성되며 Java 가상 머신에서 관리하는 가장 큰 메모리 조각입니다. 주로 객체 인스턴스를 저장하는 데 사용됩니다. 거의 모든 객체 인스턴스가 여기에 메모리를 할당합니다. Java 힙이 주요 영역입니다. 가비지 컬렉터에 의해 관리되므로 종종 GC 힙이라고 합니다. 힙에 인스턴스 할당을 완료할 메모리가 없고 힙을 더 이상 확장할 수 없으면 OutOfMemoryError 예외가 발생합니다.

3.6 상수 풀

3.6.1 클래스 상수 풀 및 런타임 상수 풀

클래스 상수 풀은 클래스 파일의 자원 창고로 이해할 수 있습니다. 클래스 파일에 포함된 클래스 버전, 필드, 메서드, 인터페이스 및 기타 설명 정보 외에도 다양한 리터럴(Literal) 및 기호 참조 ( 기호 참조) .

리터럴 수량: 등호 오른쪽에 있는 숫자 및 문자열입니다. 예: int a=1 여기서 1은 리터럴 수량입니다.

기호 참조: 기호 참조는 직접 참조와 관련된 컴파일 원리의 개념으로 주로 다음 세 가지 유형의 상수를 포함합니다.

  • 클래스 및 인터페이스의 정규화된 이름

  • 필드 이름 및 설명자

  • 메서드 이름 및 설명자

    예를 들어 a in a=1은 com.lx.Math 클래스의 전체 경로와 같은 기호 참조이며 main, test 등과 같은 클래스의 메서드를 포함합니다. ()는 UTF8 형식의 설명자이며 이들은 기호 참조입니다.

    이러한 상수 풀은 이제 정적 정보입니다. 런타임에 메모리에 로드될 때만 이러한 기호는 해당 메모리 주소 정보를 갖습니다. 이러한 상수 풀이 메모리에 로드되면 런타임 상수 풀이 됩니다. 해당 기호는 프로그램에서 참조됩니다 . 언제 로드하거나 실행 중일 때 메모리 영역에 로드된 코드에 대한 직접 참조로 변환되며 이를 동적 연결이라고 합니다. 예를 들어 main()의 심볼 참조는 런타임에 메모리에서 main() 메서드의 특정 코드 주소로 변환되며 직접 참조는 주로 개체 헤더의 유형 포인터를 통해 변환됩니다.

3.6.2 문자열 상수 풀

3.6.2.1 문자열 상수 풀의 설계 아이디어

  1. 문자열의 할당은 다른 객체 할당과 마찬가지로 많은 시간과 공간적 비용을 소모하며, 가장 기본적인 데이터 타입으로 빈발하는 문자열이 많이 생성되어 프로그램의 성능에 큰 영향을 미친다.

  2. 성능을 개선하고 메모리 오버헤드를 줄이기 위해 JVM은 문자열 상수를 인스턴스화할 때 일부 최적화를 수행했습니다.

    • 버퍼와 유사한 문자열에 대한 문자열 상수 풀 생성
    • 문자열 상수를 생성할 때 먼저 문자열 상수 풀에 해당 문자열이 존재하는지 여부를 쿼리합니다.
    • 문자열이 있으면 참조 인스턴스를 반환하고, 없으면 문자열을 인스턴스화하여 풀에 넣습니다.

3.6.2.2 문자열 상수 풀 위치

Jdk1.6 이전: 영구 생성이 있고 런타임 상수 풀은 영구 생성에 있으며 런타임 상수 풀에는 문자열 상수 풀이 포함됩니다.

Jdk1.7: 영구 세대가 있지만 점차 "영구 세대에서 제거"되었습니다. 문자열 상수 풀은 영구 세대의 런타임 상수 풀에서 힙으로 분리됩니다.

Jdk1.8 이상: 영구 생성 없음, 런타임 상수 풀은 메타스페이스에 있고 문자열 상수 풀은 여전히 ​​힙에 있음

문자열 상수 풀이 jdk1.6 이후 힙에 있음을 증명하는 방법은 프로그램과 결과를 살펴보십시오.

 
 

아두이노

코드 복사

//-Xms6M -Xmx6M public class StringConstantPoolOOM {    public static void main(String[] args) {        ArrayList<String> list = new ArrayList<String>();        for (int i = 0; i < 10000000; i++) {            String str = String.valueOf(i).intern();            list.add(str);       }   } }

실행 결과: 힙 공간 부족

이미지-20230413203743811

다음으로 케이스를 통해 문자열 상수 풀을 더 분석해 보겠습니다.

세 가지 문자열 작업:

 
 

이것

코드 복사

String s = "luoxue";  // s指向常量池中的引用

이런 방식으로 생성된 문자열 객체는 상수 풀에만 있을 것입니다. 문자 그대로 "luoxue"가 있기 때문입니다. 객체 s를 생성할 때 JVM은 먼저 상수 풀로 이동하고 equals(key) 메서드를 전달하여 다음을 결정합니다. 동일한 Object가 있으면 상수 풀에서 객체의 참조를 직접 반환하고, 그렇지 않으면 상수 풀에서 새 객체를 만든 다음 참조를 반환합니다.

 
 

자바스크립트

코드 복사

String s1 = new String("luoxue");  // s1指向内存中的对象引用

이 메서드는 개체가 문자열 상수 풀에 존재하는지 확인하고 힙이 없으면 개체를 만들고 마지막으로 힙 메모리에 개체 참조를 반환합니다. 분석: 리터럴 "qianyue"가 있기 때문에 먼저 문자열 상수 풀에 문자열 "qianyue"가 있는지 확인하고, 존재하지 않으면 먼저 문자열 상수 풀에 문자열 개체를 생성한 다음 문자열 개체를 생성합니다. 메모리에서 문자열 개체 "qianyue"가 존재하는 경우 힙 메모리에 직접 문자열 개체 "qianyue"를 만들고 마지막으로 메모리의 참조를 반환합니다.

 
 

이것

코드 복사

       String s1 = new String("luoxue"); //s1->指向堆中的对象                String s2 = s1.intern();//s2->常量池中的对象 ​        System.out.println(s1);//luoxue        System.out.println(s2);//luoxue        System.out.println(s1==s2);//false        

String의 인턴 메소드는 네이티브 메소드로, 인턴 메소드가 호출될 때 풀에 이미 이 String 객체와 동일한 문자열(equals(oject) 메소드에 의해 결정됨)이 포함되어 있으면 풀의 문자열이 반환됩니다. 그렇지 않으면 인턴이 반환한 참조를 현재 문자열 s1로 가리킵니다.

 
 

이것

코드 복사

       //堆中创建了一个字符串hello,但是没有在字符串常量池中创建,s1指向堆中hello对象的地址        String s1 = new String("he")+new String("llo");        //发现常量池没有,直接返回堆中hello的地址        String s2 = s1.intern(); ​        System.out.println(s1);//hello        System.out.println(s2);//hello        System.out.println(s1==s2);//true

4. JVM 메모리 파라미터 설정

이미지-20230328221117284

Spring Boot 프로그램의 JVM 매개변수 설정 형식(Tomcat 시작은 bin 디렉토리의 catalina.sh 파일에 직접 추가됨):

 
 

이것

코드 복사

java ‐Xms2048M ‐Xmx2048M ‐Xmn1024M ‐Xss512K ‐XX:MetaspaceSize=256M ‐XX:MaxMetaspaceSize=256M ‐jar microservice‐eurek a‐server.jar

메타스페이스에 대한 두 가지 JVM 매개변수: -XX:MetaspaceSize=N 및 -XX:MaxMetaspaceSize=N

  • -XX: MaxMetaspaceSize: 메타스페이스의 최대값을 설정합니다. 기본값은 -1, 즉 제한이 없거나 로컬 메모리의 크기에 의해서만 제한됩니다.
  • -XX: MetaspaceSize: Fullgc(메타스페이스에는 고정된 초기 크기가 없음)를 트리거하는 메타스페이스의 초기 임계값을 바이트 단위로 지정합니다. 기본값은 21M입니다. 이 값에 도달하면 전체 gc가 트리거되어 유형을 언로드하고 수집기가 값 조정: 여유 공간이 많으면 적당히 낮추고, 여유 공간이 적으면 -XX:MaxMetaspaceSize(설정된 경우)를 초과하지 않고 적절하게 값을 높여야 합니다. 이는 이전 jdk 버전의 -XX:PermSize 매개변수와 다른 것으로, -XX:PermSize는 영구 생성의 초기 용량을 나타냅니다.

메타스페이스의 크기를 조정하려면 Full GC가 필요하기 때문에 비용이 매우 많이 드는 작업이며, 어플리케이션 시작 시 Full GC가 많이 발생하는 경우는 대개 영구 세대나 메타스페이스의 크기 조정 때문입니다. 일반적으로 JVM 매개변수에서 MetaspaceSize와 MaxMetaspaceSize를 같은 값으로 설정하고 초기 값보다 크게 설정하는 것이 일반적으로 권장됩니다.8G 물리적 메모리가 있는 시스템의 경우 이 두 값은 일반적으로 256M로 설정됩니다.

Supongo que te gusta

Origin blog.csdn.net/BASK2312/article/details/131811565
Recomendado
Clasificación