JAVA 단골질문
컴파일 과정
JAVA는 OS에 독립적이다. JVM덕분
java코드 작성 -> 자바 컴파일러가 class코드로 컴파일(JVM이 이해할수 있는 코드인 바이트코드)
->class코드를 JVM의 클래스로더로 전달 -> 클래스로더가 동적로딩으로 필요한 클래스들을 로딩 및 링크하여 런타임 데이터 영역(JVM메모리)에 올린다 -> 실행엔진이 JVM 메모리에 올라온 바이트 코드들을 명령어 단위로 가져와서 실행
Call by value, Call by reference
JAVA는 오직 Call by value로 동작
JAVA는 참조를 직접 전달하는 것이 아닌, 값을 전달하는 방식으로만 동작한다.
변수를 선언하면 JVM메모리의 Stack에 변수가 저장되는데, Primitive Type(원시타입)은 값과 함께 저장되고, Reference Type(참조타입)은 객체는 Heap에 저장되고 Stack의 변수에 객체의 주소값을 저장한다.
즉, 객체를 넘겨도 그 객체의 주소값을 넘기므로, 파라미터에 선언되어 있는 변수가 스택에 잡히면서 해당 주소값을 가지고 있다. 따라서 해당 주소값에 접근해 값을 변경할 수는 있지만, 새로운 객체를 할당하면 함수가 종료되면서 가비지컬렉터가 메모리(Heap)에서 새롭게 할당된 객체를 제거한다.
String, StringBuilder, StringBuffer
String은 참조타입이며, 불변하는 객체이다.
따라서 String객체간 비교는 .equals()메소드를 사용해야 한다.
불변이기 때문에, 문자열의 조작이 일어나면 새로운 객체를 Heap에 생성하고, Stack의 변수에 새롭게 할당하는 구조이다. 따라서 가비지 컬렉터의 업무가 합쳐져 성능 문제가 생긴다.
StringBuilder와 StringBuffer는 버퍼 크기가 유동적이기에, String처럼 문자열 조작에서 성능 문제가 생기지 않는다. StringBuffer는 동기화를 제공하고, StringBuilder는 동기화를 제공하지 않는다.
따라서 멀티스레드 환경에선 StringBuffer, 단일스레드 환경에선 StringBuilder가 유리하다.
오토 박싱, 언박싱
기본타입 : int, long, float, double...
Wrapper 클래스 : Integer, Long, Float, Double, Boolean...
박싱 : 기본타입 -> Wrapper클래스
언박싱 : Wrapper클래스 -> 기본타입
오토 박싱, 언박싱 : 자바 컴파일러가 박싱과 언박싱이 필요한 상황에 자동으로 처리해준다.
직렬화
각자 PC의 OS마다 서로 다른 가상 메모리 주소 공간을 갖기 때문에, Reference Type의 데이터들은 인스턴스를 전달 할 수 없다.
따라서, 주소값이 아닌 Byte 형태의 Primitive 타입으로 직렬화해 전송 및 저장이 가능한 데이터로 만드는 것이 직렬화다.
데이터 통신 상에서 전송 및 저장을 위해 사용하는 방법이다. 이 때, 직렬화 데이터는 사이즈가 크므로 JSON포맷으로 변경하는 것이 좋다.
JSON(JavaScript Object Notation) : 사용하는 언어에 관계 없이 텍스트를 기반으로 데이터를 교환,저장하도록 정한 데이터 교환 표준
Object 클래스
JAVA의 최상위 클래스는 Object 클래스이다.
toString(), hashCode(), wait(), notify(), notifyAll() 등의 메소드를 공통으로 가지고 있다.
wait는 스레드를 잠들게 하며, notify는 임의의 스레드를 깨운다.
캐스팅
변수가 원하는 정보를 다 갖고 있는 것
다형성과 상속을 지원하기 위해 묵시적 형변환(업캐스팅), 명시적 형변환(다운캐스팅)이 존재한다.
스레드
프로세스는 운영체제로부터 시스템 자원을 할당받는 작업의 단위이고, 스레드는 프로세스 내에서 실행되는 여러 흐름의 단위이다.
즉, 스레드는 프로세스가 할당받은 CPU와 리소스들을 사용한다.
JAVA에서 스레드는 Runnable 인터페이스 구현, Thread 클래스 상속으로 구현할 수 있다.
스레드를 이용하면, JVM이 다수의 콜 스택을 번갈아가며 실행하는 Context Switching 작업을 수행한다.
동기화 : 멀티스레드 환경에서, 같은 프로세스 내의 자원을 공유하며 각 스레드의 작업들이 서로 영향을 주기 때문에 필수적이다.
임계 영역(critical section) : 공유 자원에 단 하나의 스레드만 접근하도록(하나의 프로세스에 속한 스레드만 가능)
뮤텍스(mutex) : 공유 자원에 단 하나의 스레드만 접근하도록(서로 다른 프로세스에 속한 스레드도 가능)
이벤트(event) : 특정한 사건 발생을 다른 스레드에게 알림
세마포어(semaphore) : 한정된 개수의 자원을 여러 스레드가 사용하려고 할 때 접근 제한
대기 가능 타이머(waitable timer) : 특정 시간이 되면 대기 중이던 스레드 깨움
JVM
OS에 종속받지 않고 CPU가 JAVA를 인식, 실행할 수 있게 하는 가상 컴퓨터
프로그램 메모리를 관리하고 최적화하는 것이 목표
1. 프로그램이 실행되면 JVM은 OS로부터 필요한 메모리를 할당받음
2. 자바 컴파일러가 자바 소스코드를 자바 바이트코드(.class)로 변환
3. class파일을 클래스 로더를 통해 JVM 메모리 영역으로 로딩
4. 로딩된 class파일들을 Execution Engine으로 해석
5. 해석된 바이트 코드를 메모리에 올림, 이 때 JVM에 의해 스레드 동기화, 가비지 컬렉션 등의 관리작업이 들어감
JVM 메모리의 스택, 힙을 구분하는 것이 가장 중요
가비지 컬렉션(GC)
사용하지 않는 객체는 메모리(힙)에서 삭제하는 작업
Minor GC : Young Generation(생성된지 얼마 안된 객체)를 대상으로 빠르게 GC작업 수행
Major GC : Old Generation(생성된지 꽤 된 객체)를 대상으로 느리게 GC작업 수행
Eden Space : 새로 생성된 객체가 할당되는 영역. 가득차면 Minor GC가 발생하며, 참조되고 있는 객체는 Survivor 영역으로 보낸다.
Survivor Space : 2개의 영역이 존재하며, 1번 영역이 가득 차면 2번 영역으로 이동한다. 끝까지 살아남으면 Old Generation으로 이동한다.
Minor GC에서 살아남은 숫자를 Age라고 하고, Age Threshold를 넘으면 old generation으로 promotion된다.
Error & Exception
Error의 상황을 미리 미연에 방지하기 위해서 Exception 상황을 만들 수 있으며, java에서는 try-catch문으로 Exception handling을 할 수 있습니다.
Error : 컴파일에러와 런타임에러(메모리 부족 등)처럼 프로세스가 종료될 정도의 문제(복구시도 X)
Exception : Not Found같이 애플리케이션이 복구 가능한, 복구를 시도하는 문제
Checked Exception : RuntimeException을 제외한 모든 예외. 처리하지 않으면 컴파일되지 않는다. ex)IO, SQL
Unchecked Exception : RuntimeException의 하위 예외 ex)NullPointer, IndexOutOfBound
Throwable 클래스 : 예외처리 최상위 클래스. Exception과 Error 모두 Throwable 클래스를 상속받는다.
Exception Handling : try catch문을 사용하거나, throws Exception으로 호출한 쪽이 책임지도록 유도한다.
JAVA Stream
Collection과 Stream은 데이터 계산 시점에 차이가 있다.
Collection : 모든 값을 메모리에 저장하며, 저장하기 전 미리 계산이 완료되어 있어야 한다. 외부반복을 통해 요소를 가져온다.
Stream : 요청할 때 요소를 계산한다. 내부반복을 통해 추출 요소를 선언해주면 알아서 반복처리한다. 요소를 따로 추가, 제거는 못한다.
내부연산 : 작업을 병렬처리하며 최적화된 순서로 처리한다. 외부반복은 명시적으로 컬렉션의 항목을 한개씩 가져와 처리한다.
Stream 중간 연산
filter(Predicate) : Predicate를 인자로 받아 true인 요소를 포함한 스트림 반환
distinct() : 중복 필터링
limit(n) : 주어진 사이즈 이하 크기를 갖는 스트림 반환
skip(n) : 처음 요소 n개 제외한 스트림 반환
map(Function) : 매핑 함수의 result로 구성된 스트림 반환
flatMap() : 스트림의 콘텐츠로 매핑함. map과 달리 평면화된 스트림 반환
중간 연산은 모두 스트림을 반환한다.
Stream 최종 연산
(boolean) allMatch(Predicate) : 모든 스트림 요소가 Predicate와 일치하는지 검사
(boolean) anyMatch(Predicate) : 하나라도 일치하는 요소가 있는지 검사
(boolean) noneMatch(Predicate) : 매치되는 요소가 없는지 검사
(Optional) findAny() : 현재 스트림에서 임의의 요소 반환
(Optional) findFirst() : 스트림의 첫번째 요소
reduce() : 모든 스트림 요소를 처리해 값을 도출. 두 개의 인자를 가짐
collect() : 스트림을 reduce하여 list, map, 정수 형식 컬렉션을 만듬
(void) forEach() : 스트림 각 요소를 소비하며 람다 적용
(Long) count : 스트림 요소 개수 반환
Composition
기존 클래스가 새로운 클래스의 구성요소가 되는 것
SOLID
SRP - 단일 책임 원칙 : 한 클래스는 하나의 책임만 가져야 한다.
OCP - 개방-폐쇄 원칙 : 확장에는 열려있고, 수정에는 닫혀있어야 한다.
LSP - 리스코프 치환 원칙 : 하위 타입은 항상 상위 타입을 대체 할 수 있어야 한다.
ISP - 인터페이스 분리 원칙 : 인터페이스 내에 메소드는 최소한 일수록 좋다. (하나의 일반적인 인터페이스보다 여러 개의 구체적인 인터페이스가 낫다.) SRP와 같은 문제에 대한 두 가지 다른 해결책이다.
DIP - 의존관계 역전 원칙 : 구체적인 클래스보다 상위 클래스, 인터페이스, 추상클래스와 같이 변하지 않을 가능성이 높은 클래스와 관계를 맺어라. DIP 원칙을 따르는 가장 인기 있는 방법은 의존성 주입(DI)이다.
출처: https://dev-coco.tistory.com/153 [슬기로운 개발생활:티스토리]
Reference