Java 클래스 파일 생성이 결정적입니까?


94

사용하는 경우 동일한 JDK (즉, 같은 javac실행 파일을), 생성 된 클래스 파일이 항상 동일하다? 운영 체제 또는 하드웨어 에 따라 차이가있을 수 있습니까? JDK 버전을 제외하고 다른 요인으로 인해 차이가 발생할 수 있습니까? 차이점을 피하기위한 컴파일러 옵션이 있습니까? 이론상으로 만 차이가 있거나 Oracle의javac 실제로 동일한 입력 및 컴파일러 옵션에 대해 다른 클래스 파일을 생성합니까?

업데이트 1 클래스 파일을 실행할 수 있는지 여부가 아닌 생성 , 즉 컴파일러 출력에 관심 이 있습니다. 다양한 플랫폼에서 있습니다.

업데이트 2 'Same JDK'로 나도 같은 의미javac 실행 파일을 .

업데이트 3 Oracle 컴파일러의 이론적 차이와 실제 차이의 구별.

[편집, 의역 된 질문 추가]
"다른 플랫폼에서 실행할 때 동일한 javac 실행 파일이 다른 바이트 코드를 생성하는 상황은 무엇입니까?"


5
@Gamb CORA는 다른 플랫폼에서 컴파일 된 경우 바이트 코드가 정확히 동일하다는 것을 의미 하지 않습니다 . 즉, 생성 된 바이트 코드가 정확히 동일한 작업을 수행한다는 것입니다.
dasblinkenlight 2013

10
왜 신경 쓰나요? 이것은 XY 문제 같은 냄새가납니다 .
Joachim Sauer 2013

4
@JoachimSauer 바이너리의 버전을 관리한다면 고려하십시오. 소스 코드가 변경된 경우에만 변경 사항을 감지하고 싶을 수 있지만 JDK가 출력 바이너리를 임의로 변경할 수 있다면 이것이 현명한 생각이 아니라는 것을 알 것입니다.
RB.

7
@RB .: 컴파일러는 컴파일 된 코드를 나타내는 모든 준수 바이트 코드를 생성 할 수 있습니다. 실제로 일부 컴파일러 업데이트는 약간 다른 코드를 생성하는 버그를 수정합니다 (일반적으로 동일한 런타임 동작). 즉 , 소스 변경 을 감지하려면 소스 변경을 확인하십시오 .
Joachim Sauer 2013

3
@dasblinkenlight : 당신은 그들이 가지고 있다고 주장하는 대답이 실제로 정확하고 최신이라고 가정하고 있습니다 (질문이 2003 년에서 나온 것을 감안하면 의심 스럽습니다).
Joachim Sauer

답변:


68

다음과 같이합시다.

동일한 .class파일이 주어지면 동일한 파일을 두 번 생성하지 않는 완전히 일치하는 Java 컴파일러를 쉽게 생성 할 수 있습니다 .java.

모든 종류의 바이트 코드 구성을 조정하거나 단순히 내 메서드에 불필요한 속성을 추가하여이를 수행 할 수 있습니다 (허용됨).

사양 이 컴파일러가 바이트 단위 동일한 클래스 파일을 생성 하도록 요구 하지 않는다는 점을 감안할 때 이러한 결과 에 의존 하는 것을 피할 것 입니다.

그러나 몇 번 확인한 결과 동일한 스위치 (및 동일한 라이브러리!)를 사용하여 동일한 컴파일러로 동일한 소스 파일을 컴파일하면 동일한 파일 생성되었습니다 .class.

업데이트 : 최근 에 Java 7에서 on 구현에 대한 흥미로운 블로그 게시물을switchString 우연히 발견했습니다 . 이 블로그 게시물에는 여기에서 인용 할 관련 부분이 있습니다 (강조 표시).

컴파일러의 출력은 예측 가능하고 반복하기 위해, 이러한 데이터 구조에 사용 된지도와 세트는 LinkedHashMaps와 LinkedHashSet단지보다는이야 HashMaps하고 HashSets. 생성 된 코드의 기능 정확성의 측면에서 , 주어진 컴파일시에 사용 HashMap하고 HashSet잘 될 것입니다 ; 반복 순서는 중요하지 않습니다. 그러나 시스템 클래스의 구현 세부 사항에 따라의 출력이 달라지지 않는 것이 유익하다는 것을 알게되었습니다javac .

이것은 문제를 매우 명확하게 보여줍니다. 컴파일러는 사양과 일치하는 한 결정적인 방식으로 작동 할 필요없습니다 . 그러나 컴파일러 개발자는 일반적으로 시도 하는 것이 좋습니다 (너무 비싸지 않은 경우).


@GaborSch 무엇이 빠졌습니까? "다른 플랫폼에서 실행할 때 동일한 javac 실행 파일이 다른 바이트 코드를 생성하는 상황은 무엇입니까?" 기본적으로 컴파일러를 생성 한 그룹의 변덕에 따라
emory

3
글쎄, 나에게는 이것이 의존하지 않는 충분한 이유가 될 것입니다. 컴파일러가 항상 동일한 코드를 생성한다는 사실에 의존하면 업데이트 된 JDK가 내 빌드 / 아카이브 시스템을 손상시킬 수 있습니다.
Joachim Sauer 2013

3
@GaborSch : 이미 그러한 상황에 대한 완벽한 예가 있으므로 문제에 대한 몇 가지 추가 견해가 필요했습니다. 당신의 작업을 복제하는 것은 의미가 없습니다.
Joachim Sauer 2013

1
@GaborSch 근본적인 문제는 사용자가 웹 사이트에서 수정 된 JAR 만 가져 오는 애플리케이션의 효율적인 "온라인 업데이트"를 구현하려는 것입니다. 동일한 클래스 파일을 입력으로 갖는 동일한 JAR을 만들 수 있습니다. 그러나 문제는 동일한 소스 파일에서 컴파일 할 때 클래스 파일이 항상 동일한 지 여부입니다. 우리의 전체 개념은이 사실과 함께 서 있고 실패합니다.
mstrap 2013

2
@mstrap : 결국 XY 문제입니다. 글쎄, 당신은 항아리의 차등 업데이트를 조사 할 수 있습니다 (1 바이트 차이조차도 전체 항아리가 다시 다운로드되지 않도록 함) 어쨌든 릴리스에 명시 적 버전 번호를 제공해야하므로 전체 요점이 논쟁의 여지가 있습니다. .
Joachim Sauer 2013

38

컴파일러가 각 플랫폼에서 동일한 바이트 코드를 생성 할 의무는 없습니다. javac특정 답변을 얻으려면 다른 공급 업체의 유틸리티에 문의해야합니다 .


파일 순서를 지정하여 이에 대한 실제 예를 보여 드리겠습니다.

2 개의 jar 파일이 있다고 가정 해 봅시다 : my1.jarMy2.jar. 그들은 lib나란히 디렉토리에 저장됩니다. 컴파일러는 (이 때문에 알파벳 순서로 읽고 lib)하지만, 순서는 my1.jar, My2.jar파일 시스템의 경우를 구분하는 경우, 그리고 My2.jar,my1.jar 그것은 대소 문자를 구분합니다.

my1.jar클래스를 가지고 A.class하는 방법으로

public class A {
     public static void a(String s) {}
}

My2.jar동일한 갖고 A.class있지만, 다른 방법 서명 (수락 Object)

public class A {
     public static void a(Object o) {}
}

전화가 오면

String s = "x"; 
A.a(s); 

다른 경우에 다른 서명으로 메서드 호출을 컴파일합니다. 따라서 파일 시스템 대소 문자 구분에 따라 결과적으로 다른 클래스를 얻게됩니다.


1
+1 Eclipse 컴파일러와 javac 사이에는 수많은 차이가 있습니다 (예 : 합성 생성자가 생성되는 방식) .
Paul Bellora 2013

2
@GaborSch 동일한 JDK, 즉 동일한 javac에 대해 바이트 코드가 동일한 지 여부에 관심이 있습니다. 나는 그것을 더 명확하게 할 것이다.
mstrap 2013

2
@mstrap 귀하의 질문을 이해했지만 대답은 여전히 ​​동일합니다. 공급 업체에 따라 다릅니다. 는 javac각 플랫폼 (예 : Win7에, 리눅스, 솔라리스, 맥)에 다른 바이너리를 가지고 있기 때문에, 동일하지 않습니다. 벤더의 경우 다른 구현을 갖는 것은 이치에 맞지 않지만, 플랫폼 별 문제가 결과에 영향을 미칠 수 있습니다 (예 : 디렉토리에서의 순서 지정 (디렉토리에서 생각 lib), 엔디 안성 등).
gaborsch 2013

1
일반적으로 대부분은 javacJava로 구현 javac되므로 (단순한 기본 실행기 일 뿐임) 대부분의 플랫폼 차이는 영향을 미치지 않습니다.
Joachim Sauer 2013

2
@mstrap-그가 말하는 요점은 어떤 벤더도 컴파일러가 플랫폼간에 정확히 동일한 바이트 코드를 생성하도록 할 필요 가 없으며 결과 바이트 코드가 동일한 결과를 생성한다는 것입니다. 표준 / 사양 / 요구 사항이없는 경우 질문에 대한 대답은 "특정 공급 업체, 컴파일러 및 플랫폼에 따라 다릅니다"입니다.
Brian Roach 2013

6

짧은 답변- 아니요


긴 답변

그들은 bytecode다른 플랫폼에 대해 동일 할 필요는 없다. 바이트 코드를 정확히 실행하는 방법을 아는 것은 JRE (Java Runtime Environment)입니다.

당신은을 통해 이동하면 자바 VM 사양 이이 바이트 코드가 다른 플랫폼에서 동일 함을 사실 필요가 없다 것을 알게 될 것입니다.

를 통과 클래스 파일 형식 , 그것은 클래스 파일의 구조 등을 보여줍니다

ClassFile {
    u4 magic;
    u2 minor_version;
    u2 major_version;
    u2 constant_pool_count;
    cp_info constant_pool[constant_pool_count-1];
    u2 access_flags;
    u2 this_class;
    u2 super_class;
    u2 interfaces_count;
    u2 interfaces[interfaces_count];
    u2 fields_count;
    field_info fields[fields_count];
    u2 methods_count;
    method_info methods[methods_count];
    u2 attributes_count;
    attribute_info attributes[attributes_count];
}

부 버전 및 주 버전 확인

부 버전, 주 버전

minor_version 및 major_version 항목의 값은이 클래스 파일의 부 및 주 버전 번호이며 주 및 부 버전 번호가 함께 클래스 파일 형식의 버전을 결정합니다. 클래스 파일에 주 버전 번호 M과 부 버전 번호 m이있는 경우 해당 클래스 파일 형식의 버전을 Mm으로 표시합니다. 따라서 클래스 파일 형식 버전은 사전 순으로 정렬 될 수 있습니다 (예 : 1.5 <2.0 <2.1). Java 가상 머신 구현은 v가 연속적인 범위 Mi.0 v Mj.m에있는 경우에만 버전 v의 클래스 파일 형식을 지원할 수 있습니다. Sun만이 Java 플랫폼의 특정 릴리스 레벨을 준수하는 Java 가상 머신 구현이 지원할 수있는 버전의 범위를 지정할 수 있습니다 .1

각주를 통해 더 읽기

1 Sun의 JDK 릴리스 1.0.2의 Java 가상 머신 구현은 클래스 파일 형식 버전 45.0에서 45.3까지를 지원합니다. Sun의 JDK 릴리스 1.1.X는 45.0에서 45.65535까지 범위의 클래스 파일 형식을 지원할 수 있습니다. Java 2 플랫폼 버전 1.2의 구현은 45.0에서 46.0까지의 범위에있는 버전의 클래스 파일 형식을 지원할 수 있습니다.

따라서이 모든 것을 조사하면 서로 다른 플랫폼에서 생성 된 클래스 파일이 동일 할 필요가 없음을 알 수 있습니다.


더 자세한 링크를 주시겠습니까?
mstrap 2013

'플랫폼'은 운영 체제가 아닌 Java 플랫폼을 의미한다고 생각합니다. 물론, 1.6 호환 클래스 파일을 생성하도록 javac 1.7을 지시 할 때 차이가있을 것입니다.
mstrap 2013

@mtk +1은 컴파일 중에 단일 클래스에 대해 생성되는 속성 수를 표시합니다.
gaborsch 2013

3

첫째, 사양에는 그러한 보장이 전혀 없습니다. 준수 컴파일러는 컴파일 시간을 생성 된 클래스 파일에 추가 (사용자 지정) 속성으로 표시 할 수 있으며 클래스 파일은 여전히 ​​정확합니다. 그러나 모든 단일 빌드에서 바이트 수준의 다른 파일을 생성하며 사소한 일입니다.

두 번째로, 그런 성가신 트릭이 없더라도 컴파일러가 구성과 입력이 두 경우 모두 동일하지 않는 한 연속으로 두 번 정확히 동일한 작업을 수행 할 것으로 기대할 이유가 없습니다. 사양 소스 파일 이름을 표준 속성 중 하나로 설명하며 소스 파일에 빈 줄을 추가하면 줄 번호 테이블을 변경할 수 있습니다.

셋째, 호스트 플랫폼 (클래스 경로에있는 내용의 차이로 인한 것 제외)으로 인해 빌드에서 차이가 발생하지 않았습니다. 플랫폼 (즉, 네이티브 코드 라이브러리)에 따라 달라지는 코드는 클래스 파일의 일부가 아니며 바이트 코드에서 네이티브 코드의 실제 생성은 클래스가로드 된 후에 발생합니다.

넷째로 (그리고 가장 중요한 것은) 이것을 알고 싶어하는 것은 나쁜 프로세스 냄새 (코드 냄새와 같지만 코드에서 어떻게 행동하는지에 대한) 냄새가납니다. 빌드가 아닌 가능한 경우 소스 버전을 지정하고 빌드 버전을 지정해야하는 경우 개별 클래스 파일이 아닌 전체 구성 요소 수준에서 버전을 지정합니다. 우선적으로 CI 서버 (예 : Jenkins)를 사용하여 소스를 실행 가능한 코드로 전환하는 프로세스를 관리합니다.


2

동일한 JDK를 사용하면 생성 된 바이트 코드는 사용되는 하드웨어 및 OS와 관계없이 항상 동일 할 것이라고 믿습니다. 바이트 코드 생성은 결정적 알고리즘을 사용하여 소스 코드를 바이트 코드로 "변환"하는 Java 컴파일러에 의해 수행됩니다. 따라서 출력은 항상 동일합니다. 이러한 조건에서는 소스 코드에 대한 업데이트 만 출력에 영향을줍니다.


3
그래도 이것에 대한 참조가 있습니까? 나는이 질문의 의견에 말했듯이, 이것은 확실히 하지 C #의 경우 그래서 자바의 경우와 내용의 참조를보고 싶어요. 특히 다중 스레드 컴파일러가 다른 실행에서 다른 식별자 이름을 할당 할 수 있다고 생각합니다.
RB.

1
이것은 내 질문에 대한 대답이며 내가 기대하는 바이지만 RB에 대한 참조가 중요하다는 데 동의합니다.
mstrap 2013

저도 그렇게 믿습니다. 나는 당신이 확실한 참조를 찾을 것이라고 생각하지 않습니다. 그것이 당신에게 중요하다면 연구를 할 수 있습니다. 선도적 인 것들을 모아서 오픈 소스 코드를 컴파일하는 다양한 플랫폼에서 사용해보십시오. 바이트 파일을 비교하십시오. 결과를 게시하십시오. 여기에 링크를 넣어주세요.
emory

1

전반적으로 동일한 컴파일러가 다른 플랫폼에서 컴파일 할 때 동일한 소스가 동일한 바이트 코드를 생성 할 것이라는 보장은 없습니다.

예를 들어 일본어를 지원하는 Windows와 같은 다른 언어 (코드 페이지)와 관련된 시나리오를 살펴 보겠습니다. 다중 바이트 문자를 생각하십시오. 컴파일러가 항상 모든 언어를 지원해야한다고 가정하지 않는 한 8 비트 ASCII에 대해 최적화 할 수 있습니다.

Java 언어 사양 에 바이너리 호환성에 대한 섹션이 있습니다 .

SOM (Forman, Conner, Danforth 및 Raper, Proceedings of OOPSLA '95)의 Release-to-Release 바이너리 호환성 프레임 워크 내에서 Java 프로그래밍 언어 바이너리는 작성자가 식별하는 모든 관련 변환에서 바이너리 호환이 가능합니다 (일부 경고 포함). 인스턴스 변수 추가 관련). 이 체계를 사용하여 다음은 Java 프로그래밍 언어가 지원하는 몇 가지 중요한 바이너리 호환 변경 목록입니다.

• 성능 향상을 위해 기존 메서드, 생성자 및 이니셜 라이저를 다시 구현합니다.

• 이전에 일반적으로 발생해서는 안되는 예외를 던 졌거나 무한 루프에 들어가거나 교착 상태를 유발하여 실패한 입력에 대한 값을 반환하도록 메서드 또는 생성자를 변경합니다.

• 기존 클래스 또는 인터페이스에 새 필드, 메서드 또는 생성자를 추가합니다.

• 클래스의 개인 필드, 메서드 또는 생성자를 삭제합니다.

• 전체 패키지가 업데이트되면 기본 (패키지 전용) 액세스 필드, 메서드 또는 패키지의 클래스 및 인터페이스 생성자를 삭제합니다.

• 기존 유형 선언에서 필드, 메서드 또는 생성자를 재정렬합니다.

• 클래스 계층 구조에서 메서드를 위로 이동.

• 클래스 또는 인터페이스의 직접 수퍼 인터페이스 목록 재정렬.

• 유형 계층 구조에 새 클래스 또는 인터페이스 유형을 삽입합니다.

이 장에서는 모든 구현에서 보장되는 바이너리 호환성에 대한 최소 표준을 지정합니다. Java 프로그래밍 언어는 호환 가능한 소스에서 가져온 것으로 알려지지 않았지만 여기에 설명 된 호환 가능한 방식으로 소스가 수정 된 클래스 및 인터페이스의 바이너리가 혼합 될 때 호환성을 보장합니다. 응용 프로그램 릴리스 간의 호환성에 대해 논의하고 있습니다. Java SE 플랫폼 릴리스 간의 호환성에 대한 논의는이 장의 범위를 벗어납니다.


이 기사에서는 Java 버전을 변경할 때 발생할 수있는 일에 대해 설명합니다. OP의 질문은 동일한 Java 버전 내에서 플랫폼을 변경하면 어떻게 될 수 있는지였습니다. 그렇지 않으면 좋은 캐치입니다.
gaborsch 2013

1
내가 찾을 수있는 한 가깝습니다. 언어 사양과 JVM 사양 사이에 이상한 구멍이 있습니다. 지금까지 '다른 플랫폼에서 실행될 때 동일한 자바 컴파일러가 동일한 바이트 코드를 생성 할 것이라는 보장은 없습니다'라는 OP에 답해야했습니다.
Kelly S. French

1

Java allows you write/compile code on one platform and run on different platform. AFAIK ; 이것은 다른 플랫폼에서 생성 된 클래스 파일이 동일하거나 기술적으로 동일 할 때만 가능합니다.

편집하다

기술적으로 동일한 의견이 의미하는 바 는 그 것입니다. 바이트 단위로 비교하는 경우 정확히 동일 할 필요는 없습니다.

따라서 사양에 따라 다른 플랫폼에있는 클래스의 .class 파일은 바이트 단위로 일치 할 필요가 없습니다.


OP의 질문 클래스 파일이 동일한 지 "기술적으로 동일한 지"였습니다.
bdesham

나는 그들이 동일한 지 관심이 있습니다.
mstrap 2013

대답은 '예'입니다. 내가 의미하는 바는 바이트 단위로 비교하면 동일하지 않을 수 있다는 것입니다. 그래서 기술적으로 동일한 단어를 사용했습니다.
rai.skumar 2013

@bdesham 그는 그들이 동일한 지 알고 싶었습니다. "기술적으로 똑같다"는 것이 무엇을 이해했는지 잘 모르겠습니다. 그게 반대 투표의 이유입니까?
rai.skumar 2013

@ rai.skumar 귀하의 대답은 기본적으로 "두 개의 컴파일러가 항상 동일하게 작동하는 출력을 생성합니다."라고 말합니다. 물론 이것은 사실입니다. Java 플랫폼의 전체적인 동기입니다. OP는 내 보낸 코드가 동일한 바이트 인지 여부를 알고 싶었습니다 .
bdesham

1

질문 :

"다른 플랫폼에서 실행할 때 동일한 javac 실행 파일이 다른 바이트 코드를 생성하는 상황은 무엇입니까?"

크로스 컴파일의 예를 들어 우리가 javac의 옵션을 사용하는 방법을 보여줍니다 -target 버전

이 플래그는이 명령을 호출하는 동안 지정한 Java 버전과 호환되는 클래스 파일을 생성합니다. 따라서 클래스 파일은이 옵션을 사용하여 비교하는 동안 제공하는 속성에 따라 다릅니다.


0

대부분의 경우 대답은 "예"이지만 정확한 대답을 얻으려면 컴파일하는 동안 일부 키 또는 GUID 생성을 검색해야합니다.

나는 이것이 발생하는 상황을 기억할 수 없습니다. 예를 들어 직렬화 목적으로 ID를 가지려면 프로그래머 나 IDE에 의해 생성 된 것과 같이 하드 코딩됩니다.

추신 또한 JNI가 중요 할 수 있습니다.

PPS javac는 자바로 작성되었습니다. 이것은 다른 플랫폼에서 동일하다는 것을 의미합니다. 따라서 이유없이 다른 코드를 생성하지 않습니다. 따라서 기본 호출로만이를 수행 할 수 있습니다.


Java가 모든 플랫폼 차이 로부터 사용자를 보호하지는 않습니다 . 디렉토리의 내용이 정의되어 있지 않은 나열 할 때 파일의 순서를 반환하고,이 생각할 컴파일러에 약간의 영향을 미친다.
Joachim Sauer 2013

0

두 가지 질문이 있습니다.

Can there be a difference depending on the operating system or hardware? 

이것은 이론적 인 질문이고 대답은 분명히있을 있습니다. 다른 사람들이 말했듯이 사양은 컴파일러가 바이트 단위 동일한 클래스 파일을 생성하도록 요구하지 않습니다.

현재 존재하는 모든 컴파일러가 모든 상황 (다른 하드웨어 등)에서 동일한 바이트 코드를 생성하더라도 내일의 대답은 다를 수 있습니다. javac 또는 운영 체제를 업데이트 할 계획이없는 경우 특정 상황에서 해당 버전의 동작을 테스트 할 수 있지만 예를 들어 Java 7 업데이트 11에서 Java 7 업데이트 15로 이동하면 결과가 다를 수 있습니다.

What are the circumstances where the same javac executable, when run on a different platform, will produce different bytecode?

그것은 알 수 없습니다.

구성 관리가 질문의 이유인지는 모르겠지만 이해해야 할 이유입니다. 바이트 코드를 비교하는 것은 합법적 인 IT 제어이지만 클래스 파일이 변경되었는지 확인하기위한 것이지 소스 파일이 변경되었는지 확인하는 것이 아닙니다.


0

나는 그것을 다른 방식으로 표현할 것입니다.

첫째, 질문은 결정 론적이지 않다고 생각합니다.

물론 이것은 결정 론적입니다. 컴퓨터 과학에서 무작위성은 달성하기 어렵고 컴파일러가 어떤 이유로 든 여기에이를 도입 할 이유가 없습니다.

둘째, "동일한 소스 코드 파일에 대한 바이트 코드 파일이 얼마나 유사한가?"로 재구성하면 아니오 , 유사 할 것이라는 사실에 의존 할 수 없습니다. .

이를 확인하는 좋은 방법은 .class (또는 제 경우에는 .pyc)를 git 단계에 남겨 두는 것입니다. 팀의 여러 컴퓨터에서 git은 .py 파일에 변경 사항이없는 경우 (그리고 .pyc가 다시 컴파일 된 경우) .pyc 파일 간의 변경 사항을 감지합니다.

적어도 내가 관찰 한 것입니다. 따라서 * .pyc 및 * .class를 .gitignore에 넣으십시오!

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