커리어 빌딩, java
공통
유니코드에 대해서 설명해보세요.
1바이트를 사용하는 아스키만으로는 전세계의 문자를 표현할 수 없어서 나온 코드표. 여러 코드표가 존재하며 널리 사용되는 UTF-8 은 1-4바이트 가변이다.
java
- jdk 버전 별 차이
- jdk 1.3
- dynamic proxy?
- jdk 1.4
- nio
- jdk 1.5
- auto boxing/unboxing
- annotation
- generic
- enum
- concurrency
- static import
- jdk 1.7
- nio - file
- switch string
- auto closable
- jdk 1.8
- 32bit support last version
- lambda
- stream
- local date time - joda time
- interface default
- jdk 9
- immutable collection
- private interface method
- jdk 10
- var keyward
- jdk 11
- jdk 12
- switch ->
- performance (+gabage collection)
- jdk 13
- switch yield
- jdk 1.3
- lambda
collection 에 대해서 설명해보세요.
- Collection interface, 순서나 집합
- Queue
- List interface, 순서O 중복O
- Linked List
- Vector, ArrayList + ThreadSafe
- Stack(Vector)
- ArrayList
- Set interface, 순서X 중복X
- HashSet
- SortedSet interface, 순서O
- TreeSet
- Map interface, 키-값, 순서X 키중복X 값중복O
- HashTable, ThreadSafe
- HashMap
- SortedMap interface, 순서O
- TreeMap
-
HashMap, HashTable 모두 Map 을 구현하고 있으나, HashMap 은 보조해시함수가 있어서 충돌이 상대적으로 적다.
- junit
- 3, 4, 5 life cycle
- 3
- TestCase 상속
- method prefix = test
- setUp, tearDown 등 함수로 테스트 이전 이후 설정
- 4
- jdk5+
- 독립 클래스(TestCase 상속x)
- method prefix 삭제, annotation @Test 로 테스트 마킹
- @Test(exception=ExpectedException.class)
- @Test(timeout=ExpectedTimeOutMilliseconds)
- @Ignore 로 테스트 제외
- setUp -> @Before, tearDown -> @After, @BeforeClass, @AfterClass 추가
- class level @RunWith, JUnit 의 TestRunner 대신 지정된 클래스로 테스트 진행
- class level @SuiteClasses({TestClass01.class, …}), 여러 테스트 유닛을 함께 돌림
- 5
- jdk8+
- 의존성이 나눠짐. JUnit platform + JUnit jupiter + JUnit Vintage
- JUnit platform, JVM 환경에서 테스트 프레임웍을 실행
- JUnit jupiter, annotation, assert, …
- JUnit vintage, JUnit3, 4 지원
- method level, @DisplayName 으로 테스트 명 스트링으로 지정가능
- assert 함수의 파라미터 순서 변경, 좀 더 알아보기 쉽게
- assertEquals(“message”, “expect”, “actual”) -> assertEquals(“expect”, “actual”, “message”)
- @Test(exception) 으로 사용하면 예외 발생을 맨위에서 확인하므로 가독성에 문제가 생김
- assertThrow(() => { throws new ExpectedException() }); 형식으로 lambda 를 사용하면서 가독성도 해결
- @Ignore -> @Disabled
- @Test(timeout) -> @Timeout
- @Before -> @BeforeEach (after 도 마찬가지)
- @BeforeClass -> @BeforeAll (after 도 마찬가지)
- 공식문서
- String 의 encoding 방식
OOP, 객체 지향 프로그래밍에 대해서 설명해보세요.
- Encapsulation, 캡슐화
- 객체의 속성과 행위(메소드)를 하나로 묶고, 실제 구현의 일부를 외부로부터 감춘다.
- 사용자의 객체 접근 영역을 제한하여, 오용을 막는다
- 객체간 결합도가 낮아진다
- Inheritance, 상속
- 자식 클래스가 부모 클래스의 특성과 기능을 물려 받는 것.
- 캡슐화를 유지하면서 클래스의 재사용을 용이하도록 만듬
- Polymorphism, 다형성
- 인터페이스를 통해 상위 클래스의 참조변수가 어떤 하위 클래스를 가리키느냐에 따라 호출되는 메소드가 달라지는 것
객체지향 5원칙에 대해서 설명해보세요.
- Single Responsibility Principle, 단일 책임 원칙 구조를 깔끔하게 유지할 수 있고, 불필요한 수정을 예방하며, 기능을 명확하게 분리할 수 있다.
- Open-Closed Principle, 개방-폐쇄 원칙 확장에는 열려있고 수정에는 닫혀있다.
- Liskov Substitution Principle, 리스코프 치환 원칙 자식 클래스는 언제나 부모 클래스를 대체할 수 있다. 이를 지키지 못하면 다형성을 지킬 수 없게 된다.
- Interface Segregation Principle, 인터페이스 분리 원칙 클라이언트에서 사용하지 않는 메소드는 만들지말고 인터페이스를 작게 나누어서 만든다. 인터페이스의 단일 책임 원칙.
- Dependency Inversion Principle, 의존성 역전 원칙 추상성이 높은 고수준의 클래스는 구체적이고 불안정한 저수준 클래스에 의존하면 안된다. IoC 와는 다름.
class member 초기화 순서
- static 선언부, class 가 load 될때 초기화 된다. 기본값 -> 초기화 -> 블럭 순서
- 필드 선언부, 객체 생성시, 생성자보다 먼저 초기화 된다. 기본값 -> 초기화 -> 초기화 블럭 -> 생성자 순서
- 생성자 함수, jvm 내부적으로 thread-safe, 필드 변수 중 final 부분의 공개시점은 생성자 함수가 끝난 뒤
interface 를 사용하는 이유?
- class 가 구현해야하는 동작을 지정하는 추상자료형. 고 수준의 추상화가 이루어진 클래스에서 저수준 추상화의 구체적 클래스의 구현에 구애받지 않기 위해?
Generic 에 대해서 설명해보세요.
데이터 타입을 파라미터화 한 것으로, 사용할 데이터 타입을 컴파일 타임에 지정하는 것을 의미한다.
- Type-Safe 하게 데이터 형식을 일반화 할 수 있다.
- 타입 캐스팅에 자유로워진다.
Reflection 에 대해서 설명해보세요.
클래스 구조를 runtime 에 확인할 수 있도록 하는 기술. 정적언어의 한계를 넘게 해준다. (java 는 정적언어, javascript/python/ruby 는 동적언어)
serialVersionUID 의 사용 이유
Serializable interface 를 구현하여, Object Serialize 를 이용할 경우, (역)직렬화를 이용할 때, 검증하는 부분(일종의 체크섬이랄까?) 컴파일러마다 다른 값을 부여할 수 있기 때문에, 다른 컴파일러를 사용하는 기기일경우 (역)직렬화가 실패할 수 있다.
extends vs implements, 상속과 구현의 차이, 상속과 interface 의 차이
is-a 관계에서는 상속, can-do 관계에서는 구현 상속관계가 깊어지면 상위 클래스의 변경점에 대한 하위클래스의 사이드이펙트를 예측하기 힘들다.
JVM 메모리 영역에 대해서 설명해보세요.
JVM 의 구조
- Garbage Collector
- Class loader
- Excution Engine
- Runtime Data Area
- Heap
- Young
- Old
- NoneHeap - Metaspace(permanent)
- Stack
- PC Register, 현재 실행 중인 주소
- Native Method Stack
- Method Area, 인스턴스생성에 필요한 정보, 필드/메소드/생성자 등
- Constant Pool, 상수/필드, 메소드의 레퍼런스값
- Heap
GC 에 대해서 설명해 보세요.
weak generational hypothesis 가설에 의해 설계됨
- 대부분의 객체는 금방 접근 불가능 상태(unreachable)가 된다.
-
오래된 객체에서 젊은 객체로의 참조는 아주 적게 존재한다. 이를 위해 Young/Old 영역으로 물리적 공간을 나눔. 새로 만들어진 객체는 Yong Generation -> 오래되면 Old Generation(Young < Old),
- Permanent 영역은 메소드공간
- Young 영역의 GC 는 minor GC, Old 영역의 GC 는 Major/Full GC 그리고 stop-the-world 가 일어남.
- Young 영역은 Eden 과 두개의 Survivor 영역, Eden -> Survivor1 <-> Survivor2 -> Old
- Old 영역의 객체가 Young 영역의 객체를 참조하는 것을 확인하기위해 Card Table 이 존재
HotSpot VM 에서는 빠른 메모리 할당을 위해 두가지 기술을 사용
- bump-the-pointer(Eden 영역에 할당된 마지막 객체를 추적하여 모든 크기 측정을 하지 않아도됨)
- TLABs(Thread-Local Allocation Buffers, 스레드 별 다른 Eden 영역 할당으로 Thread-safe) 라는 기술을 사용.
Old 영역의 GC 는 jdk7 기준 5가지,
- Serial GC -> 코어가 하나일때 만들어진 방식. 서버에서 사용금지, Mark(Old 영역의 살아있는 객체 식별)->Sweep(heap 의 앞부분부터 살아있는 것만 남김)->Compaction(순서대로 정리)
- Parallel GC/Throughput GC -> Serial GC 의 병렬 버전
- Parallel Old GC(Parallel Compacting GC) -> Mark-Summary-Compaction, Sweep 부분이 좀 다름
- Concurrent Mark & Sweep GC(이하 CMS/Low Latency GC) -> 복잡함
- Initial Mark(클래스로더에서 가장 가까운 객체 중 살아있는 것 식별, stop-the-world)
- Concurrent Mark(위의 식별된 객체가 참조하는 객체를 따라가며 식별, 스레드 별 수행. 전체가 멈추지 않음)
- Remark(위의 객체 중 추가되거나 삭제된 것 식별)
- Concurrent Sweep(식별된 객체 정리)
- 응답시간이 제일 빠른 GC 이면서, CPU 를 가장 많이 사용하고 Compaction 단계가 없다. 그래서 Full GC 시간이 길어진다.
- jdk 8 이전까지 기본
- G1(Garbage First) GC
- jdk 9 부터 기본, 큰메모리를 가진 멀티 프로레서를 위해 개발.
- Old 와 Young 영역 방식이 아님. 바둑판처럼 생긴 영역에 객체를 할당하고 GC 를 수행한다.
- Eden -> Old 는 맞지만,
- stop-the-world 를 최소화 하기위해 설계
- Full GC 단계
- Initial Mark - Old Region에 존재하는 객체들이 참조하는 Survivor Region을 찾는다. 이 과정에서 Stop-The-World가 발생하게 된다.
- Root Region Scan - Initial Mark 에서 찾은 Survivor Region에서 GC 대상 객체를 탐색한다.
- Concurrent Mark - 전체 Region에 대해 스캔하여, GC 대상 객체가 존재하지 않는 Region은 이후 단계에서 제외된다.
- Remark - Stop-The-World 후, GC 대상에서 제외할 객체를 식별한다.
- Cleanup - Stop-The-World 후, 살아있는 객체가 가장 적은 Region에 대해서 참조되지 않는 객체를 제거한다. Stop-The-World 끝내고 완전히 비워진 Region을 Freelist에 추가하여 재사용한다.
- Copy - Root Region Scan 단계에서 찾은 GC 대상 Region이었지만 Cleanup 단계에서 살아남은 객체들을 Available/Unused Region에 복사하여 Compaction 작업을 수행한다.
Stream 의 장점/단점
- 외부 반복을 내부 반복으로 변경하여 동작에 집중할 수 있다.
- 고수준 추상화 덕분에 병렬처리에 신경쓰지 않아도 된다.
- 파이프라이닝으로 처리하기 때문에, 이론상
laziness(요청할때 계산), short-circuiting(allMatch, noneMatch, findFirst, findAny, 전체를 계산하지 않아도 결과를 낼 수 있음) 같은 최적화를 할 수 있다.
- 개발자가 해야될 것이 추상화되어 숨겨져있기 때문에 대체적으로 성능이 떨어진다.
- 성능 이야기를 좀 더 해보자면, 공유 thread pool 을 사용하기 때문에, 병렬수행 시 성능이 매우 떨어질 수 있다.
- 마찬가지로 오류가 생겼을때, 디버깅하기가 쉽지않다.
- 함수형 프로그래밍을 한다고해서 항상 가독성이 좋은 것도 아니다.
singleton 을 사용할 때 생기는 문제와 해결 방안에 대해서 말해보세요.
singleton 변수가 초기화 되지 않은 상태에서 다중 스레드가 동시에 인스턴스 요청을 할때, 흔히 일어나는 double check locking 문제가 있음.
해결방안
- Eager Initialization, 이른 초기화 내부 static 변수에서 미리 만들어 놓는다. -> 필요 시점이 아니라 class load 시점에 singleton 변수가 초기화 된다. 간단함
- Initialization-on-demand Holder Idiom - Static Holder 사용 내부 static 변수에서 미리 만들어 놓는건 마찬가지지만 lazy load 를 위해 private static class 로 static 변수를 담을 static holder class 를 둔다. 이렇게 하면, 내부 static class 기 때문에, 외부 class load 시점이 아니라 내부 class 가 load 되는 시점에 static 변수가 초기화 되어 lazy load 효과를 줄 수 있다.
singleton 객체에 너무 많은 책임을 주게되면 객체지향설계원칙을 어기게되어 (단일책임원칙 오픈클로즈) 유지보수가 어렵게 된다.