ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [2024_동계_모각코] 1회차(01/03)
    [CNU] Mogakco 2024. 1. 3. 23:01

     


    1회차 목표

     

    JVM
    Final 키워드의 쓰임과 이점

    Interface VS Abstract class


     

     

    1. JVM

    JRE (Java Runtime Environment) 의 핵심 구성 요소

    Java bytecode 가 다른 플랫폼에서 실행되는 것을 가능하게 해준다.

    Java application ↔ 사용 중인 현재 OS, 하드웨어 사이에서 abstraction layer 역할을 한다.

    • 이러한 abstraction layer 로써의 역할이 자바 언어가 플랫폼 독립적(JVM과 함께라면 어떤 시스템에서도 실행될 수 있는) 인 성격을 가지게 하는 것임.

    JVM의 내부 동작 과정과 특징

    1. Java 프로그램이 실행될 때, JVM 은 컴파일에 필요한 클래스 파일들(ex.bootstrap class loader, extension class loader, application class loader) 을 불러온다.
    2. Bytecode 검증 절차
      1. 보안과 제약조건을 위반하지 않는 코드인지, 자바 문법과 맞는지를 확인하는 과정을 거친다.
    3. Execution engine
      1. bytecode를 해석하고 실행시키는 엔진을 포함하고 있음.
    4. GC 로 메모리 관리를 자동으로 수행함.
      1. 더이상 쓰지 않는 객체를 찾아내 비우고 메모리 정리를 함.
    5. Built-in Security Manager
      1. 내장 보안 정책이 존재하고 허가되지 않은 접근을 예방할 수 있는 시스템을 갖추고 있음.
    6. Native Interface (JNI) 존재
      1. JNI? - 자바 프로그램이 native code(C나 C++ 로 쓰여진 native code) 를 호출할 수 있는 프로그램.
      2. 이게 무슨 말? > 자바 프로그램에서 native C++ 라이브러리, C++ 코드 자체를 불러오는게 가능하다는 뜻.
      3. 즉, low-level language 나 밑단의 os, 하드웨어와 상호작용 할 수 있는 기능을 제공한다는 뜻.

     

    Java와 Java Interpreter의 차이

    JVM 을 자바 bytecode 를 실행시킬 수 있는 하나의 큰 환경으로 보고, 그 과정에서 (bytecode를 로드하고 검증하고 실행하고) 쓸 수 있는 많은 기법 중에 하나를 interpretation 으로 봐야 함.

    따라서 Java Interpreter 를 쓰는 방법도 있다는 것이지 둘이 같이 개념이 절대 아님.

    ** 최근의 JVM은 interpretation 방법 보다 bytecode 변환과 실행에 Just-In-Time (JIT) compilation 기법을 많이 사용한다고 함.

    런타임에 동적으로 bytecode → native machine code 변환시킬 수 있는 최적화 기법이기 때문에 성능 향상에 많이 도움이 된다고 한다.

    1. 파이썬의 경우) Jython, GraalVM 이 존재.
      1. Jython - JVM 위에서 돌 수 있는 파이썬 언어의 implementation 프로그램.
      2. GraalVM - 파이썬 뿐만 아니라 JS, Ruby 와 같은 언어들의 코드를 GraalVM Native Image 라는 기술을 사용하여 JVM 에서 돌 수 있는 코드로 컴파일 시켜준다.
    2. C++ 의 경우) LLVM, JavaCPP 가 존재.
      1. LLVM - LLVM C++ Backend 기술을 사용해서 C++ code를 LLVM Intermediate Representation code 으로 만들고, 후에 LLVM JIT compiler로 최적화하는 과정을 거친다.

     

    어떻게 이런 것이 가능할까?

    JVM의 유연성(flexibility) 과 extensibility(확장성) 덕에 가능한 것임.

    원래 JVM은 bytecode를 실행하기 위해서만 만들어졌지만, 아키텍쳐 자체는 다른 언어들도 이 매커니즘을 이용해서 사용할 수 있도록 설계되었다.

    예를 들어) JVM 은 stack-based bytecode 형식 위에서 돌아가기 때문에 무슨 언어든 간에 Java bytecode 로만 변환될 수 있다면 JVM에서 실행시킬 수 있다.

     

     

    Java와 함께 가상 머신(JVM)을 사용하면 얻을 수 있는 장점

    1. 플랫폼 독립성: Java 코드를 바이트코드로 컴파일하면 호환 가능한 JVM이 있는 모든 플랫폼에서 실행할 수 있음.
      1. 자바 언어의 Write once, Run anywhere 원칙 자바 언어를 유연하게 만듦.
    2. GC를 통한 메모리 관리
      1. 메모리 관리를 자동화하고 메모리 누수의 위험을 줄여줌.
    3. 보안
      1. JVM은 보안 관리자를 사용하여 시스템 리소스에 대한 접근을 제한하고 무단 접근으로부터 보호하여 믿을 수 없는 코드를 실행할 때 특히 Java 애플리케이션을 보다 안전하게 만듦.
      1. JIT 컴파일을 사용하여 바이트 코드를 런타임 중에 네이티브 머신 코드로 변환할 수 있기 때문에 이로 인해 pure interpretation 기법과 비교해 향상된 성능을 얻을 수 있음.JIT(Just-In-Time) 컴파일

    Java와 함께 가상 머신(JVM)을 사용하면 얻을 수 있는 단점

    1. Overhead 비용 발생
      1. JVM에서 Java 애플리케이션을 실행하는 것은 바이트 코드 해석과 JIT 컴파일로 인해 오버헤드가 발생할 수 있음 .
      2. JIT 컴파일된 코드가 빠를 수 있지만 그래도 특정 시나리오에서 native code 보다 느릴 수 있음.
    2. 메모리 사용량:
      1. JVM 기반 애플리케이션은 JVM 자체와 런타임 데이터 영역에서 추가적인 오버헤드로 인해 native application보다 더 많은 메모리를 사용할 수 있다.
    3. 시작 시간의 약간의 지연
      1. JVM 애플리케이션은 클래스를 로드하고 바이트 코드를 확인하며 기타 초기화 작업을 수행해야 하므로 시작 시간이 더 오래 걸릴 수 있다.
    4. 네이티브 코드와의 Interaction 제한
      1. 자바 네이티브 인터페이스(JNI)를 제공하여 네이티브 코드와 상호 작용할 수 있지만, 특정 시스템 수준의 기능과 직접 통합하는 것은 플랫폼에 직접 컴파일된 언어들과 비교해서 더 어려울 수 있다.
    5. Reverse Engineering의 위험성
      1. Java 바이트 코드는 상대적으로 쉽게 역어셈블할 수 있다고 함.
      2. 난독화 도구가 있기는 하지만 네이티브 코드보다 보호에 있어 더 어려울 수 있음

    2. Final 키워드 의 쓰임, 이점

    final 키워드의 쓰임:

    상수를 선언, 변수를 변경 불가능하게 만들거나 메서드 재정의를 방지하는 데 사용된다.

    final 키워드를 사용하는 장점은 다양한 보장을 제공한다는 점이 아닐까 싶음.

    1. 상수: final 키워드로 변수를 선언하면 값이 초기화된 후에는 변경할 수 없는 상수가 됨.
    2. 즉, 프로그램 실행 중에 수정되지 않아야 하는 값을 정의하는 데 유용함.
    final int MY_CONSTANT = 10;
    1. 불변성: final 키워드로 객체 참조 변수를 선언하면 초기화 된 후에는 해당 참조를 다른 객체를 가리키도록 변경할 수 없다.
    2. 이렇게 함으로써 변경할 수 없는 객체를 생성하는 데 도움이 되고 따라서 스레드 안전성과 코드 이해의 용이성과 같은 여러 이점이 있음.
    final String myString = "Hello";
    1. 메서드 재정의 방지: final 키워드로 메서드를 선언하면 하위 클래스에서 재정의 되는 것을 방지한다.
    2. 즉, 메서드의 동작이 모든 하위 클래스에서 일관되게 유지되도록 보장하고자 할 때 유용함.
    public class Parent {
        public final void doSomething() {
            // 구현
        }
    }
    
    public class Child extends Parent {
        // 컴파일 오류: Parent의 final 메서드 재정의 불가!
        // public void doSomething() { ... }
    }

     

    정리하자면 final 키워드를 적절하게 사용함으로써,

    • 자바 코드의 가독성, 유지보수성, 보안성을 향상시킬 수 있다.
    • 명확한 의도를 전달하고 필요할 때 코드의 특정 부분을 일정하고 변경할 수 없도록 보장한다.

    상수 보장:

    final 키워드로 변수를 선언하면 컴파일러는 이를 상수로 취급해서, 상수 접기(constant folding)라는 최적화를 수행하여 컴파일러가 표현식을 평가하고 코드 전체에서 변수를 해당 값(ex. 10)으로 대체함.

     

    이러한 최적화를 통해 중복된 조회를 제거하고 성능을 개선할 수 있다.

    final int MY_CONSTANT = 10;
    int result = MY_CONSTANT * 5; // 컴파일러가 MY_CONSTANT를 값 (10)으로 대체함.

     

    값의 불변 보장:

    final 키워드로 선언된 참조 변수는 단 한 번만 값을 할당할 수 있도록 컴파일러가 보장함. 만약에 변수에 다시 값을 할당하려고 하면 컴파일 오류가 발생.

    final String myString = "Hello";
    // 컴파일 오류: 불변 변수 'myString'에 값을 할당할 수 없음!
    myString = "World";

     

    메서드 재정의 방지:

    컴파일러는 메서드 디스패치를 최적화하고 하위 클래스에서 재정의를 확인하지 않고도 상위 클래스의 메서드가 항상 호출되도록 할 수 있음.

    정리하자면,

    이러한 컴파일 점검과 최적화는 컴파일러가 잠재적인 문제를 빨리 잡아내고 코드 품질을 향상시키며 더 효율적인 바이트코드를 생성할 수 있도록 도와준다고 할 수 있겠다.


    3. Interface VS Abstract class

    인터페이스와 추상 클래스 모두 클래스에 대한 계약을 정의하고 하위 클래스에서 구현해야 할 메서드 집합을 지정하는 데 사용된다.

    하지만 차이점을 아는게 중요.

    1. 개념적, 기능적 차이:
      • 인터페이스는 개념적으로 완전히 추상적인 클래스.
      • 구현이 없는 추상 메서드와 상수 필드(public, static, final)만 포함. **(Java 8)
      • 클래스는 여러 인터페이스를 구현할 수 있음. 즉, 다중 상속이 가능함.
      • 추상 클래스는 추상 메서드와 구현이 있는 메서드를 모두 포함할 수 있는 클래스.
      • 자바는 단일 클래스 상속을 지원하므로, 클래스는 오직 하나의 추상 클래스만 확장할 수 있음.
    2. 생성자 유무:
      • 인터페이스는 클래스가 아니기 때문에 생성자를 가질 수 없다. 직접적으로 인스턴스화 될 수 없기 때문.
      • 추상 클래스는 클래스다. 따라서 생성자를 가질 수 있고, 추상 클래스와 그 하위 클래스의 상태를 초기화하는 데 사용된다.
    3. 필드:
      • 인터페이스에 선언된 필드는 public, static, final. 즉, 상수이므로 구현하는 클래스에서 변경할 수 없음.
      • 추상 클래스는 인스턴스 변수를 가질 수 있고, 접근 제어자(public, protected, private)와 static 또는 final로 선언하지 않아도 된다.
    4. 확장성:
      • 인터페이스는 많은 클래스가 계약을 지킨 채로 구현되도록 더 좋은 유연성을 제공한다고 함. (다중 상속 때문)
      • 자바는 단일 클래스 상속을 지원하기 때문에, 추상 클래스에서의 상속 또한 단일 상속의 제한을 받을 수 밖에 없음.
      • 추상 클래스를 여러 하위 클래스에게 공통 기본 구현을 제공하는 목적으로 쓸 수도 있지만
      • 그럴거면 인터페이스를 사용하는게 개념적으로 더 맞지 않나… 생각함.

    정리하면,

    인터페이스는 계약을 정의하는 데 더 중점을 둔 반면에, 추상 클래스는 부분적인 구현을 제공하는 데 중점을 둔다.

    특정 기본 클래스에 강하게 결합되지 않고 여러 클래스가 공통 동작을 공유해야 하는 상황에서는 인터페이스가 선호되고

    하위 클래스가 특정 메서드를 구현하도록 강제하면서 기본 구현을 제공하고자 하는 경우에는 추상 클래스가 더 적합하다.

    public interface MyInterface {
        // Abstract method
        void doSomething();
    
        // Default method 직접 구현부
        default void doSomethingElse() {
            System.out.println("스터디 준비 왤케 오래 걸림");
        }
    }

     

    따라서, 이미 해당 인터페이스를 구현하고 있는 클래스들의 구현을 바꿀 필요 없이 새로운 기능을 추가한 것.

    (이미 구현한 클래스들은 default 메서드를 @Override 한 것. 새롭게 구현하는 클래스들은 재정의 할 필요가 없음)

    1. 자바의 핵심 설계 원칙 중 하나는 언어를 simple, clean, easy to learn 하게 유지하는 것이라고 함.
      1. **다이아몬드 문제: 클래스가 같은 메서드 이름을 가진 여러 클래스로부터 상속받을 때 모호성이 발생하는 상황.
    2. 추상 클래스에 다중 상속을 허용하면 클래스가 어떤 부모의 기능을 상속하는지와 같은 복잡한 상황이 발생할 수 있음.
    3. 인터페이스는 주로 클래스가 따라야 할 계약을 정의하는 데 사용됨. 그렇기 때문에 이 경우엔 다중 상속을 허용해도 모호하거나 복잡한 상황이 발생하지 않음.

     

    1회차 회고록

     

    이번 주 모각코 활동을 통해 JVM, final 키워드, 인터페이스와 추상 클래스에 대한 깊이 있는 이해를 할 수 있었다. JVM의 구조와 동작 방식을 배우며 Java가 어떻게 플랫폼 독립성을 유지하는지, 그리고 JIT 컴파일러와 GC가 어떻게 성능과 메모리 관리를 최적화하는지에 대해 실질적인 인사이트를 얻었다. 또한, final 키워드의 사용 이점과 상황별 적용 방법을 통해 코드의 안정성과 유지보수성을 높이는 방법을 배웠다. 첫 활동부터 많은 것을 공부하고 배울 수 있어서 더욱 의미있었고, 남은 주차도 모각코를 하며 유익하게 보내야겠다.

    댓글

Designed by Tistory.