Java

[Java] 예외처리

dalooong 2023. 5. 30. 14:42

1. 예외처리

1-1. 에러(error) vs 예외(exception)

자바에서는 애플리케이션 실행 시 발생할 수 있는 오류를 '에러(error)'와 '예외(exception)' 두 가지로 구분합니다.

에러는 메모리 부족, 스택오버플로우와 같이 발생하게 되면 복구할 수 없는 심각한 오류이고, 예외는 발생하더라도 수습할 수 있을 정도의 비교적 덜 심각한 오류입니다.

에러는 발생 시 막을 방도가 없지만, 예외는 프로그래머가 예외처리를 통해서 비정상종료를 막을 수 있는 것이죠.

 

1-2.  예외 클래스의 구조

Throwable 클래스를 상속 받는 클래스는 Error와 Exception이 있는데 모든 예외의 최고 상위 클래스는 당연히 Execption 입니다.

Error 의 경우 애플리케이션 레벨이 아닌 시스템 레벨의 심각한 영향을 주기 때문에 시스템에 변화를 주어 문제를 처리해야 하는 경우가 일반적입니다.

하지만 Exception은 개발자가 충분히 로직을 추가하여 처리할 수 있는 부분입니다.

 

1-3. Checked Exception과 Unchecked Exception 차이

 

위 그림 처럼 두 개 예외의 차이점은 처리여부입니다.

Checked Exception은 반드시 소스 코드내에서 예외를 처리해야만 실행단계로 넘어갑니다.

즉, 컴파일 단계에서 확인을 하기 때문에 반드시 예외처리를 구현한다(Check한다)라는 조건이 붙은 것이죠.

대표적으로는

  • IOException
  • SqlException

이 존재합니다.

public class CheckedException {
	public static void main(String[] args)
	{
		try
		{
            // Exception 클래스로 예외를 실행시킨다.
			throw new Exception();
		} catch (Exception e)
		{
            // Checked Exception 이므로 예외처리를 해주는데 해주지 않으면 컴파일 실패
			System.out.println("Exception");
		}
	}
}

Unchecked Exception은 반드시라는 조건이 없기 때문에 (Unchecked한다) 라는 조건이 붙은것이고,

실행 단계에서 발생 할 수 있는 모든 예외들을 말합니다.

특징으로는 프로그래머의 실수로 의해서 발생할 수 있는 예외라고 이해하시면 좋겠네요 🙂

대표적으로는

  • NullPointerException
  • IllegalArgumentException
  • IndexOutOfBoundException

이 존재합니다.

public class ExceptionEx1 {
	public static void main(String[] args)
	{
            // 에러가 발생하지만 컴파일은 가능하다.
		throw new RuntimeException();
	}
}

1-4. 예외 처리 방법

1. try-catch 블록을 사용한 예외 처리

  • try 블록 안에서 예외가 발생할 수 있는 코드를 작성합니다.
  • catch 블록은 try 블록에서 발생한 예외를 처리하는 부분입니다.
  • catch 블록은 예외 타입에 따라 여러 개를 작성할 수 있으며, 발생한 예외와 일치하는 catch 블록이 실행됩니다.
  • finally 블록은 선택적으로 작성할 수 있으며, 예외 발생 여부와 상관없이 항상 실행됩니다.
try {
    // 예외가 발생할 수 있는 코드
} catch (예외 타입1 변수명1) {
    // 예외 처리 코드
} catch (예외 타입2 변수명2) {
    // 예외 처리 코드
} finally {
    // 항상 실행되는 코드 (선택적)
}

2. throws를 사용한 예외 처리

  • 메서드 선언부에 throws 키워드를 사용하여 메서드에서 발생할 수 있는 예외를 명시합니다.
  • 메서드를 호출하는 곳에서 예외를 처리하도록 할 수 있습니다.
void 메서드명() throws 예외타입1, 예외타입2 {
    // 예외가 발생할 수 있는 코드
}

3. 사용자 정의 예외 처리

  1. 사용자 정의 예외 클래스
class MyException extends Exception {
    public MyException() {
        super("사용자 정의 예외가 발생했습니다.");
    }
    
    public MyException(String message) {
        super(message);
    }
}

위의 예제에서 MyException 클래스는 Exception 클래스를 상속받아 사용자 정의 예외 클래스를 만들었습니다. 생성자를 통해 예외 메시지를 설정할 수 있습니다.

  1. 사용자 정의 예외 클래스를 사용하는 코드
public class Main {
    public static void main(String[] args) {
        try {
            throwException();
        } catch (MyException e) {
            System.out.println(e.getMessage());
        }
    }
    
    public static void throwException() throws MyException {
        throw new MyException("사용자 정의 예외를 발생시킵니다.");
    }
}

위의 예제에서 throwException 메서드에서 **MyException**을 발생시키고, main 메서드에서 해당 예외를 처리합니다. 실행 결과는 "사용자 정의 예외를 발생시킵니다."라는 메시지가 출력됩니다.

이와 같이 사용자 정의 예외 클래스를 만들어 예외 상황에 대한 적절한 처리를 할 수 있습니다. 사용자 정의 예외 클래스를 만들 때는 예외의 특정 상황을 잘 표현하고 처리하기 위한 메시지와 추가적인 기능을 구현하는 것이 좋습니다.

2. JVM 동작방식 및 구조

2-1. 동작 과정

  1. 자바 언어로 작성된 애플리케이션을 실행하면 JVM은 OS로부터 메모리를 할당합니다.
  2. 자바 컴파일러(javac)가 자바 소스코드(.java)를 자바 바이트코드(.class)로 컴파일합니다.
  3. Class Loader를 통해 JVM Runtime Data Area로 로딩합니다.
  4. Runtime Data Area에 로딩 된 .class들은 Execution Engine을 통해 해석합니다.
  5. 해석된 바이트 코드는 Runtime Data Area의 각 영역에 배치되어 수행하며 이 과정에서 Execution Engine에 의해 GC의 작동과 스레드 동기화가 이루어집니다.

2-2. RunTime Data Area

자바 애플리케이션을 실행할 때 사용되는 데이터들을 적재하는 영역입니다.

모든 스레드가 공유해서 사용 (GC의 대상)

  • 힙 영역 (Heap Area)
    • new 키워드로 생성된 객체와 배열이 생성되는 영역입니다.
    • 주기적으로 GC가 제거하는 영역입니다.
  • 메서드 영역(Method Area)
    • 클래스 멤버 변수의 이름, 데이터 타입, 접근 제어자 정보와 같은 각종 필드 정보들과 메서드 정보 등이 생성되는 영역입니다.

스레드(Thread) 마다 하나씩 생성

  • 스택 영역(Stack Area)
    • 지역변수, 파라미터, 리턴 값, 연산에 사용되는 임시 값 등이 생성되는 영역입니다.
  • PC 레지스터 (PC Register)
    • Thread가 생성될 때마다 현재 스레드가 실행되는 부분의 주소와 명령을 저장하고 있는 영역입니다.
  • 네이티브 메서드 스택(Native Method Stack)
    • 자바 이외의 언어(C, C++, 어셈블리 등)로 작성된 네이티브 코드를 실행할 때 사용되는 메모리 영역입니다.

 

3. 에러(error) vs 예외(exception)

자바에서는 애플리케이션 실행 시 발생할 수 있는 오류를 '에러(error)'와 '예외(exception)' 두 가지로 구분합니다.

에러는 메모리 부족, 스택오버플로우와 같이 발생하게 되면 복구할 수 없는 심각한 오류이고, 예외는 발생하더라도 수습할 수 있을 정도의 비교적 덜 심각한 오류입니다.

에러는 발생 시 막을 방도가 없지만, 예외는 프로그래머가 예외처리를 통해서 비정상종료를 막을 수 있는 것이죠.

3-1. 예외 클래스의 구조

Throwable 클래스를 상속 받는 클래스는 Error와 Exception이 있는데 모든 예외의 최고 상위 클래스는 당연히 Execption 입니다.

Error 의 경우 애플리케이션 레벨이 아닌 시스템 레벨의 심각한 영향을 주기 때문에 시스템에 변화를 주어 문제를 처리해야 하는 경우가 일반적입니다.

하지만 Exception은 개발자가 충분히 로직을 추가하여 처리할 수 있는 부분입니다.

Checked Exception과 Unchecked Exception 차이

위 그림 처럼 두 개 예외의 차이점은 처리여부입니다.

Checked Exception은 반드시 소스 코드내에서 예외를 처리해야만 실행단계로 넘어갑니다.

즉, 컴파일 단계에서 확인을 하기 때문에 반드시 예외처리를 구현한다(Check한다)라는 조건이 붙은 것이죠.

대표적으로는

  • IOException
  • SqlException

이 존재합니다.

public class CheckedException {
	public static void main(String[] args)
	{
		try
		{
            // Exception 클래스로 예외를 실행시킨다.
			throw new Exception();
		} catch (Exception e)
		{
            // Checked Exception 이므로 예외처리를 해주는데 해주지 않으면 컴파일 실패
			System.out.println("Exception");
		}
	}
}

Unchecked Exception은 반드시라는 조건이 없기 때문에 (Unchecked한다) 라는 조건이 붙은것이고,

실행 단계에서 발생 할 수 있는 모든 예외들을 말합니다.

특징으로는 프로그래머의 실수로 의해서 발생할 수 있는 예외라고 이해하시면 좋겠네요 🙂

대표적으로는

  • NullPointerException
  • IllegalArgumentException
  • IndexOutOfBoundException

이 존재합니다.

public class ExceptionEx1 {
	public static void main(String[] args)
	{
            // 에러가 발생하지만 컴파일은 가능하다.
		throw new RuntimeException();
	}
}

3-2. 예외 처리 방법

1. try-catch 블록을 사용한 예외 처리

  • try 블록 안에서 예외가 발생할 수 있는 코드를 작성합니다.
  • catch 블록은 try 블록에서 발생한 예외를 처리하는 부분입니다.
  • catch 블록은 예외 타입에 따라 여러 개를 작성할 수 있으며, 발생한 예외와 일치하는 catch 블록이 실행됩니다.
  • finally 블록은 선택적으로 작성할 수 있으며, 예외 발생 여부와 상관없이 항상 실행됩니다.
try {
    // 예외가 발생할 수 있는 코드
} catch (예외 타입1 변수명1) {
    // 예외 처리 코드
} catch (예외 타입2 변수명2) {
    // 예외 처리 코드
} finally {
    // 항상 실행되는 코드 (선택적)
}

2. throws를 사용한 예외 처리

  • 메서드 선언부에 throws 키워드를 사용하여 메서드에서 발생할 수 있는 예외를 명시합니다.
  • 메서드를 호출하는 곳에서 예외를 처리하도록 할 수 있습니다.
void 메서드명() throws 예외타입1, 예외타입2 {
    // 예외가 발생할 수 있는 코드
}

3. 사용자 정의 예외 처리

  1. 사용자 정의 예외 클래스
class MyException extends Exception {
    public MyException() {
        super("사용자 정의 예외가 발생했습니다.");
    }
    
    public MyException(String message) {
        super(message);
    }
}

위의 예제에서 MyException 클래스는 Exception 클래스를 상속받아 사용자 정의 예외 클래스를 만들었습니다. 생성자를 통해 예외 메시지를 설정할 수 있습니다.

  1. 사용자 정의 예외 클래스를 사용하는 코드
public class Main {
    public static void main(String[] args) {
        try {
            throwException();
        } catch (MyException e) {
            System.out.println(e.getMessage());
        }
    }
    
    public static void throwException() throws MyException {
        throw new MyException("사용자 정의 예외를 발생시킵니다.");
    }
}

위의 예제에서 throwException 메서드에서 **MyException**을 발생시키고, main 메서드에서 해당 예외를 처리합니다. 실행 결과는 "사용자 정의 예외를 발생시킵니다."라는 메시지가 출력됩니다.

이와 같이 사용자 정의 예외 클래스를 만들어 예외 상황에 대한 적절한 처리를 할 수 있습니다. 사용자 정의 예외 클래스를 만들 때는 예외의 특정 상황을 잘 표현하고 처리하기 위한 메시지와 추가적인 기능을 구현하는 것이 좋습니다.

4. JVM 동작방식 및 구조

4-1. 동작 과정

  1. 자바 언어로 작성된 애플리케이션을 실행하면 JVM은 OS로부터 메모리를 할당합니다.
  2. 자바 컴파일러(javac)가 자바 소스코드(.java)를 자바 바이트코드(.class)로 컴파일합니다.
  3. Class Loader를 통해 JVM Runtime Data Area로 로딩합니다.
  4. Runtime Data Area에 로딩 된 .class들은 Execution Engine을 통해 해석합니다.
  5. 해석된 바이트 코드는 Runtime Data Area의 각 영역에 배치되어 수행하며 이 과정에서 Execution Engine에 의해 GC의 작동과 스레드 동기화가 이루어집니다.

4-2. RunTime Data Area

자바 애플리케이션을 실행할 때 사용되는 데이터들을 적재하는 영역입니다.

모든 스레드가 공유해서 사용 (GC의 대상)

  • 힙 영역 (Heap Area)
    • new 키워드로 생성된 객체와 배열이 생성되는 영역입니다.
    • 주기적으로 GC가 제거하는 영역입니다.
  • 메서드 영역(Method Area)
    • 클래스 멤버 변수의 이름, 데이터 타입, 접근 제어자 정보와 같은 각종 필드 정보들과 메서드 정보 등이 생성되는 영역입니다.

스레드(Thread) 마다 하나씩 생성

  • 스택 영역(Stack Area)
    • 지역변수, 파라미터, 리턴 값, 연산에 사용되는 임시 값 등이 생성되는 영역입니다.
  • PC 레지스터 (PC Register)
    • Thread가 생성될 때마다 현재 스레드가 실행되는 부분의 주소와 명령을 저장하고 있는 영역입니다.
  • 네이티브 메서드 스택(Native Method Stack)
    • 자바 이외의 언어(C, C++, 어셈블리 등)로 작성된 네이티브 코드를 실행할 때 사용되는 메모리 영역입니다.