자바 개발 환경 이해
- JVM (Java Virtual Machine)
- JRE (Java Runtime Envionment)
- JDK (Java Development Kit)
현실세계 | 자바 개발 환경 |
---|---|
소프트웨어 개발 도구 | JDK 자바 개발 도구 - JVM용 소프트웨어 개발 도구 |
운영체제 | JRE 자바 실행 환경 - JVM용 OS |
하드웨어 - 물리 컴퓨터 | JVM 자바 가상 기계 - 가상의 컴퓨터 |
배포되는 JDK, JRE, JVM은 JDK가 JRE를 포함하고 다시 JRE는 JVM을 포함하는 형태로 배포된다.
- JDK - 자바 소스 컴파일러
javac.exe
포함 - JRE - 자바 프로그램 실행기
java.exe
포함
자바는 기존 언어로 작성한 프로그램을 각 플랫폼 용으로 배포되는 설치 파일을 따로 준비해야 했던 불편함을 없애기 위해 이러한 구조를 택했다.
- 윈도우 95용, 윈도우 XP용, 윈도우 7용 윈도우 8용, 리눅스용, 애플 맥 OS X용 등
이러한 자바 특성을
Wirte Once Run Anywhere
라고 한다.
프로그램 메모리 사용 방식
- 모든 프로그래밍 공통된 메모리 사용 방식
- 객체 지향 프로그램 메모리 사용 방식 #T메모리구조
메모리 코드 실행 영역을 공부하면 컴퓨터 작동 원리를 이해하는 데 큰 도움이 된다. (어셈블리어 공부)
자바의 절차적/구조적 프로그래밍 유산
자바에는 절차적/구조적 프로그래밍의 유산이다.
절차적 프로그래밍을 한마디로 표현하면 ‘goto를 쓰지 말라’ 이다.
자바 예약어
자바의 예약어 중 구조적/절차적 프로그래밍 유산
- boolean
- break
- byte
- case
- char
- const
- continue
- default
- do
- double
- else
- float
- for
- goto
- if
- import
- int
- long
- native
- switch
- return
- short
- strictfp
- void
- volatile
- while
함수(function)와 메서드(method)의 차이는?
- 전혀 다르지 않다.
구조적 프로그래밍은 함수를 쓰는 것이다.
- 중복 코드를 한 곳에 모아서 관리할 수 있다.
- 논리를 함수 단위로 분리해서 이해하기 쉬운 코드를 작성할 수 있다.
- 공유 사용 시 문제가 발생하기 쉬운 전역 변수보다는 지역 변수를 쓰는 것.
결론은 객체 지향 언어에서 절차적/구조적 프로그래밍의 유산은 제어문이 존재할 수 있는 유일한 공간인 메서드 내부에서 확인 할 수 있다.
절차적/구조적 프로그래밍에서 함수라 불렀는데 객체 지향에서는 좀 다르게 불러야 하지 않을까? 해서 메서드 라고 불렀다고 한다.
차이라고는 함수는 클래스나 객체와 아무 관계가 없지만 메서드는 반드시 클래스 정의 안에 존재해야 한다.
main() 메서드 : 메서드 스택 프레임
main() 메서드는 프로그램이 실행되는 시작점 이다. mian() 메서드가 실행될 때 메모리, T 메모리에 어떤 일이 일어날까?
public class Start { public static void main(String[] args) { System.out.println("Hello OOP!!!"); } }
T 메모리 구조
Start 클래스가 실행되는 과정
JRE는 눈에 보이지 않게 뒤에서 JVM을 부팅하고, JVM은 메모리 구조를 만들고, java.lang 패키지 로딩, 각종 클래스 로딩, 메서드 스택 프레임 배치, 변수 공간 배치 등 여러가지 일을 처리 함
- JRE는 프로그램 안에 main() 메서드가 있는지 확인
- JRE는 Start 클래스에서 main() 메서드를 찾으면 프로그램 실행을 위한 사전 준비
- 가상 기계인 JVM에 전원을 넣어 부팅하는 것
- 부팅된 JVM은 목적 파일을 받아 그 목적 파일을 실행
main() 메서드의 끝을 나타내는 닫는 중괄호를 만나면 스택 프레임이 소멸되고, JRE는 JVM을 종료하고 메모리에서 사라진다.
메서드 실행 전 JVM 전처리 작업
JVM이 맨 먼저 하는 일은 "전처리"라고 하는 과정이다
1) 스테틱 영역에 java.lang
패키지 배치
- JVM은 가장 먼저 java.lang 패키지를 T 메모리의 스테틱 영역에 가져다 놓는다.
System.out.println()
메서드를 사용할 수 있음
2) 스테틱 영역에 클래스와 임포트 패키지 배치
6. JVM은 개발자가 작성한 모든 클래스와 임포트 패키지도 스테틱 영역에 가져다 놓는다.
스택 영역 할당
메서드들의 놀이터는 스택이다. 중괄호를 만날 때 마다 스택 프레임이 하나씩 생김.
7. main()
메서드 스택 프레임이 스택 영역에 할당 됨
8. 메서드의 인자 args
를 저장할 변수 공간 할당
System.out.println(“Hello OPP!!”) 구문이 실행되면 T 메모리는 어떻게 될까?
System.out.println() 구문이 코드 실행 공간에서 실행되면 GPU에 화면을 의뢰하게 된다.
이때 데이터 저장 공간인 T 메모리에는 아무런 변화가 없음.
변수와 메모리
public class Start2 { public static void main(String[] args) { int i; i = 10; double d = 20.0; } }
위 그림은 Start2 클래스의 main() 메서드 시작 지점까지의 T 메모리 상태이다.
main() 스택 프레임
int i;
구문이 실행되면 메모리에 4바이트 크기의 정수 저장 공간을 마련하라는 명령이다.
JVM은 i 변수를 위한 공간을 만들어야 한다.
- main() 스택 프레임 안에 밑에서부터 차곡차곡 변수 공간을 확보.
- 변수 i에 저장된 값은 "알 수 없는 값"이 있다.
- 왜냐면, 이전에 해당 공간의 메모리를 사용했던 청소하지 않고 간 값을 그대로 가지고 있다.
- 이어서 변수 i 값 할당 및 d 값 할당 후 T 메모리 상태는 아래와 같다.
변수 초기화를 하지 않으면?
변수 i 를 선언만 하고 초기화 하지 않은 상태에서 i 변수를 사용하면 자바 컴파일러 #javac 가 경고를 한다.
The local variable i may not have been initialized
- 그 지역 변수 i 는 초기화 되지 않았을 수 도 있습니다.
블록 구문과 메모리 : 블록 스택 프레임
public class Start3 { public static void main(String[] args) { int i = 10; int k = 20; if(i == 10) { int m = k + 5; k = m; } else { int p = k + 10; k = p; } //k = m + p; } }
if문을 만나기 전 T메모리 상태는 다음과 같다.
if문 조건에 따라 분기를 일으킨다. i에 저장된 값이 10인지 조건을 확인하고, 비교 결과 값이 true이면 if블록이 실행 된다. 중괄호를 만나면서 if(true) 블록의 스택 프레임이 만들어 진다.
int m = k + 5;
블록은 먼저 int m의 영역을 생성한다.
그리고, k+5 연산 값을 변수 m에 할당한다. 이때 if(true) 스택 프레임 밖에 있으면서 main() 메서드 스택 프레임 안에 있는 k 변수를 연산에 참여시킨다.
k = m;
블록을 만나면 T 메모리는 다음과 같다.
그리고 닫는 중괄호를 만나면 if 블록 스택 프레임은 스택 영역에서 사라진다.
만약 if~else 블록 안에 있는 변수 할당한다면?
자바 컴파일러 #javac 가 오류라며 컴파일을 거부한다.
- m 이라는 변수를 찾을 수 없습니다.
- p 라는 변수를 찾을 수 없습니다.
지역 변수와 메모리
변수는 어디에 있을까? T 메모리는 세 개의 영역 중 세 군데 모두에 다 있다.
세 군데 각각의 변수는 각기 다른 목적을 가진다.
- 지역 변수
- 스택 영역
- 스택 프레임 내부
- 클래스 멤버 변수
- 스태틱 영역
- JVM이 종료될 때까지 고정된(static) 상태로 유지된다.
- 객체 멤버 변수
- 힙 영역
- 객체와 함께 가비지 컬렉터라고 하는 힙 메모리 회수기에 의해 소멸된다.
외부 스택프레임에서 내부 스택 프레임의 변수에 접근하는 것은 불가능하나 그 역은 가능하다.
스택 메모리 내의 스택 프레임 안의 변수를 지역 변수라 한다. 그 지역(스택 프레임)에서만 사용할 수 있고, 외부에서 사용하지 못하기 때문이다. 그리고 그 지역이 사라지면 메모리에서 지역 변수도 함께 사라진다.
메서드 호출과 메모리 : 메서드 스택 프레임 2
public class Start4 { public static void main(String[] args) { int k = 5; int m; m = square(k); } private static int square(int k) { int result; k = 25; result = k; return result; } }
5번째 int m;
다음 블록을 실행할 때 T 메모리 상태는 다음과 같다.
6번째 줄에서 m = square(k);
메서드를 호출한다. square() 메서드가 선언 된 9번째 줄로 이동한다.
11번째 줄인 int result;
다음 블록을 실행할 때 T 메모리 상태는 다음과 같다.
메서드 호출이 일어나면 무조건 호출되는 메서드의 스택 프레임에 T 메모리 스택 영역이 새로 생성된다.
11번째 라인에 의해 생성되는 변수 공간이 맨 아래, 그다음 인자를 저장할 변수 공간, 마지막으로 메서드의 지역 변수가 자리를 잡는다.
12번쩨 즐인 k = 25;
블록을 실행하면 T 메모리는 다음과 같다.
main() 메서드가 가진 변수 k와 square() 메서드가 가진 변수 k가 이름만 같지 실제로 서로 별도의 변수 공간이다. >> Call By Value(값에 의한 호출)
그래서 square 메서드의 k 변수는 무슨 짓을 해도 main 메서드의 k 변수에 영향이 없다.
square() 메서드의 닫는 중괄호를 만나면 반환값을 돌려주면서 스택 프레임은 스택에서 사라진다.
다른 스택 프레임의 지역 변수에 접근할 수 있을까?
절대 접근할 수 없다.
메서드를 블랙박스화 한다
라는 말이 있는데, 메서드 사이에서 입력 값과 반환 값을 의해서만 전달 될 뿐 내부 지역 변수를 볼 수 없다.
main() 프레임 내에서 square() 메서드를 호출하여 실행되고 있는 동안, square 메서드 내 실행 명령문에서 T메모리에 존재하는 main 메서드의 지역 변수를 참조할 수 있을 거 같지만, 자바 개발자들이 금지시켰다.
- 이것이 이치에 맞기 때문. 메서드는 서로의 고유 공간인데 서로 침범하면 무단 침입이 된다.
- 포인터 문제 때문. square() 메서드에서 main() 메서드 내부의 지역변수에 접근하려면 해당 지역 변수 위치를 알아야 하는데, 포인터가 없다.
- 호출하는 메서드 내부의 지역 변수를 호출 당하는 쪽에서 제어할 수 있게 만드려면 결국 포인터를 주고 받아야 한다. 이전 포인트 문제 이유와 같다.
- 메서드를 호출하면서 만들어지는 스택 구조는 항시 변화한다.
전역 변수도 있긴 하지만, 가급적 전역 변수는 쓰지 않는 것이 좋다.
전역 변수와 메모리
public class Start5 { static int share; public static void main(String[] args) { share = 55; int k = fun(5, 7); System.out.println(share); } private static int fun(int m, int p) { share = m + p; return m - p; } }
share 변수에 static 키워드가 붙어있다. T 메모리의 스태틱 영역에 변수 공간이 할당된다.
5번째 줄에서 share = 55;
를 실행하고 나면 스태틱 영역에 있는 share 변수에 값이 새로 할당된다.
13번째 줄 share = m + p
를 실행하고 나면 share 변수에 새로 할당된 상태가 된다.
- 디버거로 스태틱 영역의 share 변수 값을 확인할 수 있음
다시 main 영역의 fun 메서드를 호출했던 구문이 끝나게 되면 fun 메서드 스택 프레임은 소멸된다.
변수 유형
- 스택 프레임에 종속적인 지역 변수
- 스택 프레임에 독립적인 전역 변수
전역 변수는 코드 어느 곳에서나 접근할 수 있다고 하여 전역 변수이고, 여러 메서드들이 공유해서 사용한다고 해서 공유 변수라고도 한다.
왜 전역변수를 쓰지 말라고 할까?
- 프로젝트 규모에 따라 코드가 커지면서 여러 메서드에서 전역 변수의 값을 변경한다면 T메모리로 추적하지 않는 이상 전역 변수에 저장된 값을 파악하기 쉽지 않다.
- 읽기 전용으로 값을 공유해서 전역 상수로 쓰는 것은 적극 추천
- 멀티 스레드 프로그램을 학습한 경험이 있다면 전역 변수가 어떤 문제를 일으키는지 알 수 있다.
멀티 스레드 / 멀티 프로세스 이해
멀티 스레드(Multi Thred)의 T 메모리 모델은 스택 영역을 스레드 개수만큼 분할해서 쓰는 것
멀티 스레드는 하나의 T 메모리 안에서 스택 영역만 분할한 것이기 때문에 하나의 스레드에서 다른 스레드의 스택 영역에는 접근할 수 없지만, 스태틱 영역과 힙 영역은 공유해서 사용하는 구조다. 멀티 프로세스 대비 메모리를 적게 사용할 수 있다.
멀티 프로세스(Multi Process)는 다수의 데이터 저장 영역, 다수의 T 메모리를 갖는 구조
멀티 프로세스는 하나의 프로세스가 다른 프로세스의 T 메모리 영역을 절대 침범할 수 없는 메모리 안전한 구조이지만 메모리 사용량은 그만큼 크다.
멀티 스레드에서 전역 변수 사용 문제점
스레드1이 공유 영역에 있는 전역 변수 A에 10을 할당했다.
그런데 CPU 사용권이 스레드2로 넘어가고 스레드2가 전역 변수 A에 20을 할당하고 다시 CPU 사용권이 스레드1로 넘어가서 A를 출력하면 어떻게 될까?
스레드1 입장에서는 갑자기 20이라는 값이 출력되는 문제가 발생한다.
쓰기 가능한 전역 변수를 사용하게 되면 스레드 안정성이 깨진다고 표현한다.
이를 보완하는 방법으로 락(lock)을 거는 방법이 있는데, 락을 거는 순간 멀티 스레드의 장점은 버린 것과 같다. 깊은 내용은 중고급 서적을 학습할 것.
public class Start6 extends Thread { static int share; public static void main(String[] args) { Start6 t1 = new Start6(); Start6 t2 = new Start6(); t1.start(); t2.start(); } public void run() { for (int count = 0; count < 10; count++) { System.out.println(share++); try { sleep(1000); } catch (InterruptedException e) { } } } }
실행 결과는 재밌다. for문이 돌며 찍히는 출력문이 2번씩 찍힌다.
그런데, 시간 차가 발생하면 share++ 이 되는 것이 스레드가 중첩되며 이전 값의 ++이 아닌 스레드1에서 ++된 값에서 스레드2에서 한번 더 ++이 되어서 for문 조건식은 10까지인데, 그 이상 숫자가 찍히기도 한다.
이클립스 내에서 T메모리 엿보기
Debug window에서 스택 현황을 확인할 수 있고, Variable Window에서 스택 프레임 현황을 확인할 수 있다.
이 장에서 설명하고자 한 내용
- 자바 개발 환경(JDK, JRE, JVM)
- 자바의 T 메모리 구조
- 스태틱 영역 - 클래스
- 스택 영역 - 메서드
- 힙 영역 - 객체
- 코드 실행에 따른 T 메모리 변화
- 멀티 스레드 및 멀티 프로세스 차이, 전역 변수 설정
- 메서드를 만들 때 순서도 또는 의사 코드를 작성하는 것이 좋다.
- 객체 지향 뿐만 아니라 현존하는 거의 모든 언어가 메모리를 사용하는 방식을 설명한 것이다.
'개발 관련' 카테고리의 다른 글
스프링 입문을 위한 자바 객체 지향의 원리와 이해 - 4. 자바가 확장한 객체 지향 (0) | 2023.12.25 |
---|---|
스프링 입문을 위한 자바 객체 지향의 원리와 이해 - 3. 자바와 객체 지향 (0) | 2023.12.25 |
스프링 입문을 위한 자바 객체 지향의 원리와 이해 - 1. 사람을 사랑한 기술 (0) | 2023.12.18 |