스터디/CS

[CS] 자바의 final, finally, finalize

kwang2134 2025. 4. 4. 16:06
728x90
반응형
728x90

final

자바의 final 키워드는 변수, 메서드, 클래스에 사용될 수 있으며 변경 불가능성을 부여합니다. 이 키워드는 코드의 안정성과 예측 가능성을 높이는데 중요한 역할을 합니다.

사용법

  1. final 변수
  • 특징: final 변수는 처음 초기화 된 후에는 값을 변경할 수 없음 → 상수처럼 작동
public final int num = 10;
num = 20; // 컴파일 에러 발생
  1. final 메서드
  • 특징: final 메서드는 하위 클래스에서 오버라이드 할 수 없음 → 메서드의 기본 동작을 보존하기 위해 사용
class Parent {
    public final void print() {
        System.out.println("final method");
    }
}

class Child extends Parent {
    // @Override
    // public void print() { // 컴파일 에러 발생
    //     System.out.println("Override");
    // }
}

	
  1. final 클래스
  • 특징: final 클래스는 다른 클래스가 상속할 수 없음 → 클래스의 기본 형태를 유지하고 보안을 강화하는데 유용
public final class FinalClass {
    // ...
}

// class NonFinalClass extends FinalClass { // 컴파일 에러 발생
// }

장점

  • 불변성: final 변수는 읽기 전용으로 값이 변경될 일이 없음 → 실행 중 의도치 않게 변경될 일이 없음
  • 스레드 안정성: final 필드는 한 번 초기화된 이후 변경되지 않아 멀티스레드 환경에도 안전
  • 성능 최적화: 컴파일러와 JVM은 final 키워드를 사용하는 요소에 대해 추가적인 최적화를 수행
    • final 키워드가 없는 가변 요소는 매번 어떤 값이 들어있는지 확인해야 함
    • final 키워드가 있는 요소는 컴파일 시점의 값이 변하지 않아 내부 값을 들여다볼 필요가 없음

최적화

  • 변수 최적화
    • final 변수: 컴파일 시점에 변수 값을 알 수 있으면 컴파일러는 변수 참조를 실제 값으로 대체 가능 → final int num = 10 이면 코드 내부적 num의 자리를 변수명이 아닌 10으로 대체하여 코드의 실행 속도를 향상
    • 비-final 변수: 변수의 값이 매번 변경될 수 있기 때문에 컴파일러는 변수 참조를 실제 값으로 대체 불가능 → 실행 시점에 매번 변수의 값을 확인 해야 함
  • 메서드 최적화
    • final 메서드: final 메서드는 오버라이드될 수 없어 컴파일러는 메서드 호출을 인라인으로 처리하거나 다른 최적화 기법 적용 가능 → 메서드 호출 오버헤드를 줄임
    • 비-final 메서드: 메서드가 오버라이드 될 수 있어 컴파일러는 메서드 호출 인라인과 최적화가 제한 → 런타임에 메서드 실제 구현을 확인해야 함
  • 클래스 최적화
    • final 클래스: final 클래스는 상속될 수 없어 컴파일러와 JVM은 클래스 내부 구조를 잘 이해하고 최적화 가능
      • 상속이 불가능하니 내부 메서드는 호출이 정적으로 결정됨
      • 런타임에서 메서드의 실제 구현을 확인할 필요가 없음
      • final 클래스의 메서드는 오버라이드 될 수 없어 인라인으로 처리할 가능성이 높음 → 함수 호출 오버헤드 감소
    • 비-final 클래스: 클래스가 상속될 수 있으므로 컴파일러와 JVM은 클래스 내부 구조를 변경될 수 있음을 고려

finally

자바의 예외 처리에 사용되는 블록으로 try, catch 블록 이후에 위치하며 예외가 발생 여부에 상관없이 실행됩니다. 주로 리소스 해제나 프로그램 정리 작업 수행에 사용됩니다.

특징

  • 항상 실행: 예외 여부와 상관없이 항상 실행
  • try 블록과 연결: try 블록 없이는 사용이 불가능
  • catch 블록의 선택성: try 블록은 필수지만 catch 블록은 선택적
  • 리소스 해제: 파일 스트림, 데이터베이스 연결, 네트워크 소켓 등과 같은 리소스를 해제하는 데 주로 사용 → 리소스 누수 방지
  • return 문과 finally 블록: try 블록에서 return문이 실행되더라도 finally 블록은 실행 → finally 블록은 프로그램의 흐름에 영향을 미치지 않음

finalize()

Object 클래스에 정의된 메서드로 가비지 컬렉터가 객체를 메모리에서 제거하기 전 호출됩니다. 객체가 소유한 리소스를 정리하거나 종료 작업을 수행하는 데 사용됩니다.

특징

  • 호출 시점: 가비지 컬렉터에 의해 호출되며 객체가 더 이상 참조되지 않을 때 호출 → 호출 시점은 JVM에 의해 결정(예측 불가)
  • 리소스 정리: 파일 스트림, 네트워크 연결, 데이터베이스 연결 등과 같은 리소스 해제에 사요
  • 메서드 정의: protected void finalize() throws Throable 형태로 정의 → 모든 클래스가 재정의할 수 있음을 의미
  • explicit 호출: 명시적 호출 시 가비지 컬렉터의 호출과 다르게 처리 → JVM은 일반 메서드로 처리하며 객체를 메모리에서 제거하지 않음

성능 문제

  • 가비지 컬렉션 지연
    • finalize() 메서드가 있는 객체는 가비지 컬렉터가 두 번의 사이클을 거쳐야만 메모리에서 제거됨
    • 첫 번째 사이클에서는 객체를 finalize() 메서드를 호출하기 위해 대기열에 넣음
    • 두 번째 사이클에서 실제로 메모리에서 제거
    • 위 과정이 메모리 해제를 지연시켜 성능에 부정적인 영향을 미침
  • 추가 메모리 사용
    • finalize() 메서드가 있는 객체는 더 오래 메모리에 머무르게 됨
    • 메모리 사용량이 증가하고 더 많은 가비지 컬렉션 사이클이 발생하게 함
    • CPU 사용량을 증가 시킴에 따라 성능 저하
  • finalizer 쓰레드 병목
    • finalize() 메서드는 별도의 스레드에서 실행
    • 별도의 쓰레드는 한 번에 하나의 객체만 처리 가능
    • 많은 객체가 빠르게 생성되면 finalizer 큐가 커질 수 있음
    • 메모리 누수와 OutOfMemoryError 유발 가능

자원 해제를 finalize()로 하는 경우

  • finalize() 메서드가 실제로 언제 호출될지 알 수 없기 때문에 중요한 자원 해제 작업을 finalize()에 의존하는 것은 좋지 않음 → 프로그램 종료 전 호출되지 않을 수도 있음
  • finalize()에서 발생한 예외는 무시되어 자원 누수로 이어질 수 있고 발생한 예외가 프로그램의 다른 부분에 영향을 미칠 수 있음

세 가지 키워드는 이름은 비슷하지만 연관성이 전혀 없는 다른 기능들이라 각각 어떤 기능인지 확실하게 알아야 할 필요가 있을 것 같다.

728x90