일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
- 인텔리제이오류
- 스프링시큐리티
- RDBMS
- JPA
- spring
- HTTP상태코드
- 자바
- server
- 스프링
- 시큐리티
- 스프링RESTAPI
- restAPI
- 상태코드
- MariaDB
- HTTP
- application.yml
- JWT
- 서버
- Static
- 스웨거
- API
- 오버라이딩
- 의존성주입
- SQL
- Java
- 어노테이션
- Swagger
- SpringSecurity
- 쿼리
- 스프링오류
- Today
- Total
취뽀몽
[JAVA] 자바와 절차적 / 구조적 프로그래밍 본문
1. 자바 프로그램의 개발과 구동
JVM, 즉 자바 가상 기계의 존재와 역할을 아는 것이 자바 개발 환경을 이해하는 데 필수적이다.
현실 세계에서 컴퓨터를 구동하기 위해서는 물리적 컴퓨터인 하드웨어와 운영체제, 그리고 그 위에서 구동될 소프트웨어가 필요하다.
거기에 더해 소프트웨어를 개발할 수 있는 개발 도구가 필요하다.
프로그램은 개발자가 개발 도구를 이용해 개발하고 운영체제를 통해 물리적 컴퓨터인 하드웨어 상에서 구동된다.
자바 개발 도구인 JDK를 이용해 개발된 프로그램은 JRE에 의해 가상의 컴퓨터인 JVM 상에서 구동된다.
- JDK(Java Development Kit) : 자바 개발 도구
- JRE(Java Runtime Environment) : 자바 실행 환경
- JVM(Java Virtual Machine) : 자바 가상 기계
2. 존재하는 절차적/구조적 프로그래밍의 유산
절차적 프로그래밍을 한마디로 표현하자면 goto를 쓰지 말자는 것이다.
goto를 사용하게 되면프로그램의 실행 순서가 인간이 이해하기에 너무 복잡해질 가능성이 있기 때문이다.
프로그램의 실행 순서를 이리저리 이동할 수 있게 되기 때문에 소스를 이리저리 따라가며 프로그램을 이해해야 한다.
구조적 프로그래밍이란 함수를 쓰라는 것이다. 함수를 쓰면 좋은 이유는 다음과 같다.
1) 중복 코드를 한 곳에 모아 관리가 가능하다.
2) 논리를 함수 단위로 분리해서 이해하기 쉬운 코드를 작성할 수 있다.
여기에 더해 구조적 프로그램의 지침 중에는 공유 사용 시 문제가 발생하기 쉬운 전역 변수보다는 지역 변수를 쓰라는 것도 있다.
함수와 메소드는 무엇이 다를까?
전혀 다르지 않다.
굳이 차이점을 뽑자면 함수는 클래스나 객체와 아무 관계가 없지만 메소드는 반드시 클래스 정의 안에 존재해야 한다.
3. main() 메소드 : 메소드 스택 프레임
main() 메소드는 프로그래밍 실행되는 시작점이다. main() 메소드가 실행될 때, 특히 T 메모리의 변화에 대해 살펴보자.
public class Start {
public static void main(String[] args) {
System.out.println("Hello OOP");
}
}
1) JRE은 먼저프로그램 안에 main() 메소드가 있는지 확인한다. JRE은 Start 클래스에서 main() 메소드를 발견할 수 있다.
2) main()메소드가 확인되면 JRE은 프로그램 실행을 위해 가상 기계인 JVM에 전원을 넣어 부팅한다.
3) 부팅된 JVM은 목적 파일을 받아 그 목적 파일을 실행한다. JVM이 맨 먼저 하는 일은 전처리이다. java.lang 패키지를 T 메모리의 스태틱 영역에 가져다 놓는다.
4) Main 메소드가 실행되기 위해 스택 프레임이 스택 영역에 할당된다. 조금 더 정확히는 여는 중괄호를 만날 때마다 스택 프레임이 하나씩 생긴다.
5) 그 후 메소드의 인자 args를 저장할 변수 공간을 스택 프레임의 맨 밑에 확보해야 한다. 즉, 메소드 인자의 변수 공간을 할당하는 것이다.
6) T 메모리를 구성하고 나면 main() 메소드 안의 첫 명령문을 실행하게 된다.
7)구문이 실행되면 T 메모리에는 변화가 없다. main() 메소드가 끝나면 JRE는 JVM을 종료하고 JRE 자체도 운영체제 상의 메모리에서 사라진다. 그럼 T 메모리도 사라지게 되는 것이다.
main() 메소드가 실행되기 전 JVM에서 수행하는 전처리 작업들은 다음과 같다.
- java.lang 패키지를 T 메모리의 스태틱 영역에 배치한다
- Import 된 패키지를 T 메모리의 스태틱 영역에 배치한다
- 프로그램 상의 모든 클래스를 T 메모리의 스태틱 영역에 배치한다.
4. 변수와 메모리
public class Start2{
public static void main(String[] args) {
int i;
i = 10;
double d = 20.0;
}
}
1) int i
메모리에 4바이트 크기의 정수 저장 공간을 마련하라는 것이다. 이 공간은 main() 소드 스택 프레임 안에 밑에서부터 차곡차곡 변수 공간을 마련한다.
변수 i를 선언만 하고 초기화하지 않은 상태에서 i 변수를 사용하는 코드를 만나면 자바 컴파일러는 “The local variable I may not have been initialized” 경고를 보낸다.
지역 변수 i가 초기화되지 않았다는 메세지이다.
2) double d = 20.0
하나의 명령문이 아닌 두 개의 명령문이다. 변수를 선언하는 명령문과 변수에 값을 할당하는 명령문 두개가 한 줄에 있는 것이다.
닫는 중괄호로 main() 메소드 스택 프레임이 스택 영역에서 사라지며 프로그램이 종료된다.
5. 블록 구문과 메모리 : 블록 스택
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는 조건에 따라 분기를 일으킬 것이다.
여는 중괄호를 만나면 스택 프레임이 시작되는데, 여기서 만들어지는 스택 프레임은 메소드 스택 프레임이 아니라 if문, 그것도 참인 블록의 스택 프레임이다.
main() 메소드의 스택 프레임 안에 if 문의 블록 스택 프레임이 중첩되어 생성된다.
m = k+5 구문은 if 스택 프레임 안의 변수 m에 값을 할당한다. 이때 if 스택 프레임 밖에 있으면서 main() 메소드 스택 프레임 안에 있는 k 변수를 참여시킨다.
if 블록 중 참일 때의 블록을 종료하는 닫는 중괄호를 만나면 if 블록 스택 프레임은 스택 영역에서 사라진다.
이때 if 블록 스택 프레임 안에 상주하던 변수의 저장 공간도 사라진다.
닫는 기호인 중괄호를 사용하면 T 메모리 소멸, JVM 가동 중지, JRE가 사용했던 시스템 자원을 운영체제에 반납하게 된다.
6. 지역 변수와 메모리
변수는 스태틱 영역, 스택 영역, 힙 영역 세 군데의 메모리에 존재한다.
하지만 세 군데 각각에 있는 변수는 각기 다른 목적을 가진다. 또한 각각의 이름도 지역 변수, 클래스 멤버 변수, 객체 멤버 변수로 다르다.
1) 지역 변수 : 스택 영역에 존재한다. 그것도 스택 프레임 안에서 존재한다. 따라서 스택 프레임이 사라지면 함께 사라진다.
2) 클래스 멤버 변수 : 스태틱 영역에 존재한다. 스태틱 영역에 한 번 자리잡으면 JVM이 종료될 때까지 고정된(static) 상태로 유지된다.
3) 객체 멤버 변수 : 힙에서 존재한다. 객체 멤버 변수들은 객체와 함께 가비지 컬렉터라고 하는 힙 메모리 회수기에 의해 종료된다.
외부 스택 프래임에서 내부 스택 프레임의 변수에 접근하는 것은 불가능하나 그 역은 가능하다.
그래서 스택 메모리 내의 스택 프레임 안의 변수를 지역 변수라고 한다. 스택 프레임에서만 사용할 수 있고 외부에서는 사용할 수 없기 때문이다.
또한 그 지역이 사라지면 지역 변수도 메모리에서 함께 사라진다.
7. 메소드 호출과 메모리 : 메소드 스택 프레임 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;
}
}
square라는 메소드 호출이 일어나면, 무조건 호출되는 메소드의 스택 프레임이 T 메모리 스택 영역에 새로 생성된다.
main()메소드가 가진 변수 k와 square() 메소드가 가진 변수 k가 이름만 같지 실제로는 서로 별도의 변수 공간이다.
이것을 Call By Value(값에 의한 호출)이라고 한다. 메소드를 호출하면서 인자로 전달되는 것은 변수 자체가 아니라 변수가 저장한 값만을 복제해서 전달하는 것이다.
그래서 square() 메소드 안의 k 변수는 main()메소드 안의 k 변수에 영향을 끼치지 않는다.
square() 메소드의 끝을 알리는 닫는 중괄호를 만나면 square() 메소드 스택 프레임은 스택에서 사라진다.
하지만 반환값이 있으니 그 값을 돌려주면서 스택에서 사라진다.
1) main() 메소드의 어디에선가 square() 메소드 내의 지역 변수 result에 직접 접근 가능한가? 또는 square() 메소드의 지역 변수 m에 직접 접근 가능한가?
답은 접근할 수 없다 이다.
입력값들과 반환값에 의해서만 메소드 사이에서 값이 전달될 뿐 서로 내부의 지역변수를 볼 수 없다는 것을 메소드를 블랙박스화 한다고 한다.
square() 메소드 내의 실행 명령문에서는 T 메모리 안에 존재하는 main 메소드의 지역 변수를 참조할 수 없다. 이유는 다음과 같다.
- 메소드는 고유 공간인데, 서로 침범하면 문제가 발생할 수 있다.
- 포인터 문제때문이다.
- 자바는 포인터를 사용할 수 없으므로 결국 언어 스펙상으로도 서로의 변수를 참조하는 것은 불가능하다.
- 구문이 실행되면 T 메모리에는 변화가 없다. main() 메소드가 끝나면 JRE는 JVM을 종료하고 JRE 자체도 운영체제 상의 메모리에서 사라진다. 그럼 T 메모리도 사라지게 되는 것이다.
메소드 사이에 값을 전달하거나 반환하는 방법은 메소드의 인자와 반환값으로만 가능하다.
물론 전역 변수(공유 변수)도 있지만 가급적 사용하지 않는 것이 좋다.
8. 전역 변수와 메모리
위에서 말했던 것처럼 전역 변수를 사용하는 방법이 있다.
전역 변수는 코드 어느 곳에서나 접근할 수 있다고 해서 전역 변수라고 하며, 여러 메소드들이 공유해서 사용한다고 해서 공유 변수라고도 한다.
전역 변수는 프로그램 규모에 따라 코드가 커지면서 여러 메소드에서 전역 변수의 값을 변경하기 시작하면 T 메모리로 추적하지 않는 이상 전역 변수에 저장되어 있는 값을 파악하기 쉽지 않다.
그렇기 때문에 전역 변수는 피할 수 있다면 피해야 한다. 다만 읽기 전용으로 값을 공유하는 경우에는 사용해도 된다.
9. 멀티 스레드 / 멀티 프로세스의 이해
1) 멀티 스레드
T 메모리 모델은 스택 영역을 스레드 개수만큼 분할해서 쓰는 것이다. 하나의 T 메모리만 사용하는데 스택 영역만 분할해서 사용하는 구조다.
T 메모리 안에서 스택 영역만 분할한 것이기 때문에 하나의 스레드에서 다른 스레드의 스택 영역에는 접근할 수 없지만 스태틱 영역과 힙 영역은 공유해서 사용한다. 따라서 멀티 프로세스 대비 메모리를 적게 사용할 수 있는 구조다.
2) 멀티 프로세스
다수의 데이터 저장 영역, 즉 다수의 T 메모리를 갖는 구조다. 각 프로세스마다 각자의 T 메모리가 있고 고유한 공간이므로 서로 참조할 수 없다.
하나의 프로세스가 다른 프로세스의 T 메모리 영역을 절대 침범할 수 없는 메모리 안전한 구조이지만 메모리 사용량이 그만큼 크다.
public class Start6 extends Thread{
static int share;
public static void main(String[] args) {
Start6 a1 = new Start6();
Start6 a2 = new Start6();
a1.start(); // 스레드 실행 => run() 실행
a2.start(); // 스레드 실행 => run() 실행
}
public void run(){
for(int count = 0; count < 10; count++){
System.out.println(share++);
try {
sleep(1000);
}
catch (InterruptedException e){
}
}
}
}
첫 번째 스레드가 share 변수를 1 증가시키기 전에, 두 번째 스레드가 share 변수를 변경하여 첫 번째 스레드의 작업 결과를 덮어쓰는 경우가 발생할 수 있다.
스레드 안전성 문제 참고 블로그 : https://developer-ellen.tistory.com/205
10. 정리
- 스태틱 : 클래스의 놀이터
- 스택 : 메소드의 놀이터
- 힙 : 객체의 놀이터
'java' 카테고리의 다른 글
[JAVA] 자바가 확장한 객체 지향 (0) | 2023.03.19 |
---|---|
[JAVA] 자바와 객체 지향 (0) | 2023.03.16 |
[Java] 오버로딩과 오버라이딩(Overloading And Overriding) (0) | 2023.03.06 |
[Java] 점층적 생성자 패턴(Telescoping Constructor Pattern), 자바 빈즈 패턴(JavaBeans Pattern), 빌더 패턴(Builder Pattern) (0) | 2023.03.03 |
[Java] 정적 팩토리 메소드(static factory method) (0) | 2023.02.28 |