우리는 어떤것을 배울때 순서를 잘 정해야 한다.
흐름을 알지 못하고 디테일을 파기 시작하면 어느순간 길을 잃게 되고,
흐름을 잡았음에도 깊이 파고들지 못하면 겉햝기로 끝나게 된다.
그래서 java를 어떻게 공부해야 할까 고민을 하며, 책을 참고해 순서를 정리해봤다.
이전글에 적힌 내용을 포함한 순서이다.
참고 : 스프링 입문을 위한 자바 객체지향의 원리와 이해(개구리책)
이후의 스프링 삼각형(IoC/DI , AOP , PSA , POJO)는 다음 글에 쓰도록 하겠다.
위와 같은 순서를 선택한 이유는 다음과 같다.
그럼, 드디어 Java가 무엇인지 공부해보도록 하자.
이 글은 독학으로 쓰여진 글이라 틀린 점이 있을수 있기에 최대한 공신력 있는 사이트를 사용하기 위해 노력할 것이다.

레퍼런스
https://www.geeksforgeeks.org/jvm-works-jvm-architecture/
간단하게 정리하자면 다음과 같다.
즉, 자바는 JVM을 통해서 실행이 되며, 실행을 돕기 위한 JRE, 자바 개발을 위한 JDK와 연관되어 있다.

JVM의 아키텍쳐 이다.
JVM의 실행 과정에 따라서 한번 알아보자.
Class Loader Subsystem
클래스 로더는 3가지 역할을 한다.
1. 로딩
클래스 로더는 .class 파일을 읽고, 그에 해당하는 바이너리 데이터를 만들어 메서드 영역에 저장한다.
(이때 generate 라는 단어가 사용되었는데, .class 파일은 이미 바이너리 데이터이다. java코드를 JVM에서 바이너리 코드로 바꾼다고 오해하지 말자.)
설명에 덧붙여서, .class 파일(binary) 코드를 읽고, 아래의 데이터를 포함한 메타데이터를 메서드 영역에 저장한다.
그 데이터의 내용은 다음과 같다.
로딩 후 java.lang 패키지에 미리 정의된 Class 타입의 객체를 생성하여 힙 메모리에 저장한다.
이 Class 객체를 통해 클래스 수준의 정보를 얻을 수 있다.
예를 들어 클래스 이름, 부모 클래스 이름, 메서드 및 변수 정보 등이 포함된다.

일반적으로 3가지 클래스 로더가 있다.
BootStrap ClassLoader : JAVA_HOME/jre/lib 디렉토리에 있는 핵심 자바 API 클래스를 로드한다.
Extension ClassLoader : JAVA_HOME/jre/lib/ext 또는 java.ext.dirs 시스템 속성에 의해 지정된 다른 디렉토리에 있는 클래스를 로드한다.
System/Application ClassLoader : App Class 경로에서 Class를 로드한다. java.class.path에 매핑된 환경변수를 사용한다.
위 클래스 로더는 위가 부모이고, 아래가 자식이다.(Bootstrap -> Extension -> System/Application)
JVM은 클래스를 로드할때 위임-계층 원칙을 따른다.
2. 링킹
검증, 준비, 해석(선택) 단계를 수행한다.
3. 초기화
모든 정적 변수에 코드에서 정의된 값과 정적 블록(있을때만)이 할당된다. 클래스 위에서 아래, 부모에서 자식순으로 실행된다.
JVM Memory

메서드 영역 : 클래스 이름, 직계 부모 클래스 이름(immediate), 메서드 및 변수 정보 등 모든 클래스 수준의 정보와 정적 변수가 저장된다. JVM당 하나의 메서드 영역이 있고, 공유 자원이다.
힙 영역 : 모든 객체의 정보가 저장된다. JVM당 하나의 힙 영역이 있고 공유 자원이다.
스택 영역 : JVM은 각 스레드마다 하나의 런타임 스택을 생성하여 저장한다. 스레드 종료시 제거되며, 공유자원이 아니다.
PC 레지스터 : 스레드의 현재 실행중인 명령어의 주소를 저장한다. 각 스레드 별로 PC 레지스터를 가진다.
네이티브 메서드 스택 : 네이티브 메서드 정보를 저장한다.
Execution Engine
.Class 코드를 실행한다. 바이트 코드를 한줄씩 읽고, 메모리 영역의 데이터와 정보를 사용하여 실행한다.
3가지로 분류 가능하다.
인터프리터 : 바이트 코드를 한줄씩 읽고 실행한다. 여러번 호출되었을때 매번 재해석 해야한다.
JIT 컴파일러 : 인터프리터의 효율을 높히는데 사용된다. 전체 바이트 코드를 컴파일하고 이를 네이티브 코드로 변경하여 인터프리터가 반복 호출된 메서드가 재해석 되지 않고도 실행시킬 수 있게 도와준다. 이로 인해 효율성이 향상된다.
가비지 컬렉터 : 우리가 잘 아는 가비지 컬렉터이다. 메모리 단편화를 방지하고 최적화 한다.
Java Native Interface(JNI)
JNI는 네이티브 메서드 라이브러리와 상호 작용하며 실에 필요한 네이티브 라이브러리(C,C++)를 제공한다.
이를 통해서 JVM이 C/C++ 라이브러리를 호출하고, C/C++ 라이브러리에서 호출될 수 있게 한다.
Native Method Library
네이티브 라이브러리(C,C++)의 모음이며 Execution Engine에 필요하다.
JRE , JDK에 대해서는 깊게 알아보진 않을 것이다.
JRE는 말 그대로 런타임 환경을 조성해 주는 것이고
JDK도 말 그대로 자바 개발을 위한 kit이다.
약간 다른 점이 있다면
JRE는 자바를 실행하기 위한 최소한의 라이브러리만을 가져 자바 실행 환경을 조성하는데만 사용되고,
JDK는 자바를 개발하기 위한 많은 개발 도구를 포함하고 있다는 점이다.
JRE의 Libraries에는
JDK의 Tools에는
이 글의 목적은 자바의 실행 과정임으로, 런타임 환경과 jdk에 대해서는 여기까지 하겠다.
래퍼런스 : https://www.geeksforgeeks.org/introduction-to-java/

위에서 봤던 메모리 영역이다.
Method Area에는 정적변수, 클래스 데이터 등이 들어가고
Stack 영역에는 런타임 스택을
Heap 영역에는 객체의 정보가 저장된다.
코드 실행 영역에는 위를 제외한 PC레지스터, 네이티브 메서드 스택 등이 포함된다.
이제 프로그램 실행에 따른 메모리 구조를 살펴보자.

(Static은 Method Area에 포함된다.)
여기서 추가적으로 주의해야 할 점들이 있다.

멀티 스레드를 설명할때 항상 나오는 말이다.
Java에서도 똑같이 통용이 된다.

멀티프로세스는 말 그대로 여러개의 프로세스를 한번에 같이 돌리는 것이다.
컨텍스트 스위칭을 통해서 병렬적으로 돌린다.
안타깝게도 자바는 멀티프로세스를 지원하지 않지만, 멀티스레드는 지원한다.
다행히 관련 교육용 프로그램을 만져본 경험이 있어서 이해가 빨랐다.
객체 지향 프로그래밍은 컴퓨터 프로그램을 명령어의 목록으로 보는 시각에서 벗어나 여러 개의 독립된 단위, 즉 "객체"들의 모임으로 파악하고자 하는 것이다. - wiki
관련 내용은 레퍼런스에 상세하고 정확하게 나와있으니 참고 바란다.
레퍼런스1 : 스프링 입문을 위한 자바 객체지향의 원리와 이해
레퍼런스2 : https://ko.wikipedia.org/wiki/객체 (컴퓨터 과학)
(2번 링크는 주소를 그대로 복사하셔야 합니다.)
레퍼런스3 : CSAPP
각각에 대해서는 짧게 설명해 보겠다.
객체란?
=> 클래스에서 정의한 것을 토대로 메모리(실제 저장공간)에 할당된 것으로, 프로그램에서 사용되는 데이터 또는 식별자에 의해 참조되는 공간을 의미한다. -wiki
코드를 하나 살펴보자.
public class Gamer{
public String name;
public void say(){
System.out.println(name+"는 근본 프로게이머!");
}
}
public class Human{
public static void main(String[] args){
Gamer 페이커 = new Gamer();
페이커.name = "대상혁";
페이커.say();
Gamer 엠비션 = new Gamer();
엠비션.name = "강찬용;
엠비션.say();
}
}
위에서 사용했던 메모리를 다시 보자.

현재 상황은, JVM을 실행하고 main() 함수를 읽은 상황이다.
그리고 Gamer이라는 Class를 static에 넣었음을 알 수 있다.
왜 Gamer는 main 안에 생성자로 쓰였기 때문에 Static에 미리 넣는 것이다.
이후, 생성자를 실행해보자.

Heap 영역에 각각의 객체들이 추가될 것이다.
여기서 잘 생각을 해야 하는 것이, Stack은 단순히 스레드의 런타임 데이터를 저장하는것 뿐이다. 멀티 스레드를 하게 될 경우, 각각의 스레드들은 개인의 스택 프레임을 가지게 된다. 이때 발생하는 중복/순서의 개념은 여기서 설명하진 않을 것이다.
그러니까, 간단히 생각하면 된다.
복잡하게 생각할 필요가 없다.
Stack은 스레드의 런타임 데이터이고
Static은 스레드가 동작하기 위한 정적변수, Class 데이터 등이며
Heap은 스레드가 동작할때 발생하고, 동작하기위해 참고하는 '객체'들이다.
왜 그런가?에 대해서 말을 하자면 그렇게 만들었기 때문이다.
뜬금없겠지만 C언어를 살펴 보자.
C언어에서 동적 메모리 할당을 위해 Heap을 사용한다.(CSAPP 9장)
즉, 프로그램 실행 시 동적인 데이터가 얼마일지 가늠이 안되니 일단 실행은 하되, 들어오는 변수를 보고 MALLOC을 통해 Heap 영역을 조절하겠다는 것이다.
여기서, 말을 조금 다르게 해석해보자.
여기서 무엇을 말하고 싶은지 이해 가는가?
C나 Java나 둘다 특정 데이터를 Heap영역에 집어넣고,
한쪽은 동적인 데이터, 다른 한쪽은 객체라고 부르는 것이다.
다시 한번 사전적인 의미를 보자.

그러니 여기에서 객체란
가 객체가 될 것이고,
name은 각 객체의 인스턴스 변수가 될 것이다.
레퍼런스 : https://www.codestates.com/blog/content/객체-지향-프로그래밍-특징
레퍼런스2 : https://www.oracle.com/java/technologies/javase/seccodeguide.html
총 4개가 있고, 다음과 같다.
우리는 CS를 공부할때도 추상화라는 것을 많이 본다.

위 그림은 추상화 계층 이다. - wiki(추상화 계층)
컴퓨터는 저런 구조로 되어있습니다~ 라고 보여주는 것이다.
위 그림은 단순히 구조를 보여줄 뿐, 세부적인 내용은 보여주지 않는다.
추상화는 복잡한 자료, 모듈, 시스템 등으로부터 핵심적인 개념 또는 기능을 간추려 내는 것을 말한다. - wiki
OOP에서도 똑같은 의미로 추상화가 쓰인다.
여러 Class들에서 공통적으로 쓰이는 것들을 상위 클래스로 만들어 사용하는 것이다.
하위 클래스들은 공통부분을 가진 상위 클래스를 받아서 변형을 한 후 사용하는 것이다.
예를 들어보자.
그럼 이러한 공통부분들을 모아서 상위 클래스를 만들어보자.
위의 사례에서 보면 사람/판다/~~ 들의 공통적인 부분을 가지고
포유류 라는 추상화를 그려낸 것이다.
위의 내용과 연결되는 이야기며, 같은 이야기라고도 볼 수 있다.
새로운 포켓몬을 한마리 만들어야 한다고 상상해보자.
그리고 이 포켓몬을 포유류 로 만들고 싶다고 가정해 보자.

포유류를 그려라고 했는데, 위 처럼 입도 없고, 털도 없으며 냉혈 동물을 그려버리면 안된다는 것이다.
이처럼, 포유류의 속성들을 이어받아 새로운 Class를 만드는것이 상속이다.
당신은 포유류 포켓몬을 만들기 위해 적어도
포켓몬을 그려야 할 것이다.
하나 더 알아야 할 것이
추상화는 interface와 Class로 나타낼 수 있는데,
interface는 그대로 가져와야 하고
Class는 오버라이딩을 사용하여 변형 후 사용할 수 있다는 차이점이 있다.
당신은 포유류에 2종류 이상 되는 생물이 포함된 다는 것을 알고있다.
그리고 그 각각의 생물들은 공통적인 부분을 가지고 있다는 것을 안다.
당신이 코딩을 한다고 가정해 보자.
당신은 어떤 포유류가 '털이있는지, 온혈인지, 입으로 밥을 먹는지' 가 궁금할 것이다.
그래서 코딩을 할려고 하는 순간 고민에 빠지게 된다.
이러한 생각을 하겠지만, OOP에는 다형성이라는것을 지원한다.

위의 그림과 같이 특정한 class 별로 하나하나 함수를 실행시키게 된다면
종류(N)에 대하여 O(N)의 시간복잡도가 나오게 될 것이다.
다시 한번 생각해보면
다형성을 적용시킨 예시를 보면

위와 같이 같은 추상화를 상속받은 자식 class들을 포유류 라는 상위 Interface/Class로 매개변수 타입을 지정해 줄 수 있다는 것이다.
추가로, 각각의 class마다 예외처리를 많이 하는 것을
결합도가 높다고 한다.

캡슐화는 한줄로 설명할 수 있다.
지극히 당연한 말이다.
서버를 만들었는데 정보가 술술 세어나가면 신뢰할 수 있는가?
이 캡슐화를 이해하기 위해서는 2가지를 기억하면 된다.

첫번째는 접근 제어자 이다.
말 그대로 접근 제어자는 어디에서 접근 할 수 있는지 명시하는 것이다.
public class ~~~
접근 제한이 없는 Class입니다.
위 두 표현은 같은 표현이다.
public은 접근 제한이 없는 것
그리고 Class를 만들겠다고 선언하는 것이다.
그렇다면, 이 접근 제어자를 어떻게 써야 하는지가 주된 내용이다.
위에 있는 어린왕자의 상자를 기억하는가?
그 상자의 내용물은 작가조차 모른다.
그 뜻은, 무언가 일어나고 있다는 추상적인 내용을 알 뿐, 정확하게 무엇이 있는지는 모른다는 것이다.
만약 내가 상자에 맛있는 음식이 있으면 좋겠다고 생각해 보자.
작가조차 내가 알고있는 맛있는 음식 중 어떤것이겠거니 생각할 것이다.
하지만 정확하게 어떤 음식이, 얼마나, 온도는 몇도고 양념은 얼마나 했고
등등의 디테일한 정보는 절대 알 수 없다는 것이다.
이제 예시를 들어보자.

당신이 현금을 송금한다고 생각해 보자.
그런데 개발자가 너무나 개을러서 서버에 당신의 개인정보가 다 나온다고 가정해 보자.
모든 데이터가 private class 안에서 public 하게 오픈되어 있다는 것이다.
이때 해커가 들어와 데이터를 변조한다면...

이렇게 의도하지 않은 곳에 의도하지 않은 돈이 송금 될 수 있다는 것이다!
이 얼마나 무서운 일인가...

그래서 이렇게 보안 가이드에 하지마라고 적혀져 있다.
(출처 - seccodeguide)
레퍼런스 : https://ko.wikipedia.org/wiki/SOLID_(객체_지향_설계)
(주소 전체를 복사해서 검색해야됨)
드디어 마지막인 SOLID에 왔다.
SOLID는 개념적인 내용이라 최대한 간결하게 설명해 보겠다.
Single responsibility principle
하나의 클래스는 하나의 책임만 가져야 된다.
하나의 클래스가 여러 일을 하고 있다면, 기능을 분화해야 한다.
클래스 하나로 여러개 다 하지 말고 나누라는 뜻이다.
Open/closed principle
소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다.
위의 포유류 예시를 생각해보자.
판다, 사람, 기린 등등 여러가지 동물들을 추가하면 의존도가 높아지게 된다.
그럼으로 포유류 라는 상위 class를 둠으로써, 의존도를 줄이고 확장성을 높혀야 한다.
Liskov substitution principle
프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다. 계약에 의한 설계를 참고하라.
간단하게 말해서
Interface segregation principle
특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다.
첫번째 단일 책임 원칙과도 비슷하다.
하나의 인터페이스는 하나의 일만 하라는 것이다.
여러개 쓰면 복잡해져서 유지/보수하는것도 힘들어진다.
Dependency inversion principle
프로그래머는 추상화에 의존해야지, 구체화에 의존하면 안된다. 의존성 주입은 이 원칙을 따르는 방법 중 하나다.
구체적인 것에 의존성을 부여하지 마라는 것이다.
위의 포유류 예시를 다시한번 사용하면
판다, 사람, 등등 하위 객체에 의존성을 집어넣지 말고
포유류에 의존성을 넣어라는 것이다.
드디어... 드디어 끝이 났다...
여기까지 적는데 온갖 사이트를 뒤지면서 이게 무슨말인지, 맞는 말인지 이해하고 검증하는데 시간이 장난아니게 걸렸다.
이제 기본적인 java에 대한 이해는 끝났으니, Spring 프레임워크를 공부해 보자.