프로그래밍의 발전 및 Java와 절차적/구조적 프로그래밍
프로그래밍의 발전
기계어 : 초창기 기술, 0과 1
어셈블리어 : 어셈블러(인간의 언어)로 기계어 생성 - 그러나 기계(cpu)마다 서로 다른 어셈블리어
C : 하나의 소스 파일만 만들면 된다. - 기계별 컴파일러 필요
Java : 하나의 소스 파일, 하나의 컴파일러 - 각 기종 별로 JRE만 설치해주면 된다.
Java : Write Once Use Anywhere !
CBD(Componenet Based Development)
컴포넌트 기반 개발
애플리케이션을 의미 있는 단위로 구분하고 그 단위를 하나하나씩 부품으로 개발하여 완성하는 방법
SOA(Service Oriented Architecture)
서비스 지향 구조
개발자 입장에서의 개발이 아니라 실제 현식의 업무를 기준으로 개발
그저 용어일 뿐, 사상(개념)과 제품은 별개이다.
스프링 프레임워크란?
OOP 프레임워크
스프링 프레임워크를 배우기 위해 알아야 할 것
- OOP 개념
- Java 언어 문법
- Java가 OOP 개념을 구현한 방식
Java와 절차적 / 구조적 프로그래밍
Java 프로그램의 개발과 구동
- JVM(Java Virtual Machine) : 자바 가상 기계
- 하드웨어(물리적 컴퓨터), 가상의 컴퓨터
- JRE(Java Runtime Environment) : 자바 실행 환경
- 운영체제, JVM용 OS
- JDK(Java Development Kit) : 자바 개발 도구
- 소프트웨어 개발 도구
프로그램(소프트웨어)는 개발자가 개발 도구를 이용해 개발한다.
프로그램은 운영체제를 통해 물리적 컴퓨터인 하드웨어 상에서 구동한다.
Java 프로그램은 JDK를 이용해 개발한다.
프로그램은 JRE를 통해 가상의 컴퓨터인 JVM 상에서 구동된다.
배포되는 JDK, JRE, JVM은 편의를 위해 JDK가 JRE를 포함하고, JRE는 JVM을 포함하는 형태로 배포된다.
- JDK는 자바 개발 도구 모음으로, 자바 프로그램을 개발할 때 필요한 컴파일러, 라이브러리, 도구들이 포함되어 있다. 개발자가 소스코드를 작성하고 컴파일하는데 사용된다.
- JRE는 자바 애플리케이션이 실행되기 위한 환경이다. JRE는 JDK를 포함하며, 자바 애플리케이션이 필요로 하는 핵심 라이브러리와 기타 파일을 제공한다.
- JVM은 자바 바이트코드를 실제로 실행하는 가상 머신이다. 플랫폼에 독립적이며, 개발자가 작성한 자바 프로그램을 다른 플랫폼에서 변경 없이 실행할 수 있도록 해준다.
즉, JVM은 자바 애플리케이션으로부터 생성된 바이트 코드를 받아 해당 플랫폼에서 실행 가능한 기계어로 변환한다. WORA(Write Once, Run Anywhere) 원칙을 가능하게 한다.
JVM이 애플리케이션 실행과 관련되어 핵심적인 역할을 하며, JRE는 JVM을 포함하여 실행에 필요한 라이브러리와 기타 파일을 제공한다.
자바 개발자는 본인이 사용 중인 플랫폼에 설치된 JVM용으로 프로그램을 작성하고, 배포하면 각 플랫폼에 맞는 JVM이 중재자로서 각 플랫폼에서 프로그램을 구동하는데 아무 문제가 없게끔 만들어준다.
Java에 존재하는 절차적/구조적 프로그래밍의 유산
객체 지향 프로그래밍은 절차적/구조적 프로그램의 어깨를 딛고 있다.
절차적 프로그래밍과 구조적 프로그래밍
절차적(Procedural) 프로그래밍 : goto를 쓰지 말자. 이해하기에 너무 복잡하다.
구조적 프로그래밍 : 함수를 쓰자. 중복 코드를 한 곳에서 관리할 수 있고, 이해하기 쉽다. + 전역 변수 대신 공유 문제가 발생하지 않는 지역 변수를 쓰자.
순차적 프로그래밍은 위에서 아래로 순서대로 실행된다. goto문을 남발하여 흐름을 쫓기 어렵다.
절차적 프로그래밍은 순차적 프로그래밍을 보완시킨 것으로, 반복되는 작업을 함수와 프로시저로 묶어 사용한다.
구조적 프로그래밍은 절차적 프로그래밍에서 발전하여 제어 흐름을 단순화하는 것을 목적으로 한다. goto문 대신 세 가지 기본 제어 구조(순차, 선택, 반복)를 사용하여 프로그램의 흐름을 제어한다. 함수 사용을 권장한다.
함수 vs 메서드
절차적/구조적 프로그래밍의 함수와 객체 지향 프로그래밍의 메서드는 다르지 않다.
단지 객체 지향 언어에서 클래스 외부에 존재할 수 있는 것은 없으므로, 함수와 달리 메서드는 클래스 정의 안에 존재해야 한다는 차이 뿐이다.
참고로 절차적/구조적 프로그래밍의 필드와 객체 지향 프로그래밍에서 사용하는 속성(property)도 같은 말이다.
어떤 사람들은 객체 지향에서 정적 변수는 필드, 필드를 외부에 노출시키는 메서드는 속성(프로퍼티)이라고 하는 사람도 있다.
ex) get/set은 프로퍼티
메모리 사용 방식
T 형태의 메모리 구조이다.
- static : 클래스의 놀이터
- stack : 메서드의 놀이터
- heap : 객체들의 놀이터
code, data, heap, stack이지만 여기서 code는 다루지 않는다.
main() 실행 방식
프로그램 실행시 메모리에는 어떤 일이 일어날까?
public class Start {
public static void main(String[] args){
System.out.println("Hello OOP!");
}
}
- JRE는 프로그램 안에 main() 메서드가 있는지 확인한다. main() 메서드의 존재가 확인되면 JRE는 JVM에 전원을 넣어 부팅한다.
- JVM은 프로그램을 전처리하기 시작한다.
- java.lang 패키지를 메모리의 스태틱 영역에 배치
- import된 패키지를 메모리의 스태틱 영역에 배치
- 프로그램 상의 모든 클래스를 메모리의 스태틱 영역에 배치
- main() 메서드의 스택 프레임이 스택 영역에 할당
- 메서드의 인자 args를 저장할 변수 공간을 스택 프레임에 확보
- 모니터에 Hello OOP! 출력
- main() 메서드의 스택 프레임 소멸(인자도 함께 소멸)
- JRE는 JVM을 종료하며 JRE도 종료
{} 중괄호가 생길 때마다 스택 영역에 스택 프레임이 생긴다.
외부 스택 프레임에서 내부 스택 프레임의 변수에 접근하는 것은 불가능하나, 그 역은 가능하다. (ex) if문 )
메모리는 Code, Data, Heap, Stack으로 이루어지지만, 여기에서는 Code 부분은 다루지 않는다.
메모리와 변수
변수는 3개의 메모리 영역 - 스태틱, 스택, 힙 영역 모두에 있다.
- 스태틱 영역 - 클래스 멤버 변수 : JVM이 종료될 때까지 고정된 상태로 존재한다.
- 스택 영역 - 메서드 안의 지역 변수 : 스택 프레임이 사라지면 함께 사라진다.
- 힙 영역 - 객체 멤버 변수 : 가비지 컬렉터라고 하는 힙 메모리 회수기에 의해 사라진다.
public class Example {
// 스태틱 영역에 저장되는 클래스 멤버 변수
static int staticVariable = 10;
// 힙 영역에 저장되는 객체 멤버 변수
int instanceVariable1 = 3;
// 객체 멤버 변수
private int instanceVariable2;
public Example(int value) {
this.instanceVariable2 = value;
}
public static void main(String[] args) {
// 스택 영역에 저장되는 메서드 안의 지역 변수
int localVariable = 5;
// 객체 생성 (객체의 멤버 변수는 힙 영역에 저장됨)
MemoryAndVariables object = new MemoryAndVariables(20);
System.out.println("스태틱 변수: " + staticVariable);
System.out.println("지역 변수: " + localVariable);
System.out.println("객체 멤버 변수 1: " + object.instanceVariable1);
System.out.println("객체 멤버 변수 2: " + object.instanceVariable2);
}
}
클래스 멤버 변수 (정적 변수)
- static 키워드를 사용한 변수
- 클래스의 모든 인스턴스에 의해 공유되며, 클래스 레벨에서 관리되어야하는 데이터를 저장한다.
ex) 모든 사진관이 공유하는 액자 크기
지역변수
- 메서드 내부에 존재하는 변수
- 메서드가 실행될 때 생성되고, 메서드 실행이 끝나면 자동으로 사라진다.
- 메서드 외부의 영향을 받지 않고 메서드 내부에서만 유효한 데이터를 관리한다.
객체 멤버 변수 (인스턴스 변수)
- 각 객체 인스턴스의 고유한 상태를 저장한다. 객체가 생성될 때마다 각각의 멤버 변수에 대한 메모리 공간이 할당되며, 객체마다 다른 값을 가질 수 있다.
- 객체 멤버 변수를 사용하려면, new 키워드로 객체의 인스턴스를 생성해야한다. (힙에 메모리 할당)
- instanceVariable1과 instanceVariable2가 new 키워드가 없이 객체 멤버 변수로 생성될 수 있는 이유는 이미 Example 클래스의 객체를 생성할 때 new 키워드를 사용했기 때문이다. 클래스 객체 생성시 객체 멤버 변수들을 초기화한다.
- 각 객체가 고유한 상태를 유지할 수 있어 데이터 캡슐화와 객체의 독립성이 유지된다.
- 해당 객체가 더 이상 사용되지 않을 때(모든 참조가 사라질 때) 가비지 컬렉터에 의해 메모리에서 제거된다.
메서드를 블랙 박스화한다.
public class Start2{
public static void main(String[] args){
int k=5;
int m = square(k);
}
private static int square(int k){
int result = 25;
return result;
}
}
- main()의 스택 프레임과 square()의 스택 프레임은 서로 접근이 불가능하다.
- main() 메서드에서 square() 메서드 내의 지역 변수를 접근할 수 없다.
- square() 메서드에서 main() 메서드의 지역 변수를 접근할 수 없다.
- 입력 값들(인자 리스트)와 반환값에 의해서만 메서드 사이에 값이 전달될 뿐 서로 내부의 지역 변수를 볼 수 없다.
- 서로 참조할 수 없게 막아놓은 이유
- 메서드는 서로의 고유 공간이므로, 침범하게 두어서는 안된다.
- java에 포인터가 필요하게 된다. square 메서드에서 main 메서드의 지역변수 m에 접근하기 위해서는 m의 위치, 즉 포인터가 필요하다.
- Call By Value : 메서드를 호출하면서 인자로 전달되는 것은 변수 자체가 아니라 변수가 저장한 값을 복제하여 전달한다.
전역 변수 사용을 지양하자.
전역 변수를 이용해서도 메서드 사이에 값을 공유할 수 있다.
전역 변수는 코드 어디에서나 접근할 수 있으며, 여러 메서드들이 공유해서 사용한다.
프로젝트 규모에 따라 코드가 커지면서 여러 메서드에서 전역 변수의 값을 변경하기 시작하면, 메모리로 추적하지 않는 이상 전역 변수에 저장되어 있는 값을 파악하기 어렵다.
또한, 멀티 스레드 프로그램에서 전역 변수는 문제를 일으킨다.
전역 상수 (read-only)
전역 변수를 읽기 전용으로 값을 공유해서 전역 상수로 사용하는 것은 추천한다. ex) PI
public class MathConstants{
public static final double PI = 3.14159;
}
public class Calculator {
public static double calculateArea(double radius){
return MathConstatns.PI * radius * radius;
}
}
멀티 스레드 / 멀티 프로세스
멀티 스레드의 메모리
스택 영역을 스레드 개수만큼 분할해서 사용한다.
- 같은 프로세스에 있는 스레드들은 Code, Data, Heap 영역을 공유한다.
- 스택 영역만 분할한 것이므로 메모리 사용량이 적다.
서블릿 컨테이너(Apache Tomcat, Jetty등)과 같은 현대의 웹 서버는 대부분 요청당 프로세스가 아닌 요청당 스레드를 사용하여 클라이언트의 요청을 처리한다.
여러개의 스레드가 동시에 실행될 수 있으므로 확장성이 좋고 프로세스보다 생성 및 종료가 빠르기 때문에 훨씬 효율적으로 자원을 사용할 수 있다.
전역 변수 사용의 위험성
멀티 스레드에서 쓰기 가능한 전역 변수를 사용할 경우 스레드 안전성이 깨진다. 전역 변수를 여러 스레드에서 접근하므로 의도치 않은 결과를 발생시킨다.
문제를 해결하려면, 해당 전역 변수에 대한 접근을 동기화해야한다.
동기화를 구현하는 방법으로는 락이 있다.
락을 사용하면 특정 시점에 하나의 스레드만이 전역 변수에 접근하여 수정할 수 있다.
락을 걸어 문제를 해결할 수 있지만, 락을 거는 순간 다른 스레드들은 락이 해제될 때까지 기다려야 하므로 병렬 처리의 이점이 감소한다. 즉, 멀티 스레드의 장점을 버린 것과 같다.
멀티 프로세스의 메모리
다수의 메모리를 갖는다.
- 한 프로세스가 다른 프로세스의 메모리 영역을 침범할 수 없다. (메모리 안전 구조)
- 메모리 사용량은 크다.
- 멀티 프로세스 모델은 멀티 코어나 멀티 CPU 환경에서 각 프로세스를 서로 다른 코어나 CPU에 할당할 수 있어 리소스를 효과적으로 활용할 수 있다.
병렬 처리 관점
멀티 스레드 병렬 처리 : CPU 사용이 많은 계산 집중적 작업(자원 공유 필요, 낮은 컨텍스트 스위칭 비용)이면 멀티 스레드 방식이 효율적이다.
멀티 프로세스 병렬 처리 : 안정성과 프로세스 격리가 중요하거나, 각 작업이 독립적이며 많은 자원을 사용하는 경우 적합하다. 또한 현대의 시스템은 멀티 코어 프로세서를 사용하므로, 프로세스를 할당하여 성능을 향상할 수 있다.
Reference
https://m.yes24.com/Goods/Detail/17350624
+ 책 내용 기반으로 추가적으로 내용을 작성했는데 오개념이 있다면 지적 부탁드립니다~!!