λ³Έλ¬Έ λ°”λ‘œκ°€κΈ°
  • μž₯원읡 κΈ°μˆ λΈ”λ‘œκ·Έ
πŸ’Š Java & Kotlin & Spring/- Java & kotlin

[쑰금 더 κΉŠμ€ Java] Java Bytecode λ₯Ό μ•Œμ•„λ³΄μž (μžλ°”λ₯Ό μ»΄νŒŒμΌν•˜λ©΄ μ–΄λ–€ 일이 μΌμ–΄λ‚ κΉŒ?)

by Wonit 2021. 11. 25.

 

μš°λ¦¬λŠ” λ§Žμ€ μ‹œκ°„ Javaλ₯Ό μ΄μš©ν•΄μ„œ λ‹€μ–‘ν•œ μ†Œν”„νŠΈμ›¨μ–΄λ₯Ό κ°œλ°œν•˜λ©΄μ„œ λ“€μ—ˆλ˜ μ†Œλ¦¬κ°€ μžˆλ‹€.

 

JavaλŠ” JVM 이 있기 λ•Œλ¬Έμ— ν”Œλž«νΌμ— 쒅속적이지 μ•Šκ³  이식성이 λ›°μ–΄λ‚˜λ‹€.

 

κ·Έ μ΄μœ μ— λŒ€ν•΄μ„œ 생각해본 κ²½ν—˜μ΄ μžˆλŠ”κ°€?

 

μ˜€λŠ˜μ€ μœ„μ˜ JVMκ³Ό 이식성을 μ΄ν•΄ν•˜κΈ° μœ„ν•΄ κΌ­ μ•Œμ•„μ•Ό ν•˜λŠ” Java Bytecode 에 λŒ€ν•΄μ„œ μ•Œμ•„λ³΄λ € ν•œλ‹€.

 

Java Bytecode, μžλ°” λ°”μ΄νŠΈμ½”λ“œ

 

μš°λ¦¬λŠ” Java μ†Œν”„νŠΈμ›¨μ–΄λ₯Ό κ°œλ°œν•˜κΈ° μœ„ν•΄μ„œ JDKλ₯Ό μ„€μΉ˜ν•˜κ³  Java μ†Œν”„νŠΈμ›¨μ–΄λ₯Ό μ‹€ν–‰μ‹œν‚€κΈ° μœ„ν•΄μ„œ JRE λ₯Ό μ„€μΉ˜ν•œλ‹€.

 

λ˜ν•œ κ°œλ°œμ„ ν•˜λ©΄ μ‹€ν–‰ κ²°κ³Όλ₯Ό ν™•μΈν•˜κΈ° μœ„ν•΄μ„œ Compile 과정을 거치게 λ˜λŠ”λ°, 이 μ»΄νŒŒμΌμ€ λ°”λ‘œ JDKλ‚˜ JRE 에 ν•¨κ»˜ ν¬ν•¨λ˜λŠ” javac.exe μ‹€ν–‰νŒŒμΌμ΄ μˆ˜ν–‰ν•˜λŠ” 것이닀.

 

μ΄λŠ” κ°œλ°œμžκ°€ μž‘μ„±ν•œ .java νŒŒμΌμ„ JVM이 이해할 수 μžˆλ„λ‘ ν•˜λŠ” Bytecode 둜 λ³€ν™˜ν•˜κ³  .class νŒŒμΌμ„ λ§Œλ“œλŠ” 것을 μ˜λ―Έν•˜λŠ”λ°, .class νŒŒμΌμ— μ‘΄μž¬ν•˜λŠ” 데이터가 λ°”λ‘œ μžλ°” λ°”μ΄νŠΈμ½”λ“œ, Java Bytecode 인 것이닀.

 

μ£Όμ˜ν•΄μ•Ό ν•  점이 컴파일 κ²°κ³Ό 라고 ν•΄μ„œ Cλ‚˜ C++이 μ»΄νŒŒμΌν•˜λ©΄ μƒμ„±ν•˜λŠ” 기계어와 λ™μΌν•˜κ²Œ μƒκ°ν•˜λ©΄ μ•ˆλœλ‹€. κΈ°κ³„μ–΄λŠ” JVM이 λ‹€λ₯Έ λͺ¨λ“ˆμ„ ν†΅ν•΄μ„œ μƒμ„±ν•˜κ³  μ‹€ν–‰ν•˜λŠ”λ°, 이 κ³Όμ •μ—μ„œ Cλ‚˜ C++에 λΉ„ν•΄ 쑰금 느린 μ„±λŠ₯을 λ‚΄λŠ” 것이닀.

 

Java Bytecode λŠ” μš°λ¦¬κ°€ κ°œλ°œν•œ μžλ°” ν”„λ‘œκ·Έλž¨(μ½”λ“œ)λ₯Ό λ°°ν¬ν•˜λŠ” κ°€μž₯ μž‘μ€ λ‹¨μœ„λΌκ³  ν•œλ‹€.

 

Java Bytecode ν™•μΈν•˜κΈ°

 

더 κΉŠμ€ 이해λ₯Ό μœ„ν•΄μ„œ μš°λ¦¬κ°€ 직접 Java Bytecode λ₯Ό μƒμ„±ν•˜κ³  ν™•μΈν•΄λ³΄μž

 

λ‹€μŒκ³Ό 같은 java μ½”λ“œκ°€ μžˆλ‹€κ³  가정해보겠닀.

 

public class Wonit {
  public static void main(String[] args) {
    String name = "μ›Œλ‹‰";
    int age = 25;

    Person blogger = new Person(name, age);

    blogger.print();
  }
}

class Person {
  String name;
  int age;

  Person(String name, int age) {
    this.name = name;
    this.age = age;
  }

  void print() {
    System.out.println("λΈ”λ‘œκ·Έ 주인의 이름은 " + name + " 이며 λ‚˜μ΄λŠ” " + age + " 이닀");
  }
}

 

이제 이 μ½”λ“œλ₯Ό μ»΄νŒŒμΌν•΄λ³΄μž.

 

IDEλ₯Ό ν†΅ν•΄μ„œ μ»΄νŒŒμΌμ„ 해도 λ™μΌν•œ κ²°κ³Όκ°€ λ‚˜μ˜€κ² μ§€λ§Œ μ§€κΈˆ λ§ŒνΌμ€ javac.exeλ₯Ό μ΄μš©ν•΄μ„œ 직접 μ»΄νŒŒμΌν•΄λ³΄μž!

 

$ javac Wonit.java

$ ls
Wonit.java    Wonit.class    Person.class

 

ν•˜λ‚˜μ˜ νŒŒμΌμ—μ„œ μž‘μ„±ν–ˆμ§€λ§Œ μš°λ¦¬λŠ” 2개의 클래슀λ₯Ό μƒμ„±ν–ˆκΈ° λ•Œλ¬Έμ— κ²°κ΅­ μ»΄νŒŒμΌλ˜λŠ” μ½”λ“œλŠ” 2개의 클래슀 파일이 μƒμ„±λ˜μ—ˆλ‹€.

 

 

그리고 이 데이터λ₯Ό HexDλ‚˜ Hex Viewer 둜 ν™•μΈν•œλ‹€λ©΄ Byte ν˜•νƒœλ‘œ 직접 확인할 수 μžˆμ§€λ§Œ 속이 μšΈλ κ±°λ¦¬λ―€λ‘œ javap 둜 λ””μ»΄νŒŒμΌλ§μ„ ν•˜κ³  보기 νŽΈν•˜κ²Œ λ°”κΏ”λ³΄μž.

 

$ javap -v -p -s Wonit.class

 

verbose λ₯Ό μ΄μš©ν•΄μ„œ λ””μ»΄νŒŒμΌλ§ν•˜λŠ” 과정은 javap λ₯Ό μ‹€ν–‰ν•˜λ©΄ λœλ‹€.

 

κ·Έλ ΄ 결과둜 λ‹€μŒκ³Ό 같은 뢄석 κ²°κ³Όκ°€ λ‚˜μ˜€κ²Œ λœλ‹€.

 

  minor version: 0
  major version: 58
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #17                         // Wonit
  super_class: #2                         // java/lang/Object
  interfaces: 0, fields: 0, methods: 2, attributes: 1
public class Wonit
  minor version: 0
  major version: 58
  interfaces: 0, fields: 0, methods: 2, attributes: 1
Constant pool:
   #1 = Methodref          #2.#3          // java/lang/Object."<init>":()V
   ... μƒλž΅
  #24 = Utf8               Wonit.java
{
  public Wonit();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    Code:
      stack=4, locals=4, args_size=1
         0: ldc           #7                  // String μ›Œλ‹‰
         5: istore_2
        ... μƒλž΅
        20: return
      LineNumberTable:
        line 3: 0
        line 4: 3
        line 6: 6
        line 8: 16
        line 9: 20
}
SourceFile: "Wonit.java"

 

사싀상 이 μ½”λ“œλ“€μ„ μ™„λ²½ν•˜κ²Œ μ΄ν•΄ν•˜λŠ” 것은 λ‚΄ μˆ˜μ€€μœΌλ‘œ λ„μ €νžˆ λΆˆκ°€λŠ₯ν•˜λ”λΌ.. κ·Έλž˜λ„ μ΅œλŒ€ν•œ μ΄ν•΄ν•˜λ €κ³  ν•΄λ³΄μ•˜λŠ”λ°, κ·Έ κ²°κ³Όλ₯Ό 이제 ν•¨κ»˜ κ³΅μœ ν•˜λ € ν•œλ‹€.

 

Java Bytecode 의 κ΅¬μ„±μš”μ†Œ

 

μžλ°” λ°”μ΄νŠΈμ½”λ“œμ˜ κ΅¬μ„±μš”μ†ŒλŠ” 많이 μžˆμ§€λ§Œ 크게 3가지가 μ‘΄μž¬ν•œλ‹€κ³  보자

 

  1. Class Format
  2. Type의 ν‘œν˜„
  3. Constant Pool
  4. Instruction Set

 

Class Format

 

μœ„μ˜ μžλ°” λ°”μ΄νŠΈμ½”λ“œλŠ” λ‚˜λ¦„μ˜ 포맷이 μ •ν•΄μ Έμžˆκ³ , ν•΄λ‹Ή 포맷으둜 ν‘œν˜„μ΄ λœλ‹€.

 

μš°λ¦¬κ°€ μ»΄νŒŒμΌν•œ μ½”λ“œλŠ”

 

minor version: 0
major version: 58
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #17                         // Wonit
super_class: #2                         // java/lang/Object
interfaces: 0, fields: 0, methods: 2, attributes: 1

 

에 ν•΄λ‹Ήν•˜λŠ”λ°, 이 포맷은 λ‹€μŒκ³Ό 같은 ν˜•μ‹μœΌλ‘œ κ΅¬μ„±λ˜μ–΄ μžˆλ‹€.

 

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];  
}

 

  • magic
    • 클래슀 파일의 첫 4λ°”μ΄νŠΈλ‘œ μžλ°” 클래슀파일이 λ§žλŠ”μ§€ κ΅¬λΆ„ν•˜λŠ” μš©λ„λ‘œ 쓰인닀. PE Header λ‚˜ Image Signatur κ³Ό 같은 μš©λ„λΌκ³  보고 CAFEBABE λΌλŠ” 이름을 κ°–κ³  μžˆλ‹€.
  • minor_version, major_version
    • 클래슀의 Version 을 λ‚˜νƒ€λ‚Έλ‹€. 즉, JDK 1.6이냐 1.8이냐 λ₯Ό κ΅¬λΆ„ν•˜λŠ”λ°, 각각 JDK 버전에 따라 λ‹€λ₯Έ μˆ˜κ°€ λ‚˜μ˜€κ²Œ λœλ‹€
  • constant_pool_count
    • 클래슀 파일의 μƒμˆ˜ ν’€(Constant Pool) 의 갯수λ₯Ό λ‚˜νƒ€λ‚΄λŠ” μš©λ„λ‘œ μ‚¬μš©λœλ‹€.
  • access_flags
    • 주둜 클래슀의 public, final κ³Ό 같은 modifier 정보λ₯Ό λ‚˜νƒ€λ‚Έλ‹€.
  • interface_count
    • ν΄λž˜μŠ€κ°€ κ΅¬ν˜„ν•œ μΈν„°νŽ˜μ΄μŠ€μ˜ κ°œμˆ˜μ™€ 각 μΈν„°νŽ˜μ΄μŠ€μ— λŒ€ν•œ constant_pool λ‚΄μ˜ 인덱슀λ₯Ό λ‚˜νƒ€λ‚Έλ‹€.

 

μœ„μ— μ„€λͺ…ν•œ 포맷 말고도 λ‹€λ₯Έ 포맷의 정보도 λΆ„λͺ… μ‘΄μž¬ν•˜κ² μ§€λ§Œ 이듀을 μ„€λͺ…ν•˜λŠ” 것은 였히렀 이 ν¬μŠ€νŒ…μ˜ λͺ©μ μ„ μžƒλŠ” 것이라 νŒλ‹¨μ΄ λ˜μ–΄ μƒλž΅ν•˜λ„λ‘ ν•˜κ² λ‹€.

 

사싀 λ‚΄κ°€ 이해λ₯Ό ν•˜μ§€ λͺ»ν•˜μ˜€κΈ° λ•Œλ¬Έμ—

 

Type의 ν‘œν˜„

 

μžλ°” λ°”μ΄νŠΈμ½”λ“œμ˜ ν‘œν˜„μ€ μš°λ¦¬κ°€ μ‚¬μš©ν•  수 μžˆλŠ” λͺ¨λ“  Type 을 Bytecode Expression 으둜 λ³€ν™˜ν•  수 μžˆλ‹€.


λŒ€ν‘œμ μΈ νƒ€μž…μ€

 

  • B : byte
  • C : char
  • I : int
  • L<classname>; : reference

 

정도가 μžˆλ‹€.

 

예λ₯Ό λ“€μ–΄μ„œ λ‹€μŒκ³Ό 같은 μ½”λ“œκ°€ μžˆλ‹€λ©΄

 

Object print(String str, int i)

 

Type 의 ν‘œν˜„μœΌλ‘œ λ‹€μŒκ³Ό 같이 ν‘œν˜„ν•  수 μžˆλ‹€.

 

(java/lang/String;I)Ljava/lang/Object;

 

λ”μš± ꡬ체적인 νƒ€μž…μ˜ ν‘œν˜„μ€ oracle java bytecode docsλ₯Ό μ°Έκ³ ν•˜λ©΄ λœλ‹€.

 

Chapter 4. The class File Format

The target of each jump and branch instruction (jsr, jsr_w, goto, goto_w, ifeq, ifne, ifle, iflt, ifge, ifgt, ifnull, ifnonnull, if_icmpeq, if_icmpne, if_icmple, if_icmplt, if_icmpge, if_icmpgt, if_acmpeq, if_acmpne) must be the opcode of an instruction wi

docs.oracle.com

 

Constant Pool

 

JVM은 Host OS 의 λ©”λͺ¨λ¦¬λ₯Ό μ΅œλŒ€ν•œ 효율적으둜 μ΄μš©ν•˜λ„λ‘ 섀계가 λ˜μ–΄μžˆλ‹€.

 

이λ₯Ό μœ„ν•΄μ„œ JVM은 Constant Pool μ΄λΌλŠ” μ „λž΅μ„ μ‚¬μš©ν•˜λŠ”λ°, JVM이 λ™μ μœΌλ‘œ μ½”λ“œλ₯Ό μ‹€ν–‰μ‹œν‚¬ λ•Œ λͺ¨λ“  데이터λ₯Ό μ¦‰μ‹œ μƒμ„±ν•˜λŠ” 것이 μ•„λ‹ˆλΌ Constant Pool 에 μ €μž₯ν•˜κ³  Constant Pool 에 μ‘΄μž¬ν•˜λŠ” 데이터λ₯Ό μš°μ„ μ μœΌλ‘œ 가져와 λ©”λͺ¨λ¦¬λ₯Ό λ”μš± 효율적으둜 μ‚¬μš©ν•  수 있게 λ˜λŠ” 것이닀.

 

# ν˜•νƒœμ˜ ν•΄μ‹œμ½”λ“œλ‘œ μ‹œμž‘ν•˜λŠ” 것이 νŠΉμ§•μ΄λ‹€.

 

이 Constant Pool에 λŒ€ν•΄μ„œλŠ” ν•΄λ‹Ή λΈ”λ‘œκ·Έμ˜ 쑰금 더 κΉŠμ€ Java - String Constant Pool μ—μ„œ 확인할 수 μžˆλ‹€.

 

[쑰금 더 κΉŠμ€ Java] String κ³Ό String Constant Pool

μš°λ¦¬λŠ” Java λ₯Ό μ‚¬μš©ν•˜λ©΄μ„œ μ•„μ£Ό λ§Žμ€ String 의 Literal 을 μ΄μš©ν•˜κ²Œ λœλ‹€. μ˜€λŠ˜μ€ κ·Έ String이 μ–΄λ–€ νŠΉμ„±μ„ κ°€μ‘ŒλŠ”μ§€ 쑰금 λ‹€λ₯Έ κ°λ„μ—μ„œ 봐보렀 ν•œλ‹€. String 은? String 은 Java μ—μ„œ μ œκ³΅ν•˜λŠ” νŠΉλ³„ν•œ 자료

wonit.tistory.com

 

Instruction Set

 

μžλ°” λ°”μ΄νŠΈμ½”λ“œλŠ” 컴파일된 결과둜 μƒμ„±λ˜λŠ” μ½”λ“œ μ΄λ―€λ‘œ μΌμ’…μ˜ λͺ…λ Ήμ–΄ 집합이라고 ν•  수 μžˆλ‹€.

 

이λ₯Ό JVM Instruction Set 이라고 ν•œλ‹€.

 

λͺ…λ Ήμ–΄λŠ” λ‹Ήμ—°ν•˜κ²Œ OpCode와 Operands 둜 κ΅¬μ„±λ˜λŠ”λ° μžλ°” λ°”μ΄νŠΈμ½”λ“œμ—μ„œλŠ” 1 Byte의 OpCode와 2 Byte의 Operands 둜 κ΅¬μ„±λœλ‹€.

 

1 Byte의 OpCode μ΄λ―€λ‘œ μ‚¬μš©κ°€λŠ₯ν•œ 총 λͺ…λ Ήμ–΄μ˜ μˆ˜λŠ” 256κ°œκ°€ 되고 μžμ„Έν•œ λͺ…λ Ήμ–΄μ˜ μ •μ˜λŠ” Oracle Java Bytecode Instruction Set Docs μ—μ„œ 확인할 수 μžˆλ‹€.

 

Chapter 6. The Java Virtual Machine Instruction Set

The wide instruction modifies the behavior of another instruction. It takes one of two formats, depending on the instruction being modified. The first form of the wide instruction modifies one of the instructions iload, fload, aload, lload, dload, istore,

docs.oracle.com

 

μš°λ¦¬κ°€ λ””μ»΄νŒŒμΌν•œ κ²°κ³Όμ—μ„œ μ°Ύμ•„λ³Ό 수 μžˆλ‹€.

 

Code:
      stack=4, locals=4, args_size=1
         0: ldc           #7                  // String μ›Œλ‹‰
         2: astore_1
         3: bipush        25
         5: istore_2
         6: new           #9                  // class Person
         9: dup
        10: aload_1
        11: iload_2
        12: invokespecial #11                 // Method Person."<init>":(Ljava/lang/String;I)V
        15: astore_3
        16: aload_3
        17: invokevirtual #14                 // Method Person.print:()V
        20: return

 

μš°λ¦¬κ°€ μ§  μ†ŒμŠ€μ½”λ“œμ˜ μžλ°” λ°”μ΄νŠΈμ½”λ“œλ₯Ό λΆ„μ„ν•˜κΈ° μœ„ν•΄μ„œ μ•Œμ•„μ•Όν•  λͺ‡κ°€μ§€ λͺ…λ Ήμ–΄λ₯Ό μ•Œμ•„λ³΄μž

 

  • aload : local variable 을 stack 에 push ν•œλ‹€
  • ldc : constant pool μ—μ„œλΆ€ν„° #index 에 ν•΄λ‹Ήν•˜λŠ” 데이터λ₯Ό κ°€μ Έμ˜¨λ‹€
  • astore : local variable 에 값을 μ €μž₯ν•œλ‹€.
  • invokespecial : instance Method λ₯Ό ν˜ΈμΆœν•˜κ³  κ²°κ³Όλ₯Ό stack 에 pushν•œλ‹€.
  • new : μƒˆλ‘œμš΄ 객체λ₯Ό μƒμ„±ν•œλ‹€.
  • invokevirtual : λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•œλ‹€.
  • dup : stack 에 μžˆλŠ” top 을 λ³΅μ‚¬ν•œλ‹€.

 

이외에도 정말 λ§Žμ€ λͺ…λ Ήμ–΄κ°€ μ‘΄μž¬ν•˜κ³  μ˜λ―Έκ°€ μ‘΄μž¬ν•˜λŠ”λ°, ν•œκΈ€λ‘œλœ μ„€λͺ…은 aroundck λ‹˜μ˜ λΈ”λ‘œκ·Έ μ—μ„œ 확인할 수 μžˆλ‹€.

λŒ“κΈ€