Java 언어에서 사용할 수없는 바이트 코드 기능


146

Java 언어로 할 수없는 Java 바이트 코드로 현재 할 수있는 (Java 6) 작업이 있습니까?

둘 다 튜링이 완료된 것이므로 "할 수 있음"을 "훨씬 더 빠르거나 더 나은 방법으로 또는 다른 방식으로 수행 할 수 있습니다"라고 읽습니다.

특정 바이트 코드 invokedynamic가 향후 버전을 제외하고 Java를 사용하여 생성 할 수없는 과 같은 추가 바이트 코드를 생각하고 있습니다.


3
"사물"을 정의하십시오. 결국 Java 언어와 Java 바이트 코드는 모두 Turing complete입니다.
Michael Borgwardt

2
진짜 질문입니까? Java 대신 Jasmin을 사용하여 바이트 코드에 유리한 프로그래밍이 있습니까?
피터 로리

2
rolC ++로 작성할 수없는 어셈블러에서 와 같이 .
Martijn Courteaux

1
그것은 컴파일 할 수 없습니다 매우 가난한 최적화 컴파일러의 (x<<n)|(x>>(32-n))A를 rol명령.
Random832

답변:


62

내가 아는 한 Java 6에서 지원하는 바이트 코드에는 Java 소스 코드에서도 액세스 할 수없는 주요 기능이 없습니다. 주된 이유는 Java 바이트 코드가 Java 언어를 염두에두고 설계 되었기 때문입니다.

그러나 최신 Java 컴파일러에서 생성하지 않은 일부 기능이 있습니다.

  • ACC_SUPER플래그 :

    이것은 클래스에서 설정할 수있는 플래그 invokespecial이며이 클래스에 대해 바이트 코드 의 특정 코너 경우를 처리 하는 방법을 지정합니다 . 모든 현대 Java 컴파일러 (여기서 "modern"이> = Java 1.1, 올바르게 기억한다면)에 의해 설정되며 고대 Java 컴파일러만이 설정되지 않은 클래스 파일을 생성했습니다. 이 플래그는 이전 버전과의 호환성을 위해서만 존재합니다. Java 7u51부터 ACC_SUPER는 보안상의 이유로 완전히 무시됩니다.

  • jsr/의 ret바이트 코드.

    이 바이트 코드는 서브 루틴 (주로 finally블록 을 구현하기 위해)을 구현하는 데 사용되었습니다 . Java 6 이후더 이상 생성되지 않습니다 . 더 이상 사용되지 않는 이유는 정적 확인을 크게 얻지 못하기 때문에 복잡합니다. 즉, 사용하는 코드는 거의 항상 오버 헤드가 거의없는 일반적인 점프로 다시 구현할 수 있습니다.

  • 클래스에 리턴 유형 만 다른 두 개의 메소드가 있습니다.

    Java 언어 사양에서는 반환 유형 다른 경우 (예 : 동일한 이름, 동일한 인수 목록 등) 동일한 클래스에서 두 개의 메서드를 허용하지 않습니다 . 그러나 JVM 스펙에는 그러한 제한이 없으므로 클래스 파일 에는 두 가지 메소드 포함될 있으므로 일반 Java 컴파일러를 사용하여 이러한 클래스 파일을 생성 할 방법은 없습니다. 이 답변 에는 좋은 예 / 설명 있습니다.


5
다른 답변을 추가 할 수도 있지만 정식 답변을 드릴 수도 있습니다. 바이트 코드로 된 메소드의 서명에 반환 유형이 포함되어 있다고 언급 할 수 있습니다 . 즉, 정확히 동일한 매개 변수 유형을 갖지만 리턴 유형이 다른 두 개의 메소드를 가질 수 있습니다. 이 토론을보십시오 : stackoverflow.com/questions/3110014/is-this-valid-java/…
Adam Paynter

8
거의 모든 문자로 클래스, 메소드 및 필드 이름을 가질 수 있습니다. 나는 "필드들"에 공백과 하이픈이있는 하나의 프로젝트를 진행했습니다. : P
Peter Lawrey

3
@ 피터 : 파일 시스템 문자의 말하기, 나는에 클래스 이름었던 난독으로 실행 a과 다른 AJAR 파일 내부. 누락 된 클래스가 어디에 있는지 깨닫기 전에 Windows 컴퓨터 에서 압축 풀 때 약 30 분이 걸렸습니다 . :)
Adam Paynter

3
@JoachimSauer : JVM 스펙, 75 페이지 의역 : 클래스 이름, 메소드, 필드, 지역 변수를 포함 할 수 있는 것을 제외하고 문자를 '.', ';', '[', 또는 '/'. 메소드의 이름은 동일하지만, 그들은 또한 포함 할 수 없습니다 '<''>'. ( 명시적인 예외 와 정적 생성자를 제외 <init>하고 <clinit>) 사양을 엄격하게 준수하면 클래스 이름이 실제로 훨씬 더 제한적이지만 제약 조건은 적용되지 않습니다.
leviathanbadger

3
@JoachimSauer : 또한 문서화되지 않은 내 자신의 추가 : 자바 언어에는 "throws ex1, ex2, ..., exn"메소드 서명의 일부로 포함됩니다 . 재정의 된 메서드에는 예외 throwing 절을 추가 할 수 없습니다. 그러나 JVM은 덜 신경 쓰지 못했습니다. 따라서 finalJVM 은 물론 RuntimeExceptions 및 Errors를 제외하고 예외없이 메소드 만 보장합니다 . 확인 된 예외 처리에 대해 너무 많은 : D
leviathanbadger

401

Java 바이트 코드로 꽤 오랫동안 작업 하고이 문제에 대한 추가 연구를 한 후 내 결과를 요약 한 것입니다.

수퍼 생성자 또는 보조 생성자를 호출하기 전에 생성자에서 코드를 실행하십시오.

JPL (Java Programming Language)에서 생성자의 첫 번째 명령문은 수퍼 생성자 또는 동일한 클래스의 다른 생성자를 호출해야합니다. JBC (Java byte code)에는 해당되지 않습니다. 바이트 코드 내에서는 다음과 같은 경우 생성자 전에 코드를 실행하는 것이 합법적입니다.

  • 이 코드 블록 다음에 호환 가능한 다른 생성자가 호출됩니다.
  • 이 호출은 조건문 내에 없습니다.
  • 이 생성자 호출 전에 생성 된 인스턴스의 필드를 읽지 않고 해당 메소드를 호출하지 않습니다. 이것은 다음 항목을 의미합니다.

수퍼 생성자 또는 보조 생성자를 호출하기 전에 인스턴스 필드 설정

앞에서 언급했듯이 다른 생성자를 호출하기 전에 인스턴스의 필드 값을 설정하는 것이 합법적입니다. 6 이전의 Java 버전에서이 "기능"을 이용할 수있는 레거시 해킹도 있습니다.

class Foo {
  public String s;
  public Foo() {
    System.out.println(s);
  }
}

class Bar extends Foo {
  public Bar() {
    this(s = "Hello World!");
  }
  private Bar(String helper) {
    super();
  }
}

이런 식으로 슈퍼 생성자가 호출되기 전에 필드를 설정할 수 있지만 더 이상 가능하지 않습니다. JBC에서는이 동작을 계속 구현할 수 있습니다.

수퍼 생성자 호출 분기

Java에서는 다음과 같은 생성자 호출을 정의 할 수 없습니다

class Foo {
  Foo() { }
  Foo(Void v) { }
}

class Bar() {
  if(System.currentTimeMillis() % 2 == 0) {
    super();
  } else {
    super(null);
  }
}

그러나 Java 7u23까지 HotSpot VM의 검증기는이 확인을 놓쳤습니다. 이것이 가능했던 이유입니다. 이것은 여러 코드 생성 도구에서 일종의 핵으로 사용되었지만 더 이상 이와 같은 클래스를 구현하는 것은 합법적이지 않습니다.

후자는이 컴파일러 버전의 버그 일뿐입니다. 최신 컴파일러 버전에서는 다시 가능합니다.

생성자가없는 클래스 정의

Java 컴파일러는 항상 모든 클래스에 대해 하나 이상의 생성자를 구현합니다. Java 바이트 코드에서는 필요하지 않습니다. 리플렉션을 사용할 때에도 생성 할 수없는 클래스를 만들 수 있습니다. 그러나 sun.misc.Unsafe계속 사용하면 그러한 인스턴스를 작성할 수 있습니다.

서명은 동일하지만 리턴 유형이 다른 메소드 정의

JPL에서 메소드는 이름 및 원시 매개 변수 유형으로 고유 한 것으로 식별됩니다. JBC에서는 원시 반품 유형이 추가로 고려됩니다.

이름이 아니라 유형별로 다른 필드를 정의하십시오.

클래스 파일은 다른 필드 유형을 선언하는 한 동일한 이름의 여러 필드를 포함 할 수 있습니다. JVM은 항상 필드를 이름 및 유형의 튜플이라고합니다.

선언되지 않은 확인 된 예외를 포착하지 않고 던져

Java 런타임 및 Java 바이트 코드는 확인 된 예외의 개념을 인식하지 못합니다. 확인 된 예외가 발생하면 항상 포착되거나 선언되었는지 확인하는 것은 Java 컴파일러뿐입니다.

람다 식 외부에서 동적 메서드 호출 사용

소위 동적 메소드 호출 은 Java의 람다 표현식뿐만 아니라 무엇이든 사용할 수 있습니다. 이 기능을 사용하면 예를 들어 런타임시 실행 로직을 전환 할 수 있습니다. JBC로 요약되는 많은 동적 프로그래밍 언어는 이 명령을 사용하여 성능향상 시켰습니다 . Java 바이트 코드에서는 Java 7에서 람다 식을 에뮬레이션 할 수 있습니다. 여기서 JVM은 이미 명령을 이해하는 동안 컴파일러에서 동적 메서드 호출을 사용할 수 없습니다.

일반적으로 합법적이지 않은 식별자 사용

메서드 이름에 공백과 줄 바꿈을 사용하는 것을 좋아 한 적이 있습니까? 코드 검토를위한 JBC와 행운을 만드십시오. 식별자에 대한 유일한 불법 문자는 ., ;, [/. 또한, 방법은 이름이되지 않은 <init>또는 <clinit>포함 할 수 없습니다 <>.

final매개 변수 또는 this참조 재 할당

final매개 변수는 JBC에 존재하지 않으므로 결과적으로 다시 지정할 수 있습니다. this참조를 포함한 모든 매개 변수 는 단일 메소드 프레임 내의 this색인 0에서 참조 를 재 지정할 수있는 JVM 내의 간단한 배열에만 저장됩니다 .

final필드 재 할당

최종 필드가 생성자 내에서 할당되는 한이 값을 다시 할당하거나 값을 전혀 할당하지 않는 것이 합법적입니다. 따라서 다음 두 생성자가 합법적입니다.

class Foo {
  final int bar;
  Foo() { } // bar == 0
  Foo(Void v) { // bar == 2
    bar = 1;
    bar = 2;
  }
}

들어 static final필드, 그것도 클래스 초기화 이외의 분야를 재 할당 할 수 있습니다.

생성자와 클래스 이니셜 라이저를 마치 메소드처럼 취급

이것은 개념적인 기능에 가깝지만 생성자는 일반 메소드와 JBC 내에서 다르게 취급되지 않습니다. 생성자가 다른 합법적 인 생성자를 호출하도록 보장하는 것은 JVM 검증 자뿐입니다. 그 외에는 생성자가 호출 <init>되고 클래스 이니셜 라이저가 호출 되는 것은 Java 명명 규칙 일뿐 <clinit>입니다. 이 차이점 외에도 메소드와 생성자의 표현은 동일합니다. Holger가 주석에서 지적했듯이 void이러한 메소드를 호출 할 수는 없지만 인수가 아닌 클래스 이니셜 라이저가 아닌 반환 유형으로 생성자를 정의 할 수도 있습니다 .

비대칭 레코드를 만듭니다 * .

레코드를 만들 때

record Foo(Object bar) { }

javac는라는 단일 필드 bar, 접근 자 메서드 bar()및 단일 생성자를 사용하여 클래스 파일을 생성합니다 Object. 또한에 대한 레코드 속성 bar이 추가됩니다. 레코드를 수동으로 생성하면 다른 생성자 모양을 만들어 필드를 건너 뛰고 접근자를 다르게 구현할 수 있습니다. 동시에 리플렉션 API가 클래스가 실제 레코드를 나타낸다고 믿도록 만들 수 있습니다.

수퍼 메소드 호출 (Java 1.1까지)

그러나 이것은 Java 버전 1 및 1.1에서만 가능합니다. JBC에서 메소드는 항상 명시적인 대상 유형으로 전달됩니다. 이것은

class Foo {
  void baz() { System.out.println("Foo"); }
}

class Bar extends Foo {
  @Override
  void baz() { System.out.println("Bar"); }
}

class Qux extends Bar {
  @Override
  void baz() { System.out.println("Qux"); }
}

뛰어 넘는 동안 Qux#baz호출 하도록 구현할 수있었습니다 . 직접 수퍼 클래스보다 다른 수퍼 메소드 구현을 호출하기 위해 명시 적 호출을 정의 할 수는 있지만, 1.1 이후 Java 버전에서는 더 이상 영향을 미치지 않습니다. Java 1.1에서이 동작은 직접 수퍼 클래스의 구현 만 호출하는 것과 동일한 동작을 가능하게하는 플래그를 설정하여 제어되었습니다 .Foo#bazBar#bazACC_SUPER

동일한 클래스에서 선언 된 메소드의 비가 상 호출 정의

Java에서는 클래스를 정의 할 수 없습니다

class Foo {
  void foo() {
    bar();
  }
  void bar() { }
}

class Bar extends Foo {
  @Override void bar() {
    throw new RuntimeException();
  }
}

위의 코드는 의 인스턴스에서 RuntimeExceptionwhen foo이 호출 될 때 항상 발생 Bar합니다. 에 정의 된 자체 메소드 Foo::foo를 호출하기 위해 메소드 를 정의 할 수 없습니다 . 으로 비 개인 인스턴스 방법, 호출은 항상 가상입니다. 바이트 코드로, 하나는하지만 사용하는 호출을 정의 할 수 있습니다 직접 연결 연산 코드 에 메소드 호출 에 의 버전. 이 opcode는 일반적으로 수퍼 메소드 호출을 구현하는 데 사용되지만 설명 된 동작을 구현하기 위해 opcode를 재사용 할 수 있습니다. barFoobarINVOKESPECIALbarFoo::fooFoo

세밀한 유형 주석

Java에서는 주석이 @Target선언 한 주석 에 따라 주석이 적용됩니다 . 바이트 코드 조작을 사용하면이 컨트롤과 독립적으로 주석을 정의 할 수 있습니다. 또한 @Target주석이 두 요소 모두에 적용 되더라도 매개 변수에 주석을 달지 않고 매개 변수 유형에 주석을 달 수 있습니다 .

유형 또는 해당 구성원에 대한 속성을 정의하십시오.

Java 언어 내에서는 필드, 메소드 또는 클래스에 대한 주석 만 정의 할 수 있습니다. JBC에서는 기본적으로 모든 정보를 Java 클래스에 임베드 할 수 있습니다. 그러나이 정보를 사용하기 위해 더 이상 Java 클래스로드 메커니즘에 의존 할 수 없지만 메타 정보를 직접 추출해야합니다.

오버플로 및 암시 적 byte으로 short, charboolean값 할당

후자의 기본 유형은 JBC에서 일반적으로 알려져 있지 않지만 배열 유형 또는 필드 및 메소드 디스크립터에 대해서만 정의됩니다. 바이트 코드 명령어 내에서 이름이 지정된 모든 유형은 32 비트의 공백을 사용하여을 나타낼 수 있습니다 int. 공식적으로 만 int, float, longdouble유형 바이트 코드 내에 존재하는 모든 필요 JVM의 검증의 규정에 의한 명시 적 변환을.

모니터를 놓지 마십시오

synchronized블록은 실제로 두 개의 문, 획득 한 하나의 모니터를 해제하기로 구성되어 있습니다. JBC에서는 릴리스하지 않고 구매할 수 있습니다.

참고 : 최근 HotSpot 구현 IllegalMonitorStateException에서 메소드 자체가 예외로 종료되면 메소드 종료시 또는 암시 적으로 해제됩니다.

return형식 초기화에 둘 이상의 문 추가

Java에서는 사소한 유형의 초기화 프로그램조차도

class Foo {
  static {
    return;
  }
}

불법입니다. 바이트 코드에서 타입 이니셜 라이저는 다른 방법과 같이 취급됩니다. 즉, return 문은 어디에서나 정의 할 수 있습니다.

돌이킬 수없는 루프 만들기

Java 컴파일러는 루프를 Java 바이트 코드의 goto 문으로 변환합니다. 이러한 명령문은 Java 컴파일러가 절대로 반복 할 수없는 루프를 만드는 데 사용될 수 있습니다.

재귀 캐치 블록 정의

Java 바이트 코드에서 블록을 정의 할 수 있습니다.

try {
  throw new Exception();
} catch (Exception e) {
  <goto on exception>
  throw Exception();
}

synchronized모니터를 해제하는 동안 예외가이 모니터 해제 지시로 리턴되는 Java에서 블록을 사용할 때 유사한 명령문이 내재적으로 작성됩니다 . 일반적으로 이러한 명령어에서는 예외가 발생하지 않지만 명령이 사용되지 않는 경우 (예 : 사용되지 않음 ThreadDeath) 모니터는 계속 해제됩니다.

기본 방법을 호출

Java 컴파일러는 기본 메소드의 호출을 허용하기 위해 몇 가지 조건이 충족되어야합니다.

  1. 이 방법은 가장 구체적인 방법이어야합니다 ( 슈퍼 유형을 포함하여 모든 유형으로 구현되는 하위 인터페이스로 재정의 해서는 안 됨 ).
  2. 기본 메소드의 인터페이스 유형은 기본 메소드를 호출하는 클래스에 의해 직접 구현되어야합니다. 그러나 인터페이스가 인터페이스를 B확장 A하지만의 메소드를 재정의하지 않으면 A메소드를 계속 호출 할 수 있습니다.

Java 바이트 코드의 경우 두 번째 조건 만 계산됩니다. 그러나 첫 번째는 관련이 없습니다.

그렇지 않은 인스턴스에서 수퍼 메소드를 호출하십시오. this

Java 컴파일러는의 인스턴스에서 수퍼 (또는 인터페이스 기본값) 메소드 만 호출 할 수 있습니다 this. 그러나 바이트 코드에서는 다음과 유사한 유형의 인스턴스에서 super 메소드를 호출 할 수도 있습니다.

class Foo {
  void m(Foo f) {
    f.super.toString(); // calls Object::toString
  }
  public String toString() {
    return "foo";
  }
}

합성 멤버 액세스

Java 바이트 코드에서는 합성 멤버에 직접 액세스 할 수 있습니다. 예를 들어, 다음 예에서 다른 Bar인스턴스 의 외부 인스턴스에 액세스 하는 방법을 고려하십시오 .

class Foo {
  class Bar { 
    void bar(Bar bar) {
      Foo foo = bar.Foo.this;
    }
  }
}

이것은 일반적으로 모든 합성 분야, 클래스 또는 방법에 해당됩니다.

비동기 일반 유형 정보 정의

Java 런타임은 일반 유형을 처리하지 않지만 (Java 컴파일러가 유형 삭제를 적용한 후)이 정보는 여전히 메타 정보로 컴파일 된 클래스에 연결되며 리플렉션 API를 통해 액세스 할 수 있습니다.

검증기는 이러한 메타 데이터 String인코딩 된 값 의 일관성을 검사하지 않습니다 . 따라서 삭제와 일치하지 않는 일반 유형에 대한 정보를 정의 할 수 있습니다. 아쉽게도 다음과 같은 주장이 사실 일 수 있습니다.

Method method = ...
assertTrue(method.getParameterTypes() != method.getGenericParameterTypes());

Field field = ...
assertTrue(field.getFieldType() == String.class);
assertTrue(field.getGenericFieldType() == Integer.class);

또한 런타임 예외가 발생하도록 서명을 유효하지 않은 것으로 정의 할 수 있습니다. 이 예외는 정보가 게으르게 평가 될 때 처음으로 정보에 액세스 할 때 발생합니다. 오류가있는 주석 값과 유사합니다.

특정 방법에 대해서만 매개 변수 메타 정보 추가

Java 컴파일러를 사용하면 parameter플래그가 활성화 된 클래스를 컴파일 할 때 매개 변수 이름 및 수정 자 정보를 포함 할 수 있습니다. 그러나 Java 클래스 파일 형식에서이 정보는 메소드별로 저장되므로 특정 메소드에 대해 이러한 메소드 정보 만 임베드 할 수 있습니다.

혼란스러운 것들과 JVM 충돌

예를 들어, Java 바이트 코드에서 모든 유형의 메소드를 호출하도록 정의 할 수 있습니다. 일반적으로 검증자는 유형이 그러한 방법을 알지 못하면 불만을 제기합니다. 그러나 배열에서 알 수없는 메소드를 호출하면 일부 JVM 버전에서 검증자가 이것을 놓치고 명령이 호출되면 JVM이 종료되는 버그를 발견했습니다. 이것은 거의 기능이 아니지만 기술적으로 javac 컴파일 Java 로는 불가능합니다 . Java에는 이중 유효성 검사가 있습니다. 첫 번째 유효성 검사는 Java 컴파일러에 의해 적용되고 두 번째 유효성 검사는 클래스가로드 될 때 JVM에 의해 적용됩니다. 컴파일러를 건너 뛰면 검증기의 유효성 검사에서 약점을 찾을 수 있습니다. 그러나 이것은 기능보다는 일반적인 진술입니다.

외부 클래스가 없을 때 생성자의 수신자 유형에 주석 달기

Java 8부터 내부 클래스의 비 정적 메소드 및 생성자는 수신자 유형을 선언하고 이러한 유형에 주석을 달 수 있습니다. 최상위 클래스의 생성자는 가장 많이 선언하지 않으므로 수신자 유형에 주석을 달 수 없습니다.

class Foo {
  class Bar {
    Bar(@TypeAnnotation Foo Foo.this) { }
  }
  Foo() { } // Must not declare a receiver type
}

Foo.class.getDeclaredConstructor().getAnnotatedReceiverType()그러나 AnnotatedType표현을 반환 하므로 클래스 파일에 직접 생성자에 Foo대한 유형 주석을 포함시킬 수 있습니다 Foo.이 주석은 나중에 리플렉션 API에서 읽습니다.

사용되지 않은 / 레거시 바이트 코드 명령어 사용

다른 사람들이 그것을 지명했기 때문에 나는 그것을 포함시킬 것입니다. Java는 이전에는 JSRand RET문 으로 서브 루틴을 사용 하고 있었습니다. JBC는이 목적을 위해 자체 유형의 반송 주소도 알고있었습니다. 그러나 서브 루틴을 사용하면 정적 코드 분석이 지나치게 복잡 해져서 이러한 명령어가 더 이상 사용되지 않습니다. 대신 Java 컴파일러는 컴파일하는 코드를 복제합니다. 그러나 이것은 기본적으로 동일한 논리를 생성하므로 다른 것을 달성하기 위해 실제로 논리를 고려하지 않습니다. 마찬가지로 예를 들어NOOP바이트 코드 명령은 Java 컴파일러에서 사용되지 않지만 실제로는 새로운 것을 달성 할 수는 없습니다. 문맥에서 지적한 바와 같이, 이러한 언급 된 "기능 지침"은 이제 법적인 opcode 세트에서 제거되어 기능을 더 적게 만듭니다.


3
메소드 이름에 대해서는 이름을 <clinit>사용하여 메소드를 정의 <clinit>하지만 매개 변수를 승인하거나 void리턴 유형 이 아닌 메소드를 두 개 이상 가질 수 있습니다 . 그러나 이러한 메소드는별로 유용하지 않으며 JVM은 해당 메소드를 무시하고 바이트 코드를 호출 할 수 없습니다. 독자를 혼동시키는 것이 유일한 용도입니다.
Holger

2
방금 Oracle의 JVM이 메소드 종료시 릴리스되지 않은 모니터를 감지 IllegalMonitorStateException하고 monitorexit명령 을 생략 하면를 던졌습니다 . 그리고을 수행하지 못한 예외적 인 메소드 종료의 monitorexit경우 모니터를 자동으로 재설정합니다.
Holger

1
@Holger-몰랐습니다. 이전 JVM에서 가능하다는 것을 알았습니다 .Rockockit에는 이러한 종류의 구현을위한 자체 핸들러가 있습니다. 항목을 업데이트하겠습니다.
Rafael Winterhalter

1
JVM 사양에서는 그러한 동작을 요구하지 않습니다. 비표준 바이트 코드를 사용하여 매달려있는 고유 잠금을 만들려고했기 때문에 방금 발견했습니다.
Holger

3
Ok, 관련 스펙을 찾았습니다 .“ 구조적 잠금 은 메소드 호출 중에 지정된 모니터의 모든 종료가 해당 모니터의 이전 항목과 일치하는 상황입니다. Java Virtual Machine에 제출 된 모든 코드가 구조적 잠금을 수행 할 것이라는 보장이 없으므로 Java Virtual Machine의 구현은 허용되지만 구조적 잠금을 보장하는 다음 두 규칙을 모두 적용 할 필요는 없습니다. …”
Holger

14

다음은 Java 바이트 코드에서 수행 할 수 있지만 Java 소스 코드에서는 수행 할 수없는 기능입니다.

  • 메소드가 예외를 처리한다고 선언하지 않고 메소드에서 확인 된 예외를 발생시킵니다. 확인 및 확인되지 않은 예외는 JVM이 아닌 Java 컴파일러에 의해서만 확인되는 것입니다. 이 때문에 예를 들어 스칼라는 선언되지 않은 메소드에서 확인 된 예외를 던질 수 있습니다. Java 제네릭에는 sneaky throw 라는 해결 방법이 있습니다.

  • Joachim의 답변 에서 이미 언급했듯이 클래스에 리턴 유형 만 다른 두 개의 메소드가있는 경우 : Java 언어 스펙은 리턴 유형 다른 경우 동일한 클래스에서 두 메소드를 허용하지 않습니다 (예 : 동일한 이름, 동일한 인수 목록, ...). 그러나 JVM 스펙에는 그러한 제한이 없으므로 클래스 파일 에는 두 가지 메소드 포함될 있으므로 일반 Java 컴파일러를 사용하여 이러한 클래스 파일을 생성 할 방법이 없습니다. 이 답변 에는 좋은 예 / 설명 있습니다.


4
이 참고 자바의 첫 번째 일을 할 수있는 방법. 때로는 비열한 던지기 라고도합니다 .
Joachim Sauer

이제 몰래! : D 공유해 주셔서 감사합니다.
Esko Luontola

나는 당신 Thread.stop(Throwable)이 교활한 던지기 에도 사용할 수 있다고 생각합니다 . 나는 이미 연결된 것이 더 빠르다고 가정합니다.
바트 반 Heukelom

2
Java 바이트 코드에서 생성자를 호출하지 않으면 인스턴스를 만들 수 없습니다. 검증기는 초기화되지 않은 인스턴스를 사용하려고 시도하는 모든 코드를 거부합니다. 객체 역 직렬화 구현은 생성자 호출없이 인스턴스를 작성하기 위해 원시 코드 헬퍼를 사용합니다.
홀거

클래스 Foo 확장 오브젝트의 경우 Object에 선언 된 생성자를 호출하여 Foo를 인스턴스화 할 수 없습니다. 검증자는 거부합니다. Java의 ReflectionFactory를 사용하여 이러한 생성자를 만들 수는 있지만 바이트 코드 기능은 아니지만 Jni에 의해 실현됩니다. 당신의 대답은 틀렸고 Holger는 맞습니다.
Rafael Winterhalter

8
  • GOTO레이블과 함께 사용하여 자신 만의 제어 구조를 만들 수 있습니다 (등 이외 for while)
  • this메소드 내 에서 로컬 변수를 대체 할 수 있습니다
  • 이 두 가지를 결합하면 꼬리 호출 최적화 바이트 코드를 만들 수 있습니다 ( JCompilo 에서이 작업을 수행함 )

관련 포인트로 디버그로 컴파일하면 메소드의 매개 변수 이름을 얻을 수 있습니다 ( Paranamer는 바이트 코드를 읽음으로써이를 수행합니다)


당신은 어떻게 할 override은이 지역 변수를?
Michael

2
@Michael 재정의는 너무 강력한 단어입니다. 바이트 코드 수준에서 모든 로컬 변수는 숫자 인덱스로 액세스되며 기존 변수에 쓰는 것과 새 변수를 초기화하는 것 (분리 된 범위로)간에 차이가 없습니다. 두 경우 모두 로컬 변수에 대한 쓰기입니다. this변수 인덱스 제로를 가지지 만 인 외에 미리 초기화 된 this인스턴스 메소드를 입력 할 때 참조하여, 단지 로컬 변수이다. 따라서 다른 값을 쓸 수 있습니다 . 사용 방법에 따라 this범위를 끝내 거나 this변수를 변경하는 것처럼 작동 할 수 있습니다.
Holger

내가 참조! 정말 this재 할당 할 수 있습니까? 나는 그것이 의미하는 것이 무엇인지 궁금하게 생각했던 단어 재정의 일뿐이라고 생각합니다.
Michael

5

이 문서의 섹션 7A는 바이트 코드 기능 보다는 바이트 코드 함정 에 관한 것이지만 관심 있을 수 있습니다 .


재미있는 책이지만, 그러한 것들을 사용 하고 싶은 것처럼 보이지는 않습니다 .
바트 반 Heukelom

4

Java 언어에서 생성자의 첫 번째 명령문은 수퍼 클래스 생성자에 대한 호출이어야합니다. 바이트 코드에는 이러한 제한이 없으며 대신 규칙은 멤버에 액세스하기 전에 객체에 대해 수퍼 클래스 생성자 또는 동일한 클래스의 다른 생성자를 호출해야한다는 것입니다. 이것은 다음과 같은 더 많은 자유를 허용해야합니다.

  • 다른 객체의 인스턴스를 만들어 로컬 변수 (또는 스택)에 저장 한 후 다른 용도로 해당 변수에 참조를 유지하면서 수퍼 클래스 생성자에 매개 변수로 전달합니다.
  • 조건에 따라 다른 다른 생성자를 호출하십시오. 이 가능해야한다 : 어떻게 자바에서 조건부 다른 생성자를 호출하는 방법?

나는 이것을 테스트하지 않았으므로 내가 틀렸다면 정정하십시오.


수퍼 클래스 생성자를 호출하기 전에 인스턴스 멤버를 설정할 수도 있습니다. 그러나 그 전에는 필드를 읽거나 메소드를 호출 할 수 없습니다.
Rafael Winterhalter

3

일반 Java 코드가 아닌 바이트 코드로 할 수있는 것은 컴파일러없이로드하고 실행할 수있는 코드를 생성하는 것입니다. 많은 시스템에는 JDK가 아닌 JRE가 있으며 코드를 동적으로 생성하려면 사용하기 전에 Java 코드 대신 바이트 코드를 생성하는 것이 더 쉽지는 않지만 컴파일하는 것이 좋습니다.


6
그러나 컴파일러를 사용하지 않고 컴파일러를 사용하여 생성 할 수없는 것을 생성하지 않고 컴파일러를 건너 뜁니다.
바트 반 Heukelom

2

나는 I-Play 일 때 바이트 코드 최적화 프로그램을 작성했습니다 (J2ME 응용 프로그램의 코드 크기를 줄 이도록 설계되었습니다). 내가 추가 한 기능 중 하나는 인라인 바이트 코드 (C ++의 인라인 어셈블리 언어와 유사)를 사용하는 기능이었습니다. 값이 두 번 필요하기 때문에 DUP 명령어를 사용하여 라이브러리 메서드의 일부인 함수의 크기를 줄일 수있었습니다. 또한 0 바이트 명령이있었습니다 (char를 사용하는 메소드를 호출하고 int를 전달하려는 경우 캐스트 할 필요가 없다는 것을 알았습니다 .char (var)를 대체하기 위해 int2char (var)를 추가하면 제거됩니다. 코드의 크기를 줄이기위한 i2c 명령 또한 float a = 2.3; float b = 3.4; float c = a + b; 그리고 고정 소수점으로 변환되도록 만들었습니다 (빠르고 일부 J2ME는 그렇지 않습니다) 부동 소수점 지원).


2

Java에서 보호 된 메소드 (또는 다른 액세스 감소)로 공용 메소드를 대체하려고 시도하면 "약한 액세스 권한을 지정하려고합니다"라는 오류가 발생합니다. JVM 바이트 코드로 수행하면 검증 기가 적합하며 부모 클래스를 통해 이러한 메소드를 공용 인 것처럼 호출 할 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.