자바 프로그램의 개발과 구동
소프트웨어 즉, 프로그램으내 개발자가 개발 도구를 이용해 개발, 운영체제를 통해 물리적 컴퓨터인 하드웨어 상에서 구동됨
=> 자바 개발 도구인 JDK를 이용해 개발된 프로그램은 JRE에 의해 가상의 컴퓨터인 JVM 상에서 구동됨
배포되는 JDK, JRE< JVM은 편의를 위해 JDK가 JRE를 포함하고, 다시 JRE는 JVM을 포함하는 형태로 배포됨
JDK는 자바 소스 컴파일러인 javac.exe 포함
JRE는 자바 프로그램 실행기인 java.exe 포함
-> 각 플랫폼(하드웨어와 OS의 조합) 용으로 배포되는 설치 파일을 따로 준비해야 했던 불편함 없앰
자바 개발자는 본인이 사용 중인 플랫폼에 설치된 JVM 용으로 프로그램을 작성, 배포하면 각 플랫폼에 맞는 JVM이 중재자로서 각 플랫폼에서 프로그램을 구동하는데 아무 문제가 없게끔 만들어줌(WORA)
프로그램이 메모리를 사용하는 방식
모든 프로그래밍 언어의 공통된 메모리 사용 방식: 코드 실행 영역/데이터 저장 영역
객체 지향 프로그램: 데이터 저장 영역을 분할:
코드 실행 영역/ Static 영역 / Stack 영역 + Heap 영역
자바에 존재하는 절차적/구조적 프로그래밍의 유산
객체지향 프로그래밍은 절차적/구조적 프로그래밍 기반
절차적 프로그래밍: goto를 쓰지 말라
자바에서도 goto, const 예약어 not used(const의 역할을 자바에서는 final)
JAVA에서는 왜 goto 사용하지 못함?
goto를 사용하게 되면 프로그램의 실행 순서가 인간이 이해하기에 너무 복잡해질 가능성이 있기 때문
goto를 통한 이동은 프로그램을 논리적으로 잘 구성하면 모두 피할 수 있음
구조적 프로그래밍: 함수를 쓰라 + 공유 사용 시 전역변수 보다는 지역변수 사용
중복 코드를 한 곳에 모아서 관리 가능, 논리를 함수 단위로 분리해서 이해하기 쉬운 코드 작성 가능
goto: 제어 흐름을 이리저리 이동시키는 용도 -> 순서도와 제어문
함수: 중복 코드 제거 + 논리 분할 용도
-> 객체 지향에서
제어: 순서도/제어문
함수: 메서드
-> 결론적으로! 객체 지향 언어에서 절차적/구조적 프로그래밍의 유산: 메서드 내부!!
함수(Function) vs 메서드(Method)
전혀 다르지 않음
절차적/구조적 프로그래밍에선 함수라 불렸는데 객체 지향에서는 좀 다르게 불리게 한 것 뿐
굳이 차이점을 찾자면,
함수는 클래스나 객체와 아무 관계 없음
메서드는 반드시 클래스 정의 안에 존재해야 함
객체 지향안에서 클래스 외부에 존재할 수 있는 것은 없음! (import문은?!ㅋㅋ)
import 구문은 타이핑을 적게 하기 위한 편의 기능(게으름의 산물)
다시 보는 main() 메서드: 메서드 스택 프레임
main() 메서드: 프로그램이 실행되는 시작점
Static 영역: 클래스들의 놀이터
Stack 영역: 메서드들의 놀이터
Heap 영역: 객체들의 놀이터
public class Start {
public static void main(String[] args) {
System.out.println("Hello OOP!!!");
}
}
1. JRE는 먼저 프로그램 안에 main() 메서드가 있는지 확인
2. Start 클래스에서 main 메서드 발견 -> JRE는 프로그램 실행을 위한 사전 준비에 착수 -> 가상의 기계인 JVM에 전원 넣어 부팅
3. 부팅된 JVM은 목적파일을 받아 그 목적 파일 실행; JVM이 맨 먼저 하는 일은 '전처리' 과정
모든 자바 프로그램이 반드시 포함하게 되는 패키지: java.lang 패키지
3-1. JVM은 먼저 java.lang 패키지를 Static 영역에 가져다 놓음(System.out.println() 같은 메서드 쓸 수 있음)
3-2. JVM은 개발자가 작성한 모든 클래스와 임포트 패키지도 Static 영역에 가져다 놓음
현재 코드에서 클래스는 Start 뿐
전처리 과정 끝!
main() 메서드가 실행되기 전 JVM에서 수행하는 전처리 작업들
1. java.lang 패키지를 메모리의 Static 영역에 배치
2. import된 패키지를 메모리의 Static 영역에 배치
3. 프로그램 상의 모든 클래스를 메모리의 Static 영역에 배치
4. main() 메서드가 놀기 위해 스택 프레임(stack frame)이 스택 영역에 할당됨
여는 중괄호를 만날 때마다 스택 프레임이 하나씩 생김
5. 메서드 인자 args를 저장할 변수 공간을 스택 프레임의 맨 밑에 확보;메서드 인자들의 변수 공간 할당)
6. main() 메서드 안의 첫 명령문 실행(코드 실행 공간은 별도로 있기 때문에 메모리의 데이터 저장 영역에는 변화 없음)
요약)
JRE: 뒤에서 JVM 자바 가상 기계 부팅
JVM: 메모리 구조 만들고, java.lang 패키지 로딩, 각종 클래스 로딩, main() 메서드 스택 프레임 배치, 변수 공간 배치 등
7. main() 메서드의 끝을 나타내는 닫는 중괄호와 만나면 스택 프레임 소멸
여는 중괄호로 스택 프레임이 만들어지고, 닫는 중괄호로 스택 프레임이 소멸됨
8. main() 메서드가 끝나면 JRE는 JVM을 종료, JRE 자체도 운영체제 상의 메모리에서 사라짐, T 메모리도 사라짐
main() 메서드는 프로그램의 시작점이자 끝
Debug를 이용해서 스택 영역 유추 가능!!
변수와 메모리: 변수! 너 어디 있니?
답은 "2.5 지역변수와 메모리"에서 확인
public class Start2 {
public static void main(String[] args) {
int i;
i = 10;
double d = 20.0;
}
}
1. JVM은 i 변수를 위한 공간을 main() 스택 프레임 안에 마련
args 위에 i 공간 있음. 값은 알 수 없는 값이 들어가 있음 -> 이전에 해당 공간의 메모리를 사용했던 다른 프로그램이 청소하지 않고 간 값을 그대로 가지고 있게 됨(쓰레기 값)
변수 i를 선언만 하고 초기화하지 않은 상태에서 i 변수를 사용하는 코드 만나면 javac(자바 컴파일러)는
"The local variable i may not have been initialized" 경고 나옴
2. i를 10으로 초기화 in 스택 프레임
3. d를 스택 프레임안에 쌓음 + 변수 초기화
변수를 선언하는 명령문 + 변수에 값을 할당하는 명령문
4. 닫는 중괄호로 main() 메서드 스택 프레임이 스택 영역에서 사라짐 -> 프로그램 종료
블록 구문과 메모리: 블록 스택 프레임
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~else에서도 여는 중괄호 -> 스택 프레임 시작
조건문은 무조건 true이므로 if(true)인 블록의 스택 프레임이 main() 스택 프레임 안에 중첩되어 생긴다
if 블록 종료하는 닫는 중괄만나면 스택 프레임은 스택 영역에서 사라짐.
if 블록 스택 프레임 안에 상주하던 변수의 저장 공간도 사라짐
주석 풀고 실행하면, 컴파일러가 오류 뱉으면서 컴파일 거부
m(or p) cannot be resolved to a variable
그 이후는, main() 메서드 스택 프레임 소멸, T 메모리 소멸, JVM 기동 중지, JRE가 사용했던 시스템 자원을 운영체제에 반납
지역 변수와 메모리: 스택 프레임에 갇혔어요!
변수는 static, stack, heap 영역 모두에 존재 가능. but 각기 다른 목적을 가짐
이름도 클래스 멤버 변수, 지역 변수, 객체 멤버 변수로 다름
- 클래스 멤버 변수: 스태틱 영역, 한번 스태틱 영역에 자리 잡으면 JVM이 종료될 때까지 고정된(static) 상태로 자리를 지킴
- 지역변수: 스택 영역, 특히 스택 프레임 안에서 일생을 보냄 -> 스택 프레임이 사라지면 함께 사라짐
- 객체 멤버 변수: 힙 영역, 객체와 함께 가비지 컬렉터라고 하는 힙 메모리 회수기에 의해 일생을 마침
메모리 상에 존재하면 접근 가능함. But!
외부 스택 프레임에서 내부 스택 프레임의 변수에 접근하는 것은 불가능하나 그 역은 가능하다
스택 메모리 내의 스택 프레임 안의 변수: 지역 변수
그 지역(스택 프레임)에서만 사용할 수 있고, 외부에서는 사용할 수 없기 때문
그 지역이 사라지면 지역 변수도 메모리에서 사라짐
메서드 호출과 메모리: 메서드 스택 프레임 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;
}
}
메서드 호출이 일어나면, 호출되는 메서드의 스택 프레임이 T 메모리 스택 영역에 새로 생성됨
반환 값을 저장할 변수 공간이 맨 아래,
그 다음 인자를 저장할 변수 공간,
마지막으로 메서드의 지역 변수
main() 메서드가 가진 변수 k, square() 메서드가 가진 변수 k
-> 이름만 같고, 실제로는 서로 별도의 변수 공간
=> Call By Value(값에 의한 호출)
square() 메서드 안에 k 변수에 무슨 짓을 해도 main() 메서드 안의 k 변수는 영향이 없음
main()메서드의 어딘가에서 square() 메서드 내의 지역 변수 result에 직접 접근 가능? X
반대로 square()메서드의 어딘가에서 main() 메서드의 지역 변수 m에 직접 접근 가능? X
메서드를 블랙박스화 한다 = 입력 값들(인자 리스트)와 반환값에 의해서만 메서드 사이의 값이 전달될 뿐, 서로 내부의 지역 변수를 볼 수 없음
스택 프레임이 만들어지지 않았거나, 사라졌기 때문에!
그럼 스택 프레임이 존재하는 동안에 참조할 수 있음?? NO
1. 메서드는 서로의 고유 공간
2. 포인터 문제; square() 메서드에서 main() 메서드의 내부 지역변수 m에 접근하려면 m의 정확한 위치 알아야함
-> m 변수의 메모리 위치, 즉 포인터라고 읽고 메모리 주소 값이라 이해해야하는 그 값을 알아야 함!
-> 자바는 포인터가 없음
3. 실전에서 메서드는 다양한 곳으로부터 호출됨 ex. A 라는 메서드에서도, B, C 메서드에서도 각각 square() 메서드 호출
A,B,C 내부의 지역 변수에 접근하려면 결국 그 지역 변수의 메모리 주소값인 포인터를 이용해 접근해야 함
-> 메서드를 호출하면서 만들어지는 스택 구조는 항시 변화함, 정확한 위치 알려면 포인터 필요!!
A() -> B() -> C() 처럼 스택 프레임이 만들어질 수도 or A() -> C() 또는 F() -> D()->C() 처럼 구성될 수도 있음
메서드 호출할 때 마다 해당 메서드의 스택 프레임 생김.
이전에 만들어진 square() 메서드 스택 프레임 내 지역 변수인 result는 다시 만들어진 스택 프레임의 result 변수와는 완전 별개
메서드를 호출하면서 인자로 전달되는 것은 변수 자체가 아니라 변수가 저장한 값만을 복제해서 전달
-> Call By Value
전역변수(공유 변수)는 가급적 쓰지 않는게 좋다.
전역 변수와 메모리: 전역 변수 쓰지 말라니까요!
두 메서드 사이에 값을 전달하는 방법
1. 메서드 호출할 때, 메서드 인자 이용
2. 메소드 종료할 때, 반환값 넘겨주기
NEW 3. 전역 변수 이용
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 키워드 -> 스태틱 영역에 변수 공간이 할당됨
Start5 클래스가 T 메모리 스태틱 영역에 배치될 때, 그 안에 share 변수가 클래스의 멤버로 공간을 만들어 저장됨
메서드 밖에서 선언된 변수 share는 메서드들 사이에서 공유해서 사용할 수 있는 전역 변수
다시 한번,
지역 변수: 스택 프레임에 종속적인 지역 변수
전역 변수: 스택 프레임에 독립적인 전역 변수
코드 어느 곳에서나 접근할 수 있어서 전역 변수라 하고,
여러 메서드들이 공유해서 사용한다고해서 공유 변수라고도 함.
프로젝트 규모가 커지면 T 메모리로 추적하지 않는 이상, 전역 변수에 저장돼 있는 값을 파악하기 쉽지 않음
-> 전역 변수 사용을 자제하자
-> 읽기 전용으로 값을 공유해서 전역 상수로 쓰는 건 추천 ex. Math.PI
멀티 스레드 / 멀티 프로세스의 이해
멀티 스레드(Multi Thread)의 T 메모리 모델: 스택 영역을 스레드 개수만큼 분할해서 씀
하나의 스레드에서 다른 스레드의 스택 영역에는 접근 불가능, 스태틱, 힙 영역은 공유해서 사용
멀티 프로세스 대비 메모리를 적게 사용할 수 있음
멀티 프로세스(Multi Process): 다수의 데이터 저장 영역, 즉 다수의 T 메모리를 갖는 구조
멀티 프로세스는 각 프로세스마다 각자의 고유 공간이 할당되므로 서로 참조할 수 없음
memory safty한 구조지만 그만큼 메모리 사용량이 크다
서블릿(Servlet)은 요청당 프로세스(CGI)가 아닌 요청당 스레드(Servlet) 생성
멀티 스레드에서 전역 변수 사용의 문제점
ex) 2개 스레드
스레드1이 공유 영역(스태틱, 힙)에 있는 전역 변수 A에 10 할당
CPU 사용권이 스레드2로 넘어감; 스레드2가 전역 변수 A에 20 할당
다시 CPU 사용권이 스레드1
A값 출력한다면?? -> 20 나옴
=> 스레드 안정성이 깨짐
그럼 어떻게 해야할까? 락(lock) 걸기
-> 멀티 스레드 장점은 버린 것과 같음 WHY??
-> 락의 단점은 다른 포스트 참조(https://hyeyoo.com/79) 추후 공부해서 정리해보자!!
'SW Books > 스프링 입문을 위한 자바 객체지향의 원리와 이해' 카테고리의 다른 글
[자바 객체지향 원리] 6장 - 스프링이 사랑한 디자인 패턴 (1) | 2023.08.31 |
---|---|
[자바 객체지향 원리] 5장 - 객체 지향 설계 원칙 - SOLID (0) | 2023.08.28 |
[자바 객체지향 원리] 4장 - 자바가 확장한 객체 지향 (0) | 2023.08.28 |
[자바 객체지향 원리] 3장 - 자바와 객체 지향 (0) | 2023.08.10 |
[자바 객체지향 원리] 1장 - 사람을 사랑한 기술 (1) | 2023.08.07 |