정의 및 시연 된 Try-finally 절

Under The Hood에 오신 것을 환영합니다 . 이 칼럼에서는 Java 개발자가 실행중인 Java 프로그램 아래에서 클릭하고 윙윙 거리는 신비한 메커니즘을 엿볼 수 있습니다. 이번 달의 기사에서는 JVM (Java Virtual Machine)의 바이트 코드 명령어 세트에 대한 논의를 계속합니다. 그 초점은 JVM이 finally절과이 절과 관련된 바이트 코드를 처리하는 방식입니다 .

마지막으로 : 응원 할 사항

Java 가상 머신이 Java 프로그램을 나타내는 바이트 코드를 실행하면 여러 가지 방법 중 하나로 코드 블록 (두 개의 일치하는 중괄호 사이의 명령문)을 종료 할 수 있습니다. 우선 JVM은 코드 블록의 닫는 중괄호를 지나서 실행될 수 있습니다. 또는 break, continue 또는 return 문이 발생하여 블록 중간 어딘가에서 코드 블록 밖으로 점프 할 수 있습니다. 마지막으로 JVM이 일치하는 catch 절로 이동하거나 일치하는 catch 절이없는 경우 스레드를 종료하는 예외가 발생할 수 있습니다. 이러한 잠재적 인 종료 점이 단일 코드 블록 내에 존재하므로 코드 블록이 종료되는 방식에 관계없이 어떤 일이 발생했음을 쉽게 표현할 수있는 방법이 필요합니다. 자바에서 그러한 욕망은try-finally 절.

try-finally조항 을 사용하려면 :

  • try여러 출구 지점이있는 코드를 블록으로 묶습니다.

  • 블록이 finally어떻게 try종료 되더라도 반드시 발생해야하는 코드를 블록에 넣습니다 .

예를 들면 :

try {// 여러 종료 점이있는 코드 블록} finally {// try 블록이 종료 될 때 항상 실행되는 코드 블록, // try 블록이 종료되는 방법에 관계없이} 

블록 catch과 연관된 절이 있는 경우 다음 과 같이 모든 절 뒤에 절을 try넣어야합니다 .finallycatch

try {// 여러 종료 점이있는 코드 블록} catch (Cold e) {System.out.println ( "Caught cold!"); } catch (APopFly e) {System.out.println ( "Caught a pop fly!"); } catch (SomeonesEye e) {System.out.println ( "누군가의 눈을 잡았습니다!"); } finally {// try 블록이 어떻게 종료 되든 // try 블록이 종료 될 때 항상 실행되는 코드 블록. System.out.println ( "그것에 대해 응원 할 것이 있습니까?"); }

try블록 내에서 코드를 실행하는 동안 블록 catch과 관련된 절에 의해 처리되는 예외가 발생 try하면 finally절이 절 다음에 실행됩니다 catch. 예를 들어, 위 블록 Cold의 명령문 (표시되지 않음)을 실행하는 동안 예외가 발생 try하면 다음 텍스트가 표준 출력에 기록됩니다.

감기에 걸리다! 그거 응원해야할까요?

바이트 코드의 Try-finally 절

바이트 코드에서 finally절은 메서드 내에서 미니어처 서브 루틴으로 작동합니다. try블록 및 관련 catch절 내부의 각 종료 지점 에서 finally절에 해당하는 미니어처 서브 루틴 이 호출됩니다. finally절이 완료된 후 ( finally예외를 던지거나 return, continue 또는 break를 실행하지 않고 절의 마지막 문을 지나서 실행하는 한) 미니어처 서브 루틴 자체가 반환됩니다. 미니어처 서브 루틴이 처음에 호출 된 지점을 지나서 실행이 계속되므로 try적절한 방식으로 블록을 종료 할 수 있습니다.

JVM이 미니어처 서브 루틴으로 점프하도록하는 opcode는 jsr 명령어입니다. JSR 명령은 2 바이트 피연산자, 상기 위치 오프셋 얻어 JSR 소형 서브 루틴이 시작 명령. 두번째의 변형 JSR 명령이다 jsr_w 동일한 기능 수행 JSR을 하지만 넓은 (4 바이트) 피연산자 걸린다. JVM이 jsr 또는 jsr_w 명령어를 발견하면 반환 주소를 스택에 푸시 한 다음 미니어처 서브 루틴의 시작에서 실행을 계속합니다. 반환 주소는 바로 다음 바이트 코드의 오프셋 JSR 또는jsr_w 명령어 및 해당 피연산자.

미니어처 서브 루틴이 완료된 후 서브 루틴 에서 반환되는 ret 명령어를 호출합니다 . RET의 명령어는 하나의 피연산자, 상기 리턴 어드레스가 저장되어있는 로컬 변수의 인덱스 걸린다. finally절 을 처리하는 opcode 는 다음 표에 요약되어 있습니다.

마지막 절
Opcode 피연산자 기술
jsr branchbyte1, branchbyte2 반환 주소, 분기를 오프셋으로 푸시
jsr_w branchbyte1, branchbyte2, branchbyte3, branchbyte4 반환 주소를 푸시하고 넓은 오프셋으로 분기합니다.
ret 인덱스 지역 변수 인덱스에 저장된 주소로 돌아갑니다.

미니어처 서브 루틴을 Java 메소드와 혼동하지 마십시오. Java 메소드는 다른 명령 세트를 사용합니다. invokevirtual 또는 invokenonvirtual 과 같은 명령 은 Java 메소드를 호출하고 return , areturn 또는 ireturn 과 같은 명령 은 Java 메소드를 리턴합니다. JSR 명령은 Java 메소드가 호출되는 원인이되지 않습니다. 대신 동일한 메서드 내에서 다른 opcode로 점프합니다. 마찬가지로, ret 명령어는 메서드에서 반환되지 않습니다. 오히려 호출 jsr 명령어와 해당 피연산자 바로 뒤에 오는 동일한 메서드에서 opcode로 다시 반환됩니다 . 구현하는 바이트 코드finally절은 단일 메소드의 바이트 코드 스트림 내에서 작은 서브 루틴처럼 작동하기 때문에 미니어처 서브 루틴이라고합니다.

ret 명령어는 jsr 명령어에 의해 푸시 된 곳이기 때문에 스택에서 반환 주소를 팝해야 한다고 생각할 수 있습니다 . 하지만 그렇지 않습니다. 대신 각 서브 루틴이 시작될 때 반환 주소가 스택 상단에서 팝되어 나중에 ret 명령이 가져 오는 동일한 로컬 변수 인 로컬 변수에 저장됩니다 . 마지막 절 (따라서, 소형 서브 루틴) 자체가 예외를 던지거나 포함 할 수 있기 때문에 반송 주소를 사용하여 작업이 비대칭 방식이 필요하다 return, break또는 continue문. 이 가능성 때문에 jsr에 의해 스택에 푸시 된 추가 반환 주소경우는 여전히하지 않도록 명령은, 바로 스택에서 제거해야 finally조항이 종료되어와 break, continue, return, 또는 던져 예외입니다. 따라서 반환 주소는 finally절의 미니어처 서브 루틴 이 시작될 때 지역 변수에 저장됩니다 .

예를 들어, finallybreak 문으로 종료 되는 절을 포함하는 다음 코드를 고려하십시오 . 이 코드의 결과는 method에 전달 된 bVal 매개 변수에 관계없이 surpriseTheProgrammer()메서드가 false다음을 반환한다는 것입니다 .

static boolean surpriseTheProgrammer (boolean bVal) {while (bVal) {try {return true; } 마침내 {휴식; }} return false; }

위의 예는 반환 주소가 finally절의 시작 부분에있는 지역 변수에 저장되어야하는 이유를 보여줍니다 . finally절이 중단과 함께 종료 되기 때문에 ret 명령을 실행하지 않습니다 . 결과적으로 JVM은 " return true"문 을 완료하기 위해 다시 돌아 가지 않습니다 . 대신 문의 break닫는 중괄호를 지나서 및 드롭 다운 while됩니다. 다음 문장은 return falseJVM이하는 일인 ""입니다.

a로 표시되는 행동 finallyA를 종료 절 break또한으로 표시됩니다 finallyA를 종료하는 것이 절을 return하거나 continue, 또는 예외를 던져. finally이러한 이유로 절이 종료 되면 절 끝에 있는 ret 명령 finally이 실행되지 않습니다. 때문에 RET 명령이 실행되도록 보장 할 수 없습니다, 스택에서 반환 주소를 제거에 의존 할 수 없습니다. 따라서 반환 주소는 finally절의 미니어처 서브 루틴 시작 부분에있는 지역 변수에 저장됩니다 .

완전한 예를 들어, try두 개의 종료 점이 있는 블록 을 포함하는 다음 방법을 고려하십시오 . 이 예에서 두 종료점은 모두 return명령문입니다.

static int giveMeThatOldFashionedBoolean (boolean bVal) {try {if (bVal) {return 1; } return 0; } finally {System.out.println ( "구식입니다."); }}

위의 메서드는 다음 바이트 코드로 컴파일됩니다.

// try 블록의 바이트 코드 시퀀스 : 0 iload_0 // 로컬 변수 0 푸시 (제수로 전달 된 인수) 1 ifeq 11 // 로컬 변수 1 푸시 (피제수로 전달 된 인수) 4 iconst_1 // 푸시 int 1 5 istore_3 // int (1) 팝, 로컬 변수에 저장 3 6 jsr 24 // finally 절의 미니 서브 루틴으로 이동 9 iload_3 // 로컬 변수 3 푸시 (1) 10 ireturn // 상단에 int 반환 stack (the 1) 11 iconst_0 // Push int 0 12 istore_3 // Pop an int (the 0), store into local variable 3 13 jsr 24 // Jump to the mini-subroutine for the finally clause 16 iload_3 // Push local variable 3 (0) 17 ireturn // 스택 상단에 int 반환 (0) // 모든 종류의 예외를 포착하는 catch 절의 바이트 코드 시퀀스 // try 블록 내에서 throw됩니다. 18 astore_1 // throw 된 예외에 대한 참조를 팝합니다.store // into local variable 1 19 jsr 24 // jump to the mini-subroutine for the finally clause 22 aload_1 // push the reference (to the thrown exception) from // local variable 1 23 athrow // Rethrow the same exception / / finally 블록을 구현하는 미니어처 서브 루틴. 24 astore_2 // 반환 주소를 팝하고 지역 변수 2에 저장 25 getstatic # 8 // java.lang.System.out에 대한 참조 가져 오기 28 ldc # 1 // 상수 풀에서 푸시 30 invokevirtual # 7 // 호출 System.out.println () 33 ret 2 // 지역 변수 2에 저장된 반환 주소로 돌아 가기로컬 변수 2에 저장 25 getstatic # 8 // java.lang.System.out에 대한 참조 가져 오기 28 ldc # 1 // 상수 풀에서 푸시 30 invokevirtual # 7 // 호출 System.out.println () 33 ret 2 // 지역 변수 2에 저장된 반환 주소로 돌아갑니다.로컬 변수 2에 저장 25 getstatic # 8 // java.lang.System.out에 대한 참조 가져 오기 28 ldc # 1 // 상수 풀에서 푸시 30 invokevirtual # 7 // 호출 System.out.println () 33 ret 2 // 지역 변수 2에 저장된 반환 주소로 돌아갑니다.

try블록 의 바이트 코드 에는 두 개의 jsr 명령어가 포함됩니다 . 다른 jsr 명령어가 catch절에 포함되어 있습니다. 이 catch절은 컴파일러에 의해 추가됩니다. try블록 실행 중에 예외가 발생 하더라도 finally 블록은 계속 실행되어야하기 때문입니다. 따라서 catch절 은 절을 나타내는 미니어처 서브 루틴을 호출 finally한 다음 동일한 예외를 다시 발생시킵니다. giveMeThatOldFashionedBoolean()아래 표시된 메서드에 대한 예외 테이블은 주소 0과 17 ( try블록 을 구현하는 모든 바이트 코드) 사이에서 발생하는 모든 예외 catch가 주소 18에서 시작 하는 절에 의해 처리됨을 나타 냅니다.

예외 테이블 : from to target type 0 18 18 any 

finally절의 바이트 코드는 스택에서 반환 주소를 팝하고 지역 변수 2에 저장하는 것으로 시작합니다. finally절의 끝 에서 ret 명령어는 적절한 위치 인 로컬 변수 2에서 반환 주소를 가져옵니다.

HopAround : 자바 가상 머신 시뮬레이션

아래 애플릿은 일련의 바이트 코드를 실행하는 Java 가상 머신을 보여줍니다. 시뮬레이션의 바이트 코드 시퀀스는 아래 표시된 클래스 javachopAround()메서드 에 대해 컴파일러에 의해 생성되었습니다 .

class Clown {static int hopAround () {int i = 0; while (true) {try {try {i = 1; } finally {// 첫 번째 finally 절 i = 2; } 나는 = 3; 반환 i; // 계속 때문에 완료되지 않음} finally {// 두 번째 finally 절 if (i == 3) {continue; // 계속하면 return 문이 무시됩니다.}}}}}

메서드 에 javac대해 생성 된 바이트 코드 hopAround()는 다음과 같습니다.