Java 바이트 코드 읽는 방법 (예 포함)

16099 단어 jvmtutorialjava
커버 이미지 divinetechygirl 제공
JVM 생태계를 사용할 때 막후에서 일어나는 일을 이해하는 데 시간이 좀 걸린다.기본적인 차원에서도 우리는 간단한 문자로 JVM이 무엇인지, 컴파일이 어떻게 작동하는지, 바이트 코드가 무엇인지, 그리고 그것을 어떻게 읽는지 이해하고 해석할 수 있어야 한다.
이 강좌에서는 JVM의 10000피트 뷰를 보고 기본 개념을 이해하고 간단한 프로그램에서 바이트 코드를 읽는 방법을 배울 것입니다.
시작합시다.

JVM은 무엇입니까?


간단히 말하면 JVM은 Java VM 규범에 지정된 형식으로 컴파일된 코드를 읽고 현재 시스템에서 실행하는 엔진입니다.이런 방법의 장점은 주로 크로스플랫폼 호환성이다. 왜냐하면 컴파일된 코드(바이트 코드)는 플랫폼과 무관해야 하기 때문이다.
이것은 Linux 시스템에서 컴파일된 코드와 Windows 시스템에서 컴파일된 코드가 모두 JVM에서 작업해야 한다는 것을 의미한다.우리는 컴파일된 .class 파일을 linux에서 윈도우즈로 복사해서 그곳에서 실행할 수 있으며, 문제가 발생하지 않을 뿐만 아니라, 반대로도 마찬가지이다.
즉, Windows PC에 Java를 설치할 때 java 도구는 플랫폼별 실행 시 및 JIT 컴파일러를 사용하여 Windows에서 코드를 실행합니다.한편, javac.java 파일을 유니버설 바이트 형식으로 컴파일할 것이다.
바이트 코드 자체는 Java VM 규범을 따르는 형식입니다.그것은 현재 버전을 바탕으로 각종 기능을 활성화시켰다.이러한 특성은 JSR 또는 Java 사양 요청 및 현재 구현에 따라 지정됩니다.다음과 같은 OpenJDK 9의 목록입니다.
102: Process API Updates
110: HTTP 2 Client
143: Improve Contended Locking
158: Unified JVM Logging
165: Compiler Control
193: Variable Handles
197: Segmented Code Cache
199: Smart Java Compilation, Phase Two
200: The Modular JDK
201: Modular Source Code
211: Elide Deprecation Warnings on Import Statements
212: Resolve Lint and Doclint Warnings
213: Milling Project Coin
214: Remove GC Combinations Deprecated in JDK 8
215: Tiered Attribution for javac
216: Process Import Statements Correctly
217: Annotations Pipeline 2.0
219: Datagram Transport Layer Security (DTLS)
220: Modular Run-Time Images
221: Simplified Doclet API
222: jshell: The Java Shell (Read-Eval-Print Loop)
223: New Version-String Scheme
224: HTML5 Javadoc
225: Javadoc Search
226: UTF-8 Property Files
227: Unicode 7.0
228: Add More Diagnostic Commands
229: Create PKCS12 Keystores by Default
231: Remove Launch-Time JRE Version Selection
232: Improve Secure Application Performance
233: Generate Run-Time Compiler Tests Automatically
235: Test Class-File Attributes Generated by javac
236: Parser API for Nashorn
237: Linux/AArch64 Port
238: Multi-Release JAR Files
240: Remove the JVM TI hprof Agent
241: Remove the jhat Tool
243: Java-Level JVM Compiler Interface
244: TLS Application-Layer Protocol Negotiation Extension
245: Validate JVM Command-Line Flag Arguments
246: Leverage CPU Instructions for GHASH and RSA
247: Compile for Older Platform Versions
248: Make G1 the Default Garbage Collector
249: OCSP Stapling for TLS
250: Store Interned Strings in CDS Archives
251: Multi-Resolution Images
252: Use CLDR Locale Data by Default
253: Prepare JavaFX UI Controls & CSS APIs for Modularization
254: Compact Strings
255: Merge Selected Xerces 2.11.0 Updates into JAXP
256: BeanInfo Annotations
257: Update JavaFX/Media to Newer Version of GStreamer
258: HarfBuzz Font-Layout Engine
259: Stack-Walking API
260: Encapsulate Most Internal APIs
261: Module System
262: TIFF Image I/O
263: HiDPI Graphics on Windows and Linux
264: Platform Logging API and Service
265: Marlin Graphics Renderer
266: More Concurrency Updates
267: Unicode 8.0
268: XML Catalogs
269: Convenience Factory Methods for Collections
270: Reserved Stack Areas for Critical Sections
271: Unified GC Logging
272: Platform-Specific Desktop Features
273: DRBG-Based SecureRandom Implementations
274: Enhanced Method Handles
275: Modular Java Application Packaging
276: Dynamic Linking of Language-Defined Object Models
277: Enhanced Deprecation
278: Additional Tests for Humongous Objects in G1
279: Improve Test-Failure Troubleshooting
280: Indify String Concatenation
281: HotSpot C++ Unit-Test Framework
282: jlink: The Java Linker
283: Enable GTK 3 on Linux
284: New HotSpot Build System
285: Spin-Wait Hints
287: SHA-3 Hash Algorithms
288: Disable SHA-1 Certificates
289: Deprecate the Applet API
290: Filter Incoming Serialization Data
291: Deprecate the Concurrent Mark Sweep (CMS) Garbage Collector
292: Implement Selected ECMAScript 6 Features in Nashorn
294: Linux/s390x Port
295: Ahead-of-Time Compilation
297: Unified arm32/arm64 Port
298: Remove Demos and Samples
299: Reorganize Documentation

바이트 코드는 어떻게 읽습니까?


간단한 프로그램에서 바이트 코드를 이해하고 읽어 봅시다.
이 강좌에서는 IntelliJ IDEA를 ASM Bytecode Outline Plugin에 사용하지만 VScodejavap에 사용할 수도 있습니다.
  • 편집기 대화 상자를 사용하여 새 Java 프로그램을 만듭니다.
  • 은 다음 코드를 사용하여 DetermineOS.java이라는 새 자바 파일을 만듭니다.
  • public class DetermineOS {
    
        public static void main(String[] args) {
    
            String strOSName = System.getProperty("os.name");
    
            System.out.print("Display the current OS name example.. OS is ");
            if(strOSName != null)
            {
                if(strOSName.toLowerCase().contains("linux"))
                    System.out.println("Linux");
                else
                    System.out.print("not Linux");
            }
        }
    }
    
    
    위 코드는 os.name 시스템 속성만 검색하고 문자열 linux을 포함하는지 확인합니다.그리고 이런 상황에 따라 문자열을 인쇄합니다.
  • 클릭 보기 -> Jclasslib으로 바이트 표시

  • 오른쪽 패널에서 바이트 코드에 대한 정보를 볼 수 있습니다.한 명씩 살펴보겠습니다.

  • 일반 정보: 이 섹션에서는 이러한 JVM 버전 컴파일, 상수 풀의 상수, 액세스 플래그 및 기타 카운터에 대한 특정 정보를 표시합니다.

  • 상수 풀: JVM은 클래스 경로에서 볼 수 있는 각 유형에 상수 해시 맵을 로드합니다.이 맵은 기본적으로 문자 값, 문자열, 형식이나 필드에 대한 다른 인용으로 구성되어 있습니다.모든 값은 유일 키로 인용됩니다.우리의 예시에서 os.name의 문자열이 키 #33:
  • 을 불러오는 것을 볼 수 있다
  • 인터페이스 + 필드: 이 섹션은 모든 인터페이스와 필드 설명을 표시합니다.우리는 어떤 내용도 지정하지 않았기 때문에 이 부분은 비어 있다.

  • 방법: 이 섹션에는 바이트 코드의 에센스가 포함되어 있습니다.우리의 프로그램에서 우리는 현재 main 방법을 정의했다.javac이 코드를 컴파일할 때, 두 개의 항목을 만들 것입니다. 하나는 방법으로, 다른 하나는 main 방법입니다.다음 사항에 대해 자세히 알아보겠습니다.

  • : 이 메서드에서 보내는 바이트 코드는 다음과 같습니다.
  • 0 aload_0
    1 invokespecial #1 <java/lang/Object.<init>>
    4 return
    
    JVM에서 클래스의 각 구조 함수(정의되지 않은 경우)는 <init>에 대한 호출로 호출되며 컴파일러가 제공합니다.이상의 설명은 <init>으로 전화하는 최저 요구입니다.aload_0은 실행 시 현재 프레임의 인덱스 0에 로컬 참조를 로드하는 것을 나타냅니다.이것은 <java/lang/Object.<init>> 자체에 대한 인용을 포함하기 때문에 다음 명령 invokespecial #1은 특수한 실례 호출로 #1을 호출합니다.그리고 우리 return.

  • main: 이 메서드에서 보내는 바이트 코드는 다음과 같습니다.
  • 0 ldc #5 <os.name>
     2 invokestatic #6 <java/lang/System.getProperty>
     5 astore_1
     6 getstatic #2 <java/lang/System.out>
     9 ldc #7 <Display the current OS name example.. OS is >
    11 invokevirtual #4 <java/io/PrintStream.print>
    14 aload_1
    15 ifnull 49 (+34)
    18 aload_1
    19 invokevirtual #8 <java/lang/String.toLowerCase>
    22 ldc #9 <linux>
    24 invokevirtual #10 <java/lang/String.contains>
    27 ifeq 41 (+14)
    30 getstatic #2 <java/lang/System.out>
    33 ldc #11 <Linux>
    35 invokevirtual #12 <java/io/PrintStream.println>
    38 goto 49 (+11)
    41 getstatic #2 <java/lang/System.out>
    44 ldc #13 <not Linux>
    46 invokevirtual #4 <java/io/PrintStream.print>
    49 return
    
    이것은 더욱 복잡하지만, 만약 네가 자세히 관찰한다면, 너는 그것을 코드 자체와 연결시킬 수 있다.바이트 판넬은 모든 명령에 링크를 제공함으로써 우리를 돕는다.다음은 몇 가지 문제에 대한 설명입니다.
    0 ldc #5
    
    항목 #5를 상수 풀(즉 "os.name")에서 스택으로 밀어넣기
    2 invokestatic #6
    
    # 6곳에서 인용한 정적 방법, 즉 <java/lang/System.getProperty>을 호출합니다.이것은 우리가 이전에 전달한 최상위 창고 프레임 변수를 사용할 것이다.
    5 astore_1
    
    나중에 맨 위 스택 위치의 참조가 색인 1의 부분 프레임에 저장됩니다.이것은 기본적으로 이전에 invokestatic을 호출한 결과이다.따라서 다음과 같은 작업을 완료했습니다.String strOSName = System.getProperty("os.name");
    15 ifnull 49
    
    조건 분기, 즉.이전 명령 aload_1(astore_1의 결과를 저장하는 데 사용됨)이 현재 스택의 맨 위에서 가져온 값이 비어 있으면 49번째 줄로 이동하십시오. 그렇지 않으면 다음 줄을 계속합니다.바이트 코드의 49번째 줄은 다음과 같습니다.
    49 return
    
    바이트 코드를 계속 읽고 invokevirtual, getstatic 또는 ifeq의 의미를 이해할 수 있습니다.

  • 속성: 실제 원본 파일에 대한 정보를 표시합니다.예를 들어, 상수 탱크에 비치는 파일 이름을 볼 수 있습니다:

  • 참고: javap 도구를 사용하여 코드를 보려면 다음 명령을 실행할 수 있습니다.
    $ javap -c out.production.jvm-experiments.DetermineOS
    
    나는 나의 항목을 jvm-experiments으로 명명했기 때문에, 당신은 당신의 프로젝트 이름을 사용하여 정확한 경로를 선택해야 합니다.

    추가 정보


    지금 이대로.저는 당신이 오늘 우리가 한 일을 좋아하고 JVM과 바이트 코드의 기본 블록을 이해하기를 바랍니다.JVM 생태계에 대한 자세한 내용을 보려면 다음 리소스를 추천합니다.

  • JVM Specification: JVM 플랫폼과 관련된 모든 컨텐츠의 실제 참조입니다.

  • Jprofiler: 사장처럼 Java 응용 프로그램을 평가합니다.

  • Tutorials Point: 신속 단순 강좌
  • 좋은 웹페이지 즐겨찾기