▼ Why ?
이번에 복학하면서 백엔드에 관심이 생겼고 Spring이라는 프레임워크를 다루기 전에 Java에 대한 개념을 잡고자 동아리에서 진행하는 자바 스터디에 참여하게 되었다. 스터디의 목표는 Spring을 다루기 위한 기반을 잡기 위해, 강의나 책을 하나 골라 그것을 메인으로 정하고 이번 여름 방학까지 학습을 끝내는 것이다. "자바의 정석"이라는 책을 고르된 이유는 이 책은 굳이 몰라도 되는 부분까지 설명되어 있다고는 하지만, 이왕 자바 스터디까지 진행하는거, 다소 심화적인 내용까지 다뤄보는 것도 괜찮을 것 같아 이 책으로 선정하게 되었다. 스터디는 매주 학습 범위를 정하고 각자 블로그에 학습한 내용을 정리한 뒤, 자신이 맡은 파트를 스터디 시간에 설명해주며 복습하는 방식으로 진행해보려고 하고 공부한 과정에서 생긴 의문점에 대해서도 이야기해보는 시간을 가져볼 것이다.
🌱 24.04.04 - Java 스터디(멘토주도형) 진행
- 작년에 <자바의 정석>으로 공부하며 정리한 내용, 대학 강의 "시스템 프로그래밍"에서 배운 것, 그리고 인프런 강사 "김영한"님의 강의자료의 내용을 기반으로 멘토 주도형 스터디를 진행하게 되었다.
🌑 C언어를 왜 배울까?
실력있는 개발자가 되기 위해서 !
- 컴퓨터는 항상 인간이 설계해놓은 컴퓨터(시스템) 구조에 따라 절차대로 작동하기 떄문에, 컴퓨터의 내부 동작 과정을 이해할 필요가 있다 !
"다 알아서 처리해주겠지~" 하면서 개발하면 절대 실력있는 개발자가 될 수 없고 !
나중에 Java를 다루더라도 Java의 내부 동작(ex: JVM, Heap, Stack)을 이해하고 개발하는 사람과 모르고 개발하는 사람은 분명한 차이가 있다고 생각해요..!
- C언어는 기계어에 가장 가깝다고 할 수 있는 어셈블리어(Assembly Language)와 대응되는 언어이다.
➙ C언어도 사람이 좀 더 사용하기 쉬운 언어를 만들기 위한 과정에서 탄생한 것 !
- Java, Python 같은 언어들을 보통 "High-level language"라고 하고, 기계어(binary code)에 가까운 언어일수록 "Low-level language"라고 한다.
➙ 우리가 쓰는 대부분의 언어들도 결국 C언어를 기반으로 만들어진 것이기 때문에, C언어를 배우면 다른 언어를 좀 더 빠르게 습득할 수 있다고 하긴 하는데..
( 근데, 사실 이 부분은 C언어만 해당되는게 아니라 어떤 언어를 배워놓더라도 다른 언어를 배울 때 도움이 된다고 생각해요 ! )
💡 어셈블리어를 배우기 위해선 C언어부터 배워야 한다 !
컴퓨터 시스템 구조를 제대로 이해하기 위해선 C언어가 아니라 어셈블리어를 배워야 하지만, 어셈블리어를 바로 공부하자기엔 너무 어렵기 때문에 비교적 쉽고 어셈블리어와 직접 대응되는 C언어부터 공부하는 것이다.
💡 C vs. C++ ( Chat GPT 😤 )
- 기본 개념
: C는 절차 지향 프로그래밍 언어로, 프로그램을 함수의 집합으로 볼 수 있습니다.
반면, C++는 C에 객체 지향 프로그래밍 개념을 추가한 언어로, 데이터와 함수를 객체라는 단위로 묶어 관리할 수 있습니다.
즉, C++는 C의 모든 기능을 포함하면서도 클래스, 상속, 다형성, 캡슐화와 같은 객체 지향 프로그래밍의 특징을 제공합니다.
- 용도
: C언어는 시스템 프로그래밍, 임베디드 시스템 개발 등에 주로 사용됩니다.
이는 C가 하드웨어에 가까운 수준에서 효율적인 코드를 작성할 수 있게 해주기 때문입니다.
반면, C++는 게임 개발, 그래픽스 애플리케이션, 실시간 시스템 개발 등 더 복잡한 애플리케이션 개발에 적합합니다.
객체 지향 프로그래밍을 통해 코드의 재사용성과 유지 보수성이 향상되기 때문입니다.
- 메모리 관리
: C에서는 메모리 관리를 수동으로 해야 합니다.
malloc()과 free() 함수를 사용하여 동적 메모리 할당 및 해제를 직접 관리해야 합니다.
C++에서도 이러한 방식을 사용할 수 있지만, new와 delete 연산자를 통해 더 간편하게 메모리 관리를 할 수 있으며, 스마트 포인터 같은 자동 메모리 관리 기능을 제공합니다.
컴퓨터의 메모리 동작을 이해하자 !
- C언어를 배울 때 언어의 문법을 배우는 것도 중요하지만..!
➙ 개인적으로 메모리의 구조를 이해한다는 마음가짐으로 배워보면 많이 도움될 것 같아요 !
( Java를 공부할 때 객체지향의 개념을 이해하며 공부하라고 했던 것처럼 )
- C언어에서 변수를 통해서 메모리에 값이 어떻게 저장되는지를 생각해보자 !
- "변수"라는 건 메모리에 할당된 주소값을 갖고 있는 것을 의미하고, 그게 C언어에서 말하는 "포인터"이다 !
➙ 포인터(= 포인터 변수) = 메모리의 주소값을 저장하는 변수 - 주소 연산자 &: 해당 변수의 값이 저장된 위치의 주소값을 반환.
- 참조연산자 *: 포인터의 이름이나 주소 앞에 사용하여, 포인터에 가리키는 주소에 저장된 값을 반환.
- "변수"라는 건 메모리에 할당된 주소값을 갖고 있는 것을 의미하고, 그게 C언어에서 말하는 "포인터"이다 !
int num = 1234;
int* ptr_num = # // 0xFFFFFFFF (32bit의 최대 주소값 ➙ 약 4GB 크기)
"num"을 출력하면? "1234"
"*ptr_num"을 출력하면? "1234"
➙ "num"이 직접적인 변수 접근을 통한 것이고, "*ptr_num"은 포인터를 통한 간접 접근이라고 보면 된다 !
"ptr_num"을 출력하면? "0xFFFFFFFF"
💡 변수명이 곧 주소값(offset) ?
우리가 변수를 생성할 때 사람이 쉽게 읽을 수 있는 변수명을 지어 생성하게 되는데, 사실 컴파일러가 컴파일할 때 변수명(num)을 주소값(offset)으로 변환하는 과정이 숨어있는 것이다 !
💡Offset
: 해당하는 데이터가 들어있는 데이터 세그먼트(segment)의 시작 부분부터 레이블(label)까지의 거리(byte)를 의미한다.
➙ 예를 들어, 109호에 어떤 데이터들이 있는지 알고 싶어 1층이라는 세그먼트(segment)가 있으면, 1층이 시작되는 위치에서 109호가 얼마나 떨어져있는지를 나타내는 것이다 !
- C는 Java와 달리 메모리를 직접 관리해줘야 한다.
( Java는 메모리를 할당(new 연산자)하게 되면 "가비지 컬렉터(GC)"라는 것이 자동으로 관리해준다 ! )
int* arr;
arr = (int*)malloc(sizeof(int) * 4); // size 4 동적할당
arr[0] = 100;
arr[1] = 200;
arr[2] = 300;
arr[3] = 400;
for (int i = 0; i < 4; i++) {
printf("arr[%d] : %d\n", i, arr[i]);
}
free(arr); //동적할당 해제
💡 컴퓨터 시스템 정보에서 32 Bit (x86-32)가 의미하는게 뭘까 ?
"x86"은 Intel CPU 아키텍쳐 명칭이고, "32 Bit"는 CPU가 한 번에 처리할 수 있는 데이터 처리 단위를 의미한다 !
( 64 Bit: "x86-64(x64)", AMD: "AMD64" )
💡 "0xFFFFFFFF"가 뭐지 ?
"0x" ➙ 그 뒤에 오는 숫자가 16진수라는 것을 알리는 접두사(prefix)이다 !
"FFFFFFFF"
➙ 32-Bit(x86 프로세서) 운영체제로 작동하는 컴퓨터의 메모리가 가질 수 있는 최대 주소값으로 약 4GB 크기이다 !
➙ 4GB로는 데이터를 처리하는데 역부족이게 되면서, 64-Bit로 확장하게 된 것이다 !
( 그래서, 32-Bit 운영체제가 깔려있는 컴퓨터엔 RAM을 아무리 많이 꽂아도 4GB까지밖에 인식 못해요 ! )
약 4GB인 이유는 ?
➙ "F" 하나가 2^4개의 이진수 값(binary data)을 표현 가능하다 !
( 참고로 용량을 표현하는 최소 단위는 바이트(Byte)이다 )
➙ 따라서, 2^32 Byte(4,294,967,296 Bit) 범위의 주소를 저장할 수 있다.
( 2^10 Byte = 1 KB, 2^20 Byte = 1 MB, 2^30 Byte * (2^2) = 1 GB * 4 = 4 GByte )
💡 16진수를 사용하는 이유 ?
16진수는 이진수나 십진수 표현보다 값을 더 간결하게 나타내기 위해 사용한다.
🌒 자바 언어의 특징
1. 운영체제에 독립적이다.
- 크로스 플랫폼 언어
Java 이외에도 Python, JavaScript, C#(Windows에 중점을 뒀었는데 .NET Core가 도입된 이후로는 독립적), Ruby, Kotilin, Go 등 요즘 자주 쓰이는 "High-Level" 언어들은 대부분 운영체제에 독립적이라는 장점을 갖고 있다.
➙ "한 번 작성하면 어디에서나 실행 가능하다(Write Once, Run Anywhere)"는 철학을 지향하는 언어 !
💡 ".NET Core"가 뭘까 ?
.NET Core는 Windows, macOS, Linux를 지원하는 .NET 프레임워크의 "크로스 플랫폼" 버전이다 !
- 자바 응용 소프트웨어, 쉽게 말해서 자바로 만든 프로그램은 "JVM(Java Virtual Machine)"이라는 "자바 가상 머신"을 통해서 OS나 하드웨어와 통신하게 된다 !
➙ 쉽게 말하면, "JVM"은 자바로 만든 프로그램과 OS 혹은 하드웨어 사이에서 통역사 역할을 해주는 것 !
❗단, JVM 자체는 OS에 종속적이라 여러 OS에 설치할 수 있는 서로 다른 버전의 JVM이 제공되고 있다 !
💡 하나의 OS에 종속되어 개발된 프로그램은 어떤 문제가 ?
해당 프로그램을 다른 종류의 OS에 적용하기 위해선 매우 복잡한 작업이 요구된다 !
💡 운영체제에 독립적일 수 있는 이유는 ?
Java 코드가 JVM(Java Virtual Machine) 위에서 실행되는 바이트코드(Bytecode)로 컴파일되기 때문이다 !
( 이와 다르게, C로 작성된 코드는 직접 운영체제에서 실행되는 기계 코드로 컴파일되기 때문에, 운영체제가 바뀌면 새로 개발해야 한다..! )
2. 객체지향언어이다.
- Java는 상속, 캡슐화, 다형성과 같은 성질이 잘 드러나는 순수 객체지향언어이다 !
- 객체지향이 뭔지 너무 궁금하다면 ?
https://ukym-tistory.tistory.com/entry/GDSC-%EB%B6%81-%EC%8A%A4%ED%84%B0%EB%94%94-01%ED%98%91%EB%A0%A5%ED%95%98%EB%8A%94-%EA%B0%9D%EC%B2%B4%EB%93%A4%EC%9D%98-%EA%B3%B5%EB%8F%99%EC%B2%B4
[GDSC] 북 스터디 : 01_협력하는 객체들의 공동체
▼ What ? 이 "협력하는 객체들의 공동체" 라는 챕터에선 실세계를 모방하여 '객체 지향' 이라는 세계를 이해하도록 하는 내용들을 담고 있다. ▼ Summory & Comment 이 챕터를 읽고 나서 기억에 남는 말
ukym-tistory.tistory.com
3. 비교적 배우기 쉽다.
- 공부하면서 객체지향의 특징들을 이해하기 좋은 언어라고 생각 !
4. 자동 메모리 관리 (cf. Garbage Collector)
- 자바로 구현된 프로그램이 실행되면, "가비지컬렉터(Garbage Collector)"가 자동적으로 메모리를 관리해주기 때문에 사람이 메모리를 따로 신경써서 관리해줄 필요가 없다 !
➙ 메모리 관리는 우리 책임이 아니다 ~ - 하지만, 자동으로 메모리를 관리해주는 작업 때문에 추가적인 CPU 리소스가 필요하며 오히려 메모리 사용량이 늘어날 수 있다는 점 !
💡 가비지컬렉터(GC)가 없는 경우는 어떻게 ?
프로그래머가 메모리 관리를 직접 해줘야 C언어 같은 경우엔 malloc, calloc, realloc 함수 등을 이용해서 메모리를 직접 동적으로 할당해주고, 그렇게 할당한 메모리를 더이상 사용할 일이 없으면 free 함수로 해당 메모리를 해제해줘야 한다 !
C언어 하면 떠오르는 개념인 "포인터(pointer)"가 바로 사용하고자 하는 메모리에 접근하기 위해 해당 메모리 주소를 저장해두는 변수이다 !
5. 네트워크와 분산처리를 지원한다.
- 다양한 네트워크 프로그래밍 라이브러리(Java API)가 있다.
💡 API (Applicaiton Programming Interface) ?
운영체제와 응용프로그램 사이의 통신에 사용되는 언어나 메시지 형식인데, 쉽게 말해서 쉐프, 웨이터, 손님이 있을 때 쉐프와 손님을 연결해주는 역할을 하는 "웨이터"가 바로 API이다 !
6. 멀티쓰레드(multi-thread)를 지원한다.
- OS에 따라 멀티쓰레드를 구현하는 방법이 달라지는데, Java를 사용하면 시스템에 관계없이 쉽게 구현 가능하다 !
- "멀티쓰레드(multi_thread)"가 CPU를 좀더 효율적으로 사용할 수 있도록 하는 데 큰 역할을 한다 !
- "자바 인터프리터(Java Interpreter)"가 바로 이런 여러 쓰레드에 대한 "스케줄링(scheduling)"을 담당하는 역할을 한다 !
💡 멀티쓰레드(multi-thread) ?
"멀티쓰레드"를 이용하면, 하나의 프로세스를 다수의 실행 단위(프로세스)로 구분하여 자원을 공유하고 자원의 생성과 관리의 중복성을 최소화하여 실행 성능을 높일 수 있다 !
쉽게 말하면, 하나의 프로그램에서 동시에 여러 개의 일을 수행 가능하게 해주는데, 사실 분산된 작업들이 "스케줄링"를 통해 동시에 실행되는 것 처럼 보이는 것이다 !
7. 동적 로딩(Dynamic Loading)을 지원한다.
- Java를 이용해 만든 애플리케이션은 여러 개의 클래스로 구성되어 있는데, 프로그램을 실행하게 되면 모든 클래스들이 실행되는 것이 아니다 !
➙ JVM의 "클래스 로더(class loader)"라는 것 덕분에 그때그때 필요할 때만 클래스를 로딩하여 사용할 수 있다 ! - 일부 클래스가 변경되어도 모든 클래스, 즉 전체 애플리케이션을 다시 컴파일할 필요가 없다 !
+) Java의 단점
- Java는 C와 C++와 같은 네이티브 언어이지만, 많은 메모리를 소비한다.
1. "Java Compiler"에 의해 JVM이 이해할 수 있는 "Java 바이트 코드"로 변환된다( .class )
2. 런타임(Runtime) 환경(프로세스가 실행 중)에서 "Class Loader"에 의해 JVM이 사용하는 메모리에 로드되고, "Execution Engine(Java Interpreter, JIT 컴파일러)" 에 의해 Java 바이트 코드는 해당 OS 가 이해할 수 있는 기계어로 변환되어 실행된다.
➙ 이 과정을 "컴파일(Compile)"이라고 한다 !
- 이처럼 일반 애플리케이션 코드와 달리 Java 애플리케이션은 OS만 거치고 하드웨어에 전달되는 것이 아니라 중간에 JVM을 한 번 더 거치기 떄문에, C와 C++ 같은 언어에 비해 작업 수행 속도가 느려질 수 밖에 없다 !
( 사실, 속도 격차가 그렇게 심하진 않다. )
💡 바이트 코드(Bytecode) ?
CPU(바이너리 코드)가 아닌 가상머신(ex. JVM)이 이해할 수 있는 0과 1로 구성된 이진 코드이다.
Java로 작성된 코드는 운영체제가 아닌 JVM 위에서 실행되는 바이트코드로 컴파일된다.
➙ 그렇기 때문에, 운영체제에 독립적일 수 있는 것이다 ! ( 장점이자 단점 ! )
- GC을 지원해주는 부분도 지속적으로 성능이 저하되는 원인이 된다 !