논리 주소와 물리 주소

 

위 그림과 같이 CPU 내부에는 논리 주소와 물리 주소로 주소 공간을 나눠져 있다. 논리 주솔르 물리 주소로 변환하는 방법은 무엇일까?

CPU와 실행 중인 프로그램은 현재 메모리 몇 번지에 무엇이 저장되어 있는지 다 알고 있을까?

 

물리 주소와 논리 주소

메모리에 저장된 값들은 시시각각 변한다. 새롭게 실행되는 프로그램은 새롭게 메모리에 적재되고, 실행이 끝난 프로그램은 메모리에서 삭제된다. 같은 프로그램을 실행하더라도 실행할 때마다 적재되는 주소는 달라진다. 

 

이런 상황에서 어떤 주소에 어떤 명령어가 저장되어 있는지 일일이 다 알 수 있을까? 

 

불가능하다. 그래서 이러한 문제를 극복하기 위해 주소체계를 물리 주소와 논리 주소로 나눈 것이다.

 

물리 주소

  • 메모리 입장에서 바라본 주소
  • 말 그대로 정보가 실제로 저장된 하드웨어상의 주소

논리 주소

  • CPU와 실행 중인 프로그램 입장에서 바라본 주소
  • 실행 중인 프로그램 각각에게 부여된 0번지부터 시작하는 주소

그렇다면 물리 주소와 논리 주소 간의 변환은 어떻게 이루어질까?

물리 주소와 논리 주소의 변환

 

MMU(메모리 관리 장치)라는 하드웨어에 의해 변환된다. MMU는 논리 주소와 베이스 레지스터(프로그램의 기준주소) 값을 더하여 논리 주소를 물리 주소로 변환한다.

MMU에 의한 변환 예시

 

MMU애 의한 변환 예시(2)

 

 

베이스 레지스터 : 프로그램의 가장 작은 물리 주소(프로그램의 첫 물리 주소)를 저장

 

논리 주소 : 프로그램의 시작점으로부터 떨어진 거리

 

메모리 보호

한계 레지스터

  • 프로그램의 영역을 침범할 수 있는 명령어의 실행을 막는다.
  • 베이스 레지스터가 실행 중인 프로그램의 가장 작은 물리 주소를 저장한다면, 한계 레지스터는 논리 주소의 최대 크기를 저장한다.
  • 베이스 레지스터의 값 <= 프로그램의 물리 주소 범위 < 베이스 레지스터 + 한계 레지스터 값

CPU가 접근하려는 논리 주소는 한계 레지스터가 저장한 값보다 커서는 안된다.

논리 주소의 값

 

예를 들어, 베이스 레지스터에 100, 한계 레지스터에 150이 저장되어 있다고 가정해보자.

-> 물리 주소 시작점이 100번지, 프로그램의 크기(논리 주소의 최대 크기)는 150이라는 말이다.

 

위와 같은 그림의 경우 한계 레지스터보다 큰 메모리 주소에 접근하려고 하기 때문에 이를 막아준다.

 

이처럼 CPU는 메모리에 접근하기 전 접근하고자 하는 논리 주소가 한계 레지스터보다 작은지를 항상 검사한다.

이로인해 실행 중인 프로그램의 독립적인 실행 공간을 확보하고, 하나의 프로그램이 다른 프로그램을 침범하지 못하게 보호한다

 

'컴퓨터구조와 운영체제' 카테고리의 다른 글

보조기억장치  (1) 2024.07.20
캐시 메모리  (0) 2024.07.12
RAM의 특성과 종류  (1) 2024.07.03
명령어 집합 구조  (2) 2024.07.02
명령어 병렬 처리 기법(CPU)  (3) 2024.07.01

자바의 예외처리 방법인 try-catch에 대해서 정리해보자

 

다음의 코드를 보고 결과값을 생각해보자

public static void main(String[] args) throws Exception {
	
    for(int i = 0; i < 10; i++) {
    	System.out.println(i);
        
        if(i==5) {
        	throw new Exception();
        }   
    }
}

 

해당 코드를 실행하면 출력값은 어떻게 될까???

 

출력값 : 0 1 2 3 4 5 

 

5번 인덱스 출력 후  중단된다.

 

그럼 다음 코드의 출력값은 어떨까?

 

public static void main(String[] args) throws Exception {
	
    for(int i = 0; i < 10; i++) {
    	System.out.println(i);
        
        if(i==5) {
        	try {
            	throw new Exception();
            } catch (Exception e) {
            	e.printStackTrace();
            }
        	
        }   
    }
}

 

출력값 : 0 1 2 3 4 5 6 7 8 9

 

try-catch 내에서 예외가 발생하면 해당 예외는 catch 블록을 통해 예외가 처리된다.

따라서, 정상적으로 for loop가 작동하게 된다.

 

반면에, 맨위 코드의 경우 throw new Exception(); 을 통해 예외를 던지고 있다. 하지만 이것을 받아줄 catch 부분이 없기 때문에 종료되게 된다.

 

 

다중 try-catch

public static void main(String[] args) {

		try {
			System.out.println("외부 try 로직 수행.");
			try {
				System.out.println("내부 try 로직 수행");
				Exception e = new Exception();
				throw e;
			} catch (Exception e) {
				System.out.println("내부 try catch문 exeption : " + e);
				System.out.println("예외 던지기 한번 더");
				throw e;
			}finally {
				System.out.println("finally 구문 출력");
			}
		} catch (Exception e) {
			System.out.println("외부 try - catch exception : " + e);
		}
		System.out.println("종료");
	}

위 코드의 실행 순서를 생각해보자.

  1. '외부 try 로직 수행.' 출력
  2. '내부 try 로직 수행.' 출력
  3. '내부 try catch문 exception : java.lang.Exception' 출력
  4. '예외 던지기 한번 더' 출력
  5. 'finally 구문 출력' 출력
  6. '외부 try - catch exception : java.lang.Exception' 출력
  7. '종료' 출력

내부의 예외처리부분인 catch에서 강제로 Exception 을 한번 더 발생시켰을때 처리해줄 catch 부분이 외부 catch 부분이기 때문에

'외부 try - catch exception : java.lang.Exception' 이 출력되게 된다.

'Back-End > Java' 카테고리의 다른 글

HTTP 요청 하나당 스레드는 몇 개나 동작할까?  (0) 2025.05.12
할머니도 이해할 수 있는 자바 Thread  (0) 2025.05.01
스레드  (1) 2024.07.05
프로세스  (0) 2024.07.04
Java HTTP 통신  (4) 2024.06.12

사전적 의미로 한 가닥의 실이라는 뜻으로 한가지 작업을 실행하기 위해 순차적으로 실행할 코드를 실처럼 이어놓았다고 해서 유래된 이름이다. 하나의 스레드는 하나의 코드 실행 흐름이므로 한 프로세스 내 스레드가 2개라면 2개의 실행 흐름이 생긴다.

 

프로세스가 할당받은 자원을 이용하는 실행의 단위이며

  • 프로세스 내의 명령어 블록으로 시작점과 종료점을 가진다.
  • 실행중에 멈출 수 있으며 동시에 수행 가능하다.
  • 어떠한 프로그램 내에서 특히 프로세스 내에서 실행되는 흐름의 단위이다.

자바에서는 멀티 스레딩을 통해 하나의 프로세스 내에서 여러 스레드가 병렬로 실행될 수 있다.

 

스레드의 특징

  1. 공유 주소 공간 : 같은 프로세스 내의 스레드들은 동일한 메모리 공간을 공유한다. 이를 통해 스레드 간 데이터 공유와 통신이 매우 효율적이다.
  2. 독립적인 실행 흐름 : 각 스레드는 자신만의 실행 흐름을 가지며, 독립적으로 스케줄링되어 실행할 수 있다.
  3. 스레드 동기화 : 스레드들이 같은 자원을 동시에 접근할 때 발생할 수 있는 문제를 방지하기 위해 동기화 메커니즘이 필요하다 자바에서는 'synchronized' 키워드와 'java.util.concurrent' 패키지의 여러 클래스를 통해 동기화를 지원한다.
  4. 생성 및 관리 : 자바에서 스레드는 두 가지 방법으로 실행할 수 있다. 
    1. 'Tread' 클래스를 상속받아 새로운 클래스를 만들고 'run()' 메서드를 오버라이딩
    2. 'Runnable' 인터페이스를 구현하여 스레드 객체에 전달
// Thread 클래스를 상속받아 스레드 생성
class MyThread extends Thread {
    public void run() {
        System.out.println("Thread is running.");
    }
}

MyThread t1 = new MyThread();
t1.start();

// Runnable 인터페이스를 구현하여 스레드 생성
class MyRunnable implements Runnable {
    public void run() {
        System.out.println("Thread is running.");
    }
}

Thread t2 = new Thread(new MyRunnable());
t2.start();

 

스레드의 동작을 확인할 수 있는 예제

public class Sample extends Thread {
    int seq;

    public Sample(int seq) {
        this.seq = seq;
    }

    public void run() {
        System.out.println(this.seq + " thread start.");  // 쓰레드 시작
        try {
            Thread.sleep(1000);  // 1초 대기한다.
        } catch (Exception e) {
        }
        System.out.println(this.seq + " thread end.");  // 쓰레드 종료 
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {  // 총 10개의 쓰레드를 생성하여 실행한다.
            Thread t = new Sample(i);
            t.start();
        }
        System.out.println("main end.");  // main 메서드 종료
    }
}

 

위의 예제는 총 10개의 스레드를 실행시키는 예제이다. 어떤 스레드인지 확인하기 위해 스레드마다 생성자에 순서를 부여한다. 그리고 시작과 종료를 출력하게 했고 시작과 종료 사이에 1초의 간격이 생기도록 Tread.slepp(1000) 을 작성하였다. 그리고 main 메서드 종료 시 'main.end'를 출력하도록 했다. 결과를 보자

0 thread start.
4 thread start.
6 thread start.
2 thread start.
main end.
3 thread start.
7 thread start.
8 thread start.
1 thread start.
9 thread start.
5 thread start.
0 thread end.
4 thread end.
2 thread end.
6 thread end.
7 thread end.
3 thread end.
8 thread end.
9 thread end.
1 thread end.
5 thread end.

 

결과는 출력 때마다 다르다. 0번 스레드부터 9번 스레드까지 순서대로 실행되지 않고, 그 순서가 일정하지 않은 것을 보면 스레드는 순서에 상관없이 동시에 실행된다는 사실을 알 수 있다. 더욱 놀라운 사실은 스레드가 종료되기 전 main 메서드가 종료되었다는 사실이다.

 

그렇다면 모든 스레드가 종료된 후에 main 메서드를 종료하고 싶은 경우에는 어떻게 해야 할까?

import java.util.ArrayList;

public class Sample extends Thread {
    int seq;
    public Sample(int seq) {
        this.seq = seq;
    }

    public void run() {
        System.out.println(this.seq+" thread start.");
        try {
            Thread.sleep(1000);
        }catch(Exception e) {
        }
        System.out.println(this.seq+" thread end.");
    }

    public static void main(String[] args) {
        ArrayList<Thread> threads = new ArrayList<>();
        for(int i=0; i<10; i++) {
            Thread t = new Sample(i);
            t.start();
            threads.add(t);
        }

        for(int i=0; i<threads.size(); i++) {
            Thread t = threads.get(i);
            try {
                t.join(); // t 쓰레드가 종료할 때까지 기다린다.
            }catch(Exception e) {
            }
        }
        System.out.println("main end.");
    }
}

 

생성된 스레드를 ArrayList 객체인 threads 에 담은 후 main 메서드가 종료되기 전에 threads 객체에 담긴 각각의 스레드에 join 메서드를 호출하여 스레드가 종료될때까지 대기하도록 했다. join 메서드는 스레드가 종료될 때까지 기다리게 하는 메서드이다.

 

0 thread start.
5 thread start.
2 thread start.
6 thread start.
9 thread start.
1 thread start.
7 thread start.
3 thread start.
8 thread start.
4 thread start.
0 thread end.
5 thread end.
2 thread end.
9 thread end.
6 thread end.
1 thread end.
7 thread end.
4 thread end.
8 thread end.
3 thread end.
main end.

 

이렇게 하면 'main end.' 문자열이 가장 마지막에 출력되는 것을 확인할 수 있다.

'Back-End > Java' 카테고리의 다른 글

할머니도 이해할 수 있는 자바 Thread  (0) 2025.05.01
try-catch  (0) 2024.07.06
프로세스  (0) 2024.07.04
Java HTTP 통신  (4) 2024.06.12
Exception과 Transaction  (1) 2024.02.05

+ Recent posts