>  기사  >  Java  >  JAVA Virtual Machine(JVM)에 대한 자세한 소개(6) - 바이트코드 실행 엔진

JAVA Virtual Machine(JVM)에 대한 자세한 소개(6) - 바이트코드 실행 엔진

王林
王林앞으로
2019-08-24 15:57:142712검색

JVM의 실행 엔진은 Java 코드를 실행할 때 일반적으로 해석된 실행(인터프리터를 통한 실행)과 컴파일된 실행(Just-In-Time 컴파일러를 통해 생성된 로컬 코드 실행)의 두 가지 옵션을 갖습니다.

스택 프레임

정의:

스택 프레임은 가상 머신의 메소드 호출 및 메소드 실행을 지원하는 데 사용되는 데이터 구조입니다.

기능:

각 메소드의 호출 시작부터 실행 완료까지의 과정은 가상머신 스택에서 스택 프레임이 스택으로 푸시되어 스택 밖으로 팝되는 과정에 해당합니다. .

JAVA Virtual Machine(JVM)에 대한 자세한 소개(6) - 바이트코드 실행 엔진

특징:

(1) 스택 프레임에는 지역 변수 테이블, 피연산자 스택 등이 포함됩니다. 지역 변수 테이블이 얼마나 큰지, 피연산자 스택의 깊이는 컴파일 타임에 결정됩니다. 스택 프레임에 할당해야 하는 메모리 양은 프로그램 런타임 중에 변수 데이터의 영향을 받지 않기 때문입니다.

(2) 두 스택 프레임 간의 데이터 공유. 개념적 모델에서는 두 스택 프레임이 완전히 독립적이지만, 가상 머신 구현에서는 두 스택 프레임이 부분적으로 겹치도록 일부 최적화 처리가 수행됩니다. 이러한 방식으로 메소드 호출 시 추가 매개변수 복사 및 전달 없이도 데이터의 일부를 공유할 수 있습니다.

JAVA Virtual Machine(JVM)에 대한 자세한 소개(6) - 바이트코드 실행 엔진

(1) 지역변수 테이블

지역변수 테이블은 메소드 매개변수와 메소드 내에 정의된 지역변수를 저장하는 데 사용되는 변수값 저장공간의 집합이다.

//方法参数   
max(int a,int b)
int a;//全局变量
void say(){
   int b=0;//局部变量
 }

로컬 변수는 클래스 변수(정적으로 수정된 변수)와 다릅니다.

클래스 변수에는 초기 값을 할당하는 두 가지 프로세스가 있습니다. 준비 단계(초기 값을 시스템에 할당)와 초기화 단계(정의된 초기 값 할당) 프로그래머에 의해). 따라서 초기화 단계에서 클래스 변수에 값이 할당되지 않더라도 여전히 특정 초기 값을 갖는 것은 중요하지 않습니다.
단, 로컬 변수는 정의되어 있지만 초기값이 할당되지 않은 경우에는 사용할 수 없습니다.

(2) 작업 스택

메서드가 막 실행되기 시작하면 이 메서드의 피연산자 스택이 비어 있습니다. 메소드 실행 중에는 피연산자에 대한 다양한 바이트코드 명령이 있습니다. 스택에서 팝핑하고 스택으로 밀어넣는 작업이 있습니다.

예를 들어 다음과 같이 계산합니다.

int a=2+3

피연산자 스택의 맨 위에 가장 가까운 두 요소는 2와 3입니다. iadd 명령이 실행되면 2와 3이 스택에서 팝되어 추가되고 그 결과가 추가됩니다. 5 스택 위로 밀어 넣습니다.

(3) 동적 링크

클래스 파일의 상수 풀에는 많은 수의 기호 참조가 있습니다. 바이트코드의 메서드 호출 명령어는 상수 풀의 메서드를 가리키는 기호 참조를 다음과 같이 사용합니다. 매개변수. 이러한 기호 참조는 두 부분으로 나뉩니다.

정적 해상도: 클래스 로딩 단계 중 또는 처음 사용될 때 직접 참조로 변환됩니다. 동적 링크: 각 실행 중에 직접 참조로 변환됩니다.

(4) 반품 주소


当一个方法开始执行后,只有两种方式可以退出这个方法:正常退出、异常退出。无论采用何种退出方式,在方法退出之后,都需要返回到方法被调用的位置,程序才能继续执行。

当方法正常退出时

调用者的PC计数器作为返回地址。栈帧中一般会保存这个计数器值。

当方法异常退出时

返回地址是要通过异常处理器表来确定的。栈帧中一般不会保存这部分信息。

方法调用

方法调用是确定调用哪一个方法。

(1)解析

对“编译器可知,运行期不可变”的方法进行调用称为解析。符合这种要求的方法主要包括

静态方法,用static修饰的方法私有方法,用private修饰的方法

(2)分派

分派讲解了虚拟机如何确定正确的目标方法。分派分为静态分派和动态分派。讲解静动态分派之前,我们先看个多态的例子。

Human man=new Man();

在这段代码中,Human为静态类型,其在编译期是可知的。Man是实际类型,结果在运行期才可确定,编译期在编译程序的时候并不知道一个对象的实际类型是什么。

静态分派:

所有依赖静态类型来定位方法执行版本的分派动作称为静态分派。它的典型应用是重载。

public class StaticDispatch{  
   static abstract class Human{  
    }  
   static class Man extends Human{
    }
    static class Woman extends Human{
    }
    public void say(Human hum){  
        System.out.println("I am human");  
    }  
    public void say(Man hum){  
        System.out.println("I am man");  
    }  
    public void say(Woman hum){  
        System.out.println("I am woman");  
    }  
    
    public static void main(String[] args){  
        Human man = new Man();  
        Human woman = new Woman();  
        StaticDispatch sr = new StaticDispatch();  
        sr.say(man);  
        sr.say(woman);  
    }  
}

运行结果是:

I am human
I am human

为什么会产生这个结果呢?
因为编译器在重载时,是通过参数的静态类型而不是实际类型作为判断依据的。在编译阶段,javac编译器会根据参数的静态类型决定使用哪个重载版本,所以两个对say()方法的调用实际为sr.say(Human)。

动态分派:

在运行期根据实际类型确定方法执行版本的分派过程。它的典型应用是重写。

public class DynamicDispatch{  
   static abstract class Human{  
            protected abstract void say();
    }  
   static class Man extends Human{
            @Override
             protected abstract void say(){
             System.out.println("I am man");  
            }
    }
    static class Woman extends Human{
         @Override
             protected abstract void say(){
             System.out.println("I am woman ");  
            }
    }
    
    public static void main(String[] args){  
        Human man = new Man();  
        Human woman = new Woman();  
        man.say();
        woman.say();
        man=new Woman();
        man.say();
    }  
}

运行结果:

I am man
I am woman 
I am woman

这似乎才是我们平时敲的java代码。对于方法重写,在运行时才确定调用哪个方法。由于Human的实际类型是man,因此调用的是man的name方法。其余的同理。

动态分派的实现依赖于方法区中的虚方法表,它里面存放着各个方法的实际入口地址。如果某个方法在子类中被重写了,那子类方法表中的地址将会替换为指向子类实现版本的入口地址,否则,指向父类的实现入口。

单分派和多分派:

方法的接收者与方法的参数统称为方法的宗量,根据分派基于多少种宗量,分为单分派和多分派。

在静态分派中,需要调用者的实际类型和方法参数的类型才能确定方法版本,所以其是多分派类型。在动态分派中,已经知道了参数的实际类型,所以此时只需知道方法调用者的实际类型就可以确定出方法版本,所以其是单分派类型。综上,java是一门静态多分派,动态单分派的语言。

字节码解释执行引擎

虚拟机中的字节码解释执行引擎是基于栈的。下面通过一段代码来仔细看一下其解释的执行过程。

public int calc(){  
    int a = 100;  
    int b = 200;  
    int c = 300;  
    return (a + b) * c;  
}

第一步:将100入栈。

JAVA Virtual Machine(JVM)에 대한 자세한 소개(6) - 바이트코드 실행 엔진

2단계: 작업 스택에서 100을 팝하고 지역 변수에 저장합니다. 이후 200,300에도 동일하게 적용됩니다.

JAVA Virtual Machine(JVM)에 대한 자세한 소개(6) - 바이트코드 실행 엔진

3단계: 지역 변수 테이블의 100을 피연산자 스택의 맨 위에 복사합니다.

JAVA Virtual Machine(JVM)에 대한 자세한 소개(6) - 바이트코드 실행 엔진

4단계: 지역 변수 테이블의 200을 피연산자 스택의 맨 위에 복사합니다.

JAVA Virtual Machine(JVM)에 대한 자세한 소개(6) - 바이트코드 실행 엔진

5단계: 스택에서 100과 200을 꺼내고 정수 덧셈을 수행한 다음 마지막으로 결과 300을 스택에 다시 푸시합니다.

JAVA Virtual Machine(JVM)에 대한 자세한 소개(6) - 바이트코드 실행 엔진

6단계: 로컬 변수 테이블의 세 번째 숫자 300을 스택 맨 위에 복사합니다. 다음 단계는 스택에서 두 개의 300을 팝하고, 정수 곱셈을 수행하고, 최종 결과 90000을 스택에 푸시하는 것입니다.

JAVA Virtual Machine(JVM)에 대한 자세한 소개(6) - 바이트코드 실행 엔진

7단계: 메서드가 종료되고 피연산자 스택 맨 위에 있는 정수 값이 이 메서드의 호출자에게 반환됩니다.

JAVA Virtual Machine(JVM)에 대한 자세한 소개(6) - 바이트코드 실행 엔진

위 내용은 JAVA 가상 머신-바이트코드 실행 엔진에 대한 전체 소개입니다. 더 많은 관련 질문이 있는 경우 PHP 중국어 웹사이트를 방문하세요. #🎜 🎜#JAVA 비디오 튜토리얼

위 내용은 JAVA Virtual Machine(JVM)에 대한 자세한 소개(6) - 바이트코드 실행 엔진의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 csdn.net에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제
이전 기사:같음과 해시코드다음 기사:같음과 해시코드