JAVA
종류
자바는 크게 세가지의 종류로 나뉜다. SE, EE, ME
- Java SE (Standard Edition)
자바 언어의 핵심 기능을 제공. 자바 언어의 기본적인 타입과 객체에서부터, 네트워킹, 보안, 데이터베이스 접근, GUI, XML 파싱에 사용되는 고수준의 클래스까지 모두 정의한다. 보통 학생들이 사용하는 수준의 에디션이다.
- Java EE (Enterprise Edition)
얘는 Java SE 플랫폼 상에 구축된다. 이는 기업용 애플리케이션을 개발하는 데 필요한 여러가지 도구와 라이브러리를 모아 놓은 것이다. 이 패키지는 응용 서버, 웹서버, 엔터프라이즈 자바 빈즈를 지원하고 자바 서블릿 API, JSP 등을 포함한다. EE는 엔터프라이즈 급의 서버 지향 구조나 차세대 웹 애플리케이션을 구현하는 업계 표준이 되어가고 있다.
- Java ME (Micro Edition)
핸드폰, PDA, TV 셋톱박스, 프린터와 같은 모바일 기기나 다른 임베디드 장치들에서 실행되는 애플리케이션을 ㅎ위한 강인하고 유연한 환경을 제공한다. ME 역시 SE의 부분 잡합에 속하며, 모바일 장치를 위한 특수한 클래스 라이브러리를 추가적으로 지원한다. Java ME에 기반을 둔 애플리케이션은 많은 장치 간에 이식이 가능하며 성능이 저하되지 않는다.
개발 도구
우리가 자바 프로그램을 개발하기 위해서는 자바 컴파일러가 있어야 한다. 일반적으로 자바 컴파일러와 JVM, 디버깅 도구, 각종 유틸리티 등의 도구들을 JDK (Java Development Kit)라 부른다. 자바 개발 도구라는 의미이다.
- JRE (Java Runtime Environment)
JRE는 자바 프로그램을 실행하기 위한 라이브러리, JVM, 기타 컴포넌트들을 제공한다. 자바 프로그램을 실행만 할 뿐 개발은 하지 않는 일반인들을 위한 환경이다.
- JDK (Java Development Kit)
JDK는 JRE에 자바 프로그램을 개발하는데 필요한 컴파일러, 디버거와 같은 도구를 추가한 것이다. JDK 안에 JRE가 포함되어 있다. 만약 자바 개발을 하고자 하는 사람이라면 둘을 따로 다운받을 필요 없이 JDK 하나만 다운 받으면 된다.
JDK를 다운 받으면 컴퓨터 안에 몇 개의 폴더가 생성된다. C:\Program Files\Java 안에 JDK와 JRE 폴더가 있다.
JDK의 디렉토리에는
- bin(컴파일러, 디버거 등의 도구가 들어있음)
- include(네이티브 코드 프로그래밍을 지원하는 헤더 파일들. 이 파일들은 자바와 C를 동시에 사용하는 프로그램 개발 시에 쓰인다)
- lib(개발 도구들이 필요로 하는 추가적인 클래스 라이브러리와 지원 파일들이다)
세가지의 폴더가 존재한다.
구동 원리 (실행 과정)
- 경로 설정하기 JDK에 포함된 명령어 도구를 사용하기 위해서는 운영 체제에게 JDK가 설치된 위치를 알려주어야 한다. 이를 위해 환경 변수의 Path 값에 C:\Program Files\Java\JDK\bin의 주소를 추가해준다.
- 소스 파일 생성 자바 에디터를 통해 자바 소스(*.java)를 작성한다.
- 컴파일 소스 파일이 작성되면 자바 컴파일러(javac)로 컴파일 한다. 컴파일러는 자바 언어로 작성된 프로그램을 가상 컴퓨터의 기게어가 들어있는 파일(바이트 파일, *.class)로 변환한다. 보통 클래스 파일은 소스 파일과 동일한 디렉토리에 만들어진다.
- 실행 자바 가상 기계(JVM)라는 특수한 가상 컴퓨터 소프트웨어를 통해 바이트 코드를 한 줄씩 해석하여 실행한다. 콘솔로 실행할 때는 >java Hello 처럼 확장자를 제거하여 입력해야한다. (java가 JVM이다)
이렇게 두 단계로 나누어서 컴파일하고 실행하는 이유는 응용 프로그램들을 다시 컴파일 하지 않아도 모든 컴퓨터에서 실행되도록 하기 위해서이다. 우리가 JVM만 가지고 있다면 어떤 바이트 코드도 다시 컴파일할 필요없이 JVM 위에서 실행할 수 있다. JVM는 많은 운영체제에서 지원되기 때문에 동일한 바이트 코드 파일이 윈도우, 리눅스, 맥OS 에서 변경없이 실행될 수 있는 것이다.
JVM
개념
일반적인 프로그래밍 언어는 플랫폼에 종속된다. 예를 들어 C++언어로 만들어진 프로그램은 컴파일러에 의하여 특정 CPU의 기계어로 변환된다. 따라서 이 프로그램은 CPU가 다른 컴퓨터에서는 실행되지 않는다. 예를 들어 인텔CPU를 가진 컴퓨터에서 컴파일하면 모토롤라 CPU를 사용하는 컴퓨터에서는 실행할 수 없다. 이것을 플랫폼 종속이라고 부른다.
하지만 자바는 플랫폼에 종속되지 않는다. 자바의 가장 중요한 특징이 자바 프로그램을 한번만 작성하면 어떤 컴퓨터 기종에서도 실행이 가능하다는 점이다. 실행 파일을 변경할 필요도 소스 파일을 다시 컴파일할 필요도 없다. 이것은 인터넷에서 자바 애플릿을 다운로드 하여서 실행해보면 알 수 있다. 자신이 사용하는 컴퓨터가 애플 컴퓨터거나 IBM-PC이거나에 상관 없이 애플릿의 실행이 가능함을 알 수 있다.
이는 자바가 가상 기계(Virtual Machine)의 개념을 사용하기 때문이다. 가상 기계는 가상적인 컴퓨터를 의미한다. 자바 컴파일러는 소스 프로그램을 가상 컴퓨터의 기계어로 번역하는데, 이렇게 만들어진 기계어는 자바 가상 기계(JVM)에 의해 실행된다. JVM을 이용하면 프로그램으로부터 운영 체제와 하드웨어를 숨길 수 있다.
가상 기계라는 아이디어는 자바가 처음이 아니지만 상업적으로 성공 시킨 언어가 바로 자바이다. 자바에서는 컴파일된 실행 코드가 플랫폼 독립적이다. 자바로 개발된 프로그램은 CPU나 운영 체제의 종류에 관계 없이 어디서나 실행할 수 있다. 이러한 특징 때문에 자바는 인터넷 시대에 가장 잘 맞는 언어라고 할 수 있는데 인터넷은 다양한 종류의 컴퓨터가 연결된 네트워크이기 때문이다.
근데 웃기게 JVM은 플랫폼 종속적이라 운영체제마다 다른 JVM을 설치해주어야 한다.
일정 기준을 넘어가면 컴파일 방식을 변경한다? jit 방식으로 바뀐다. 인터프리터가 한줄 씩 해석하는데 반복해서 읽는 코드의 경우에는 그걸 JIT 컴파일러가 미리 기게어로 만들어둬서 인터프리터가 다시 그 코드를 읽으려할 때 미리 만들어 둔 네이티브 코드를 보고 읽는다. (https://beststar-1.tistory.com/3)
https://perfectacle.github.io/2019/05/11/jvm-gc-advanced/
Garbage Collection
개념
Garbage Collection(쓰레기 수집)은 동적 할당된 메모리 영역 가운데 더 이상 사용할 수 없게 된 영역, 즉 어떤 변수도 가리키지 않게 된 영역을 탐지하여 자동으로 해제하는 기법이다. 많은 현대 언어들이 쓰레기 수집 기법을 사용한다. 자바, 스몰토크, 자바스크립트 등의 객체지향 언어들은 대부분 쓰레기 수집이 내장되어 있다. 그러나 델파이와 C++은 쓰레기 수집을 사용하지 않는다(라이브러리를 통해 사용할 수 있다).
장단점
프로그래머가 동적으로 할당한 메모리 영역의 전체를 완벽하게 관리할 필요가 없어진다. 쓰레기 수집은 유효하지 않은 포인터 접근, 이중 해제, 메모리 누수 등의 버그를 줄이거나 없앨 수 있다.
작동 과정
가비지 컬렉터는 다양한 동작 방식을 가지고 그에 따라 종류도 많다. 그러나 공통적으로 2가지의 작업을 수행한다.
- 힙 내의 객체 중에 가비지를 찾아낸다.
- 찾아낸 가비지를 처리해서 힙의 메모리를 회수한다.
Reachability
힙 영역에 있는 객체를 사용되는 것(Reachable)과 사용되지 않는 것(Unreachable)으로 구분한다. Reachability를 판가름하는 기준은 root set의 참조 사슬에 속하느냐 속하지 않느냐가 된다. root set이란 최초의 참조를 의미하고, 이들이 참조하는 객체들이 꼬리를 물어 참조 사슬을 이룬다. 여기에 속하지 않는 객체를 Unreachable 객체라 부르며 가비지 컬렉터의 대상이 된다.
Reference
Reference는 soft reference와 weak reference, phantom reference를 클래스 형태로 제공한다. (일반 참조는 strong reference)
Reference와 Reachability
원래 GC 대상 여부는 reachable인가 unreachable인가로만 구분하였고 이를 사용자 코드에서는 관여할 수 없었다. 그러나 java.lang.ref 패키지를 이용하여 reachable 객체들을 strongly reachable, softly reachable, weakly reachable, phantomly reachable로 더 자세히 구별하여 GC 때의 동작을 다르게 지정할 수 있게 되었다.
GC가 동작할 때, unreachable 객체뿐만 아니라 weakly reachable 객체도 가비지 객체로 간주되어 메모리에서 회수된다. root set으로부터 시작된 참조 사슬에 포함되어 있음에도 불구하고 GC가 동작할 때 회수되므로, 참조는 가능하지만 반드시 항상 유효할 필요는 없는 LRU 캐시와 같은 임시 객체들을 저장하는 구조를 쉽게 만들 수 있다.
Strength of Reachability
자바 GC는 root set으로부터 시작해서 객체에 대한 모든 경로를 탐색하고 그 경로에 있는 참조 객체들을 조사하여 그 객체에 대한 reachability를 결정한다. 다양한 참조 관계의 결과, 하나의 객체는 다음 5가지 reachability 중 하나가 될 수 있다.
- strongly reachable: root set으로부터 시작해서 어떤 reference object도 중간에 끼지 않은 상태로 참조 가능한 객체, 다시 말해, 객체까지 도달하는 여러 참조 사슬 중 reference object가 없는 사슬이 하나라도 있는 객체
- softly reachable: strongly reachable 객체가 아닌 객체 중에서 weak reference, phantom reference 없이 soft reference만 통과하는 참조 사슬이 하나라도 있는 객체
- weakly reachable: strongly reachable 객체도 softly reachable 객체도 아닌 객체 중에서, phantom reference 없이 weak reference만 통과하는 참조 사슬이 하나라도 있는 객체
- phantomly reachable: strongly reachable 객체, softly reachable 객체, weakly reachable 객체 모두 해당되지 않는 객체. 이 객체는 파이널라이즈(finalize)되었지만 아직 메모리가 회수되지 않은 상태이다.
- unreachable: root set으로부터 시작되는 참조 사슬로 참조되지 않는 객체
알고리즘
가비지 컬렉터에는 다양한 알고리즘이 존재하지만 가장 대표적으로는 Mark-Sweep 방식이 있다. root set에서부터 시작해 참조 객체 사슬을 비트 마스크 1로 표시를 하며 쭉 내려간다. 그 뒤 표시가 되어있지 않은 참조 객체들의 메모리를 회수하는 방식이다. 그 뒤 파편화된 메모리 영역을 다시 앞에서부터 채워나가는 작업까지 수행한다. 그러나 이 방식은 stop-the-world 시간(Mark 과정에서 전체 스레드가 작동을 멈추게 되는 시간)이 너무 길어 요즘에는 사용되지 않는다.
Thread-Safe
개념
스레드 안전(thread safety)은 멀티 스레드 프로그래밍에서 일반적으로 어떤 함수나 변수, 혹은 객체가 여러 스레드로부터 동시에 접근이 이루어져도 프로그램의 실행에 문제가 없음을 뜻한다. 보다 엄밀하게는 하나의 함수가 한 스레드로부터 호출되어 실행 중일 때, 다른 스레드가 그 함수를 호출하여 동시에 함께 실행되더라도 각 스레드에서의 함수의 수행 결과가 올바로 나오는 것으로 정의한다.
스레드 안전 판별
보통 어떤 프로그램이 스레드 안전인지 아닌지 알아내는 것은 간단하지 않지만 다음을 참고할 수 있다.
- 전역 변수나 힙, 파일과 같이 여러 스레드가 동시에 접근 가능한 자원을 사용하는지 여부
- 핸들과 포인터를 통한 데이터의 간접 접근 여부
- 부수 효과를 가져오는 코드가 있는지 여부
방식
thread-safe하지 않은 코드
class MyCounter {
private static int counter = 0;
public static int getCount() {
return counter++;
}
}
- 함수 동기화하기
class MyCounter {
private static int counter = 0;
public static synchronized int getCount() {
return counter++;
}
}
mutex로 동기화 한 코드
class MyCounter {
static Semaphore mutex = new Semaphore(0);
static Semaphore mutex = new Semaphore(1);
private static int counter = 0;
public static int getCount() {
try {
mutex.acquire(); // lock before producing
try {
counter++;
} finally {
mutex.release(); // releasing after production
}
} catch(InterruptedException ie) {
// ...
}
return counter;
}
}
- java.util.concurrent.atomic 임포트 해서 AtomicInteger 변수에 접근하도록 만든다
import java.util.concurrent.atomic.AtomicInteger;
public class MyCounter {
private static AtomicInteger counter = new AtomicInteger(0);
public static int getCount() {
return counter.getAndIncrement();
}
}
직렬화(Serialization)
개념
객체를 파일에 저장할 때 객체가 가진 데이터들을 순차적인 데이터로 변환하는 절차를 의미한다. Serializable 인터페이스를 구현한 클래스에서만 사용할 수 있으며, 스트림은 ObjectStream을 사용해야 한다. 대부분의 표준 클래스는 이미 객체의 직렬화를 지원하고 있다.
객체가 직렬화된 데이터를 읽어서 자신의 상태를 복구하는 것을 역직렬화(deserialization) 라고 한다. 역직렬화해서 담을 데이터 인수 타입과 역직렬화 후 데이터 타입은 반드시 일치해야 한다.
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("d://object.dat"));
out.writeObject(new Date());
out.flush();
ObjectInputStream in = new ObjectInputStream(new FileInputStream("d://object.dat"));
Date d = (Date) in.readObject();
// 실행 결과 (예)
// Sat Feb 09 14:46:32 KST 2022