3 Week

8 분 소요




Property

속성이란 뜻이다. 해당 Object의 특징.
사람을 예로들면 피부색, 키, 나이, 몸무게 등이다.
Property의 읽기와 쓰기는 일반적으로 gettersetter 메서드 호출로 변환된다.

  • 자바는 property가 없고 코틀린이나 자바스크립트는 property를 사용 이건 왜그래?





String

자바에서 제일 많이 사용되는 String은 특별대우를 받는다.
Reference Type 이지만 기본적인 사용은 Primitive Type 이다.
String 객체생성은 2가지 방법이 있다.

String name = "kim";			// 리터럴로 생성
String name2 = new String("lee");	// new 연산자로 생성
  • new : 일반 객체들처럼 힙 영역에 할당된다.
  • 리터럴 : Constant Pool 영역에 할당된다.
    • String의 intern()메서드를 호출한다.
      • intern() 메서드 : Constant Pool에 생성하려는 문자열이 존재할 경우 주소값을 반환하고
        없으면 객체 생성 후 주소값을 반환한다.


Constant Pool

문자열 리터럴을 사용해서 생성한 String 객체가 담기는 곳이며 자바8 이후 힙 영역에 속한다.
같은 문자열을 각각 2개씩 리터럴과 new연산자로 생성했을 때, new 연산자로 생성된 문자열은 서로 다른객체를 만들고 바라보지만 리터럴로 생성해 Constant Pool에 담긴 두 객체는 동일한 객체를 바라본다.


String은 불변(immutable)하고 thread-safe 하다.

문자열 리터럴은 불변이기 때문에 Constant Pool에서 문자열이 같다면 동일한 객체를 참조할 수 있다.

  • static String?
    두개의 클래스의 각각 static String abc = "abc"; 를 선언하고 해시코드 값을 출력해보면 동일하다.
    즉, static 으로 선언해도 동일성과 동등성이 보장된다.


String 연산

Constant Pool의 특성 때문에 연산을 하게되서 문자열이 변경될 경우 기존 객체는 불변이기 때문에 새로운 객체를 만들고 해당 객체를 참조한다. 그리고 곧 참조가 사라진 기존 문자열은 가비지컬렉터의 수집 대상이 된다.
그렇기 때문에 문자열의 + 연산 또는 concat 연산 은 좋지 않다.
아래 두 클래스는 내부 Buffer에 문자열을 저장해두고 그 안에서 추가,수정,삭제 등을 작업할 수 있다.

  • StringBuffer
    • StringBuilder 보다 성능이 좋지 않다.
    • 동기화를 지원하여 thread-safe 하다.

  • StringBuilder
    • StringBuffer 보다 성능이 좋다
    • 동기화를 지원하지 않아 thread-safe 하지 않다.


활용


새로운 객체를 만들지 않고 문자열을 수정할 수 있기 때문에 참조가 끊어진 String 객체가
가비지컬렉션에 의해 메모리 해제를 기다리지 않아도 된다.
하지만 buffer의 크기를 초기에 설정해줘야 해서 생성속도가 String 객체보다 느리고 read가 느리다.
문자열 수정을 할 경우에도 Buffer의 크기를 줄이고 늘리고 명칭을 변경하는 등의 연산이 필요하다.

  • String
    • 문자열의 연산이 적을 경우
    • 빠른 조회 성능을 기대해야 할때
    • 문자열 연산이 적고 멀티스레드 환경일 경우

  • StringBuffer
    • 문자열 연산이 많을 경우
    • thread-safe 이 필요한 경우
      • 멀티스레드 환경

  • StringBuilder
    • 문자열 연산이 많을 경우
    • thread-safe 이 필요하지 않은 경우
      • 단일스레드 환경


JDK 1.5 이후


자바 1.5 이후 String의 immutable속성의 문제점을 해결하기 위해 도입되었다.
그래서 자바 1.5 이후 한줄 + 연산시 자동으로 StringBuilder 로 자동변환되어 성능 최적화를 이뤘다.
하지만 여러줄 + 연산할 경우 매 연산마다 새로운 StringBuilder 객체를 선언한다.
또한 for문 같은 반복문같은 경우에도 컴파일러가 최적화를 해주지 못하고 있다.
추가로 concat 연산은 컴파일러가 아예 최적화를 해주지 못한다.
그냥 문자열 연산에는 StringBuffer / StringBuilder 를 쓰도록 하자.





Annotation

Annotation 의 사전적인 의미로는 주석이다.
자바에서는 코드사이에 주석처럼 쓰여서 특별한 의미, 기능을 수행하도록 한다.
프로그램에게 추가정보를 제공해주는 메타데이터라고 볼 수 있다.
이 클래스에게 어떤 역할을 줄까? 이 속성을 어떤용도로 사용할까?

  • 메타데이터 : 데이터를 위한 데이터


용도

  • 컴파일러에게 에러를 체크하도록 정보 제공
  • 빌드나 패치 시 코드를 자동으로 생성할 수 있도록 정보 제공
  • 런타임단계에서 특정 기능을 실행하도록 정보 제공


설정

  • @Target : 적용대상
    • 어디(클래스, 필드, 메서드…)에 Annotation 을 적용할지
  • @Retention : 정보유지되는 대상
    • Annotation 값들을 언제까지 유지할 것인지 (대부분 Runtime)


왜쓰는데?

Annotation 을 붙일 타겟과 유지시기 등을 설정해 자신이 원하는 용도로 활용이 가능하다.
이것은 비즈니스 로직과는 별도로 시스템 설정과 관련된 부가적인 사항들을 Annotation
에게 위임하고 개발자는 비즈니스 로직 구현에만 집중할 수 있도록 하는 일종의 AOP
즉, Annotation 을 사용함으로써 관심사의 분리를 가능하게 한다는 의미기도 하다.





To Do





Exception

예외는 기본적으로 폭탄돌리기와 같다.
처리하거나 처리 try catch 할 수 없다면 throw 즉, 던져야 한다.
예외를 처리하지 못하고 계속 던지게 되면 결국 메인메서드까지 던져지게 되고 결국 어플리케이션이 종료된다.

이미지

  • Throwable : 최상위 예외 이며, 객체이기 때문에 Object를 상속받는다.

  • Error : 메모리부족, 시스템 오류같은 어플리케이션 레벨에서 복구가 불가능한 시스템 장애이다.
    ExceptionError는 다르다

  • 상위 예외를 잡으면 하위 예외까지 잡아버리기 때문에 어플리케이션에선 Exception 예외부터 필요한 예외로 잡아야 한다.
    또한, Layer에 역할에 맞는 Exception을 던질 필요가 있다.


Checked Exception

Exception을 상속받는 예외는 checked exception이 되며 Exception은 어플리케이션에서 사용할 수 있는 실질적인 최상위 예외이다. 컴파일러가 체크하며 잡아서 처리하거나, 또는 밖으로 던지도록 선언해야 한다.
예외 발생 시 트랜잭션 roll-back 처리를 하지 않는다.
(복구가 가능하다는 매커니즘을 가지고 있기 때문에 복구를 하라고 하고 롤백은 처리하지 않겠다 아닐까?)
단, RuntimeException을 상속받는 경우 unchecked exception이 된다.

  • 장점
    • 예외를 처리하거나 밖으로 던지는 throw 를 필수로 선언해야 컴파일 오류가 발생하지 않기 때문에
      개발자가 실수로 누락하는 상황이 발생하지 않는다.
  • 단점
    • 모든 체크예외를 반드시 잡거나 던지도록 처리해야 한다.
    • 의존관계 문제가 발생한다.
      • 예를들어 Controller / Service 등에서 SQL예외를 던졌을때 해당 클래스에서 SQLException을 의존하게 된다. 이럴경우 JDBC가 아닌 JPA같은 기술로 변경할 시 모든 코드를 고쳐야한다.
        즉, OCP / DI를 위반한다.
  • 활용
    기본적으로 unecked exception을 사용하자. checked exception은 비즈니스 로직상 반드시 처리해야 하는 경우에만 (계좌 이체 실패, 결제시 포인트 부족 등)
    즉, 개발자가 실수로 놓치게 되면 치명적인 문제가 발생하는 경우에 컴파일러의 도움을 받기위해 사용하자.
    또한, catch 절에서 딱히 할일이 없을 경우 사용하지 말고 확실히 할일이 있는 경우에 사용하자

  • 대표적인 Exception
    • IOException
    • SQLException


UnChecked Exception

RuntimeException과 그걸 상속받는 예외들은 unchecked exception이다.
컴파일러가 체크하지 않는 예외이며 throw를 생략할 수 있고 이럴 경우 자동으로 예외를 던진다.
즉, 예외를 잡아서 처리하지 않아도 throw를 생략할 수 있다.
예외 발생 시 트랜잭션 roll-back 처리를 한다.

  • 장점
    • throw 생략으로 예외를 무시할 수 있다.
    • 시스템에서 발생하는 복구가 불가능한 예외에 대해 신경쓰지 않아도 된다.
    • 신경쓰고 싶지 않은 예외의 의존관계를 참조하지 않아도 된다.
  • 단점
    • 컴파일 단계에서 체크가 안되기 때문에 실수할 가능성이 있다.
  • 대표적인 Exception
    • NullPointerException
    • IllegalArgumentException


예외 던져도 안죽던데?

웹 어플리케이션은 servlet의 오류 페이지나, 또는 Spring MVC가 제공하는 ControllerAdvice 에서 이런 예외를 공통으로 처리해 주기 때문에 예외가 메인메서드까지 가지 않는다.

  • 하지만 이런 문제들은 클라이언트 입장에선 어떤 문제가 발생했는지 자세히 알기 어렵다.
  • API라면 HTTP 상태코드 500으로 응답을 내려준다.
  • 수정하기 전까진 해결되지 않으므로 별도의 오류 로그를 남기고 메일, 알림등을 통해
    개발자가 빨리 인지할 수 있도록 처리해두는게 좋다.


자바의 Checked Exception 도입?

자바의 Checked Exception은 자바만의 독특한 특징이다.
“강제성이 없기 때문에 실수할 여지가 있다” 를 해결하기위해 도입했지만 컴파일단계에서 예외를 처리하라고
강요하는 Checked Exception 은 자바 이후 설계된 C# 이나 ruby등에서도 채택되지 않았다.
요즘은 극단적으로 자바의 Checked Exception 도입은 실패라는 주장도 심심치 않게 보인다.
JDBC API에서는 Checked Exception을 남발하고 catch절에서 아무것도 하지 않았고
이런 문제점을 알고 그후에 JDBC를 활용한 Spring-JdbcTemplet, Hibernate, JPA 등에서는
Checked ExceptionSQLException을 볼수 없게 설계되었고 이런 사례들을 보면 자바의
Checked Exception은 도입은 논란의 여지가 있는듯 하다.





의문점

가비지컬렉터는 알아서 메모리를 관리해 주는데, 결국 한계점에선 Major GC를 실행할 수 밖에 없고
이것이 실행되는 동안에는 결국 stop the world 즉, JVM이 멈추게 되는 현상이 발생하는데 그렇기 때문에
과거에는 GC를 튜닝해서(도메인에 따라 메모리 관리가 틀리기 때문에) 이런 현상이 최대한 적게 일어나게
하는것이 한동안 이슈였다.

그렇다면 하나의 큰 JVM 말고 여러개의 소규모 컨테이너로 JVM을 실행하면? 마치 로드밸런싱과도 비슷한데..
이렇게 하면 하나의 JVM에서 Major GC가 발생해도 어플리케이션 전체가 stop the world에 빠지진 않는데?
하지만 이 방법은 JVM이 나뉘어 짐에 따라 스택프레임을 공유할 수 없다는 단점이 있는데

요즘은 웬만한 프로그램도 웹어플리케이션으로 패러다임이 움직임에 따라 프로그램은
request -> response 이후 스레드가 종료되는 단순한 형태로 바뀌기 시작했고
그 결과 스택프레임을 공유하는 빈도 수 가 줄면서 GC튜닝 이슈는 좀 사그라 들었다.
하지만 완벽한 해결법은 아니고 자바 11 이후 GC의 방식이 조금 바뀌었다. 하지만 이것도 장단점..


자바 11 이후 바뀐 가비지컬렉션에 대해 자세히 알아보자

  • Generational GC
    JDK 7부터 본격적으로 사용할 수 있는 G1 GC를 제외한, Oracle JVM에서 제공하는 모든 GC

  • The Serial GC
    가장 단순한 GC이지만 사용하지 않는 것을 추천한다. 싱글 쓰레드 환경을 위해 설계 되었고 아주 작은 Heap영역을 가진다.
    Full GC가 일어나는 동안 애플리케이션 전체가 대기해야하는 현상이 발생하기 때문에 서버 애플리케이션에 적당하지 않다.

  • Parallel GC
    Java 8의 디폴트 GC이고 병렬로 GC한다.
    메모리가 충분하고 CPU의 성능과 코어 개수가 많아 순간적으로 트래픽이 몰려도 일시 중단을 견딜 수 있고 GC에 의해 야기된 CPU 오버 헤드에 대해 최적화할 수 있는 애플리케이션에 가장 적합하다.

  • G1(Garbage First) GC
    대용량 메모리가 있는 다중 프로세서 시스템을 대상으로 하는 서버-스타일 가비지 컬렉터이다.
    GC GC는 Generational 한 알고리즘과는 다르게 백그라운드의 멀티 쓰레드를 활용해 1MB에서 32MB까지의 수 많은 리젼으로 Heap을 분할한다.
    G1 GC는 위와 같이 바둑판의 각 영역에 객체를 할당하고 GC를 실행한다. 그러다가, 해당 영역이 꽉 차면
    다른 영역에서 객체를 할당하고 GC를 실행한다.
    즉, 지금까지 설명한 Young의 세가지 영역에서 데이터가 Old 영역으로 이동하는 단계가 사라진 GC 방식이라고 이해하면 된다.

    G1 GC의 가장 큰 장점은 성능이다.
    G1은 지연 시간을 줄이기 위해서 지금까지 설명한 어떤 GC 방식보다도 빠르다.
    하지만 이와 같이 4GB 이상의 큰 Heap을 가지는 것은 요즘과 같이 마이크로 서비스 아키텍쳐에서는 논쟁 거리가 될만하다.
    지난 몇 년동안 많은 개발자들이 거대한 시스템을 작은 마이크로 단위로 옮기는 노력을 해왔기 때문이다.
    이러한 Garbage Collector에 대한 변화는 Parallel GC와 같이 처리량을 극대화하는 것보다 GC의 지연 시간을 제한하는 것이 더 중요하다는 가정 하에 이루어졌다


튜닝?

JVM을 튜닝한다는 의미는 기존 GC 에서 Old 영역으로 넘어가는 객체의 수를 최소화하는 것과 Full GC의 실행 시간을 줄이는 노력이다.
Full GC 실행 시간을 줄이기 위해서 Old 영역의 크기를 줄이면 자칫 OutOfMemoryError가 발생하거나
Full GC 횟수가 늘어난다. 반대로 Old 영역의 크기를 늘리면 Full GC 횟수는 줄어들지만 실행 시간이 늘어난다.
Old 영역의 크기를 적절하게 설정해야 한다.
G1 GC를 제외한 GC에서는 JVM Heap을 무한정 늘리면 Full GC 시간 증가로 인해 오히려 성능 병목이 될 수 있다.
JVM의 Heap을 증가시키기 보다는 JVM의 인스턴스를 늘려 클러스터링이나 로드밸런서로 가용성을 확보하는 방법을 권장한다.


멀티코어상황에서 유용하다는데? 알아보자


스택프레임에 대해 알고있는 것보다 더 자세히 알아보자


하나의 JVM으로 띄우는 것과 여러개의 컨테이너로 띄우는 것의 장단점에 대해 자세히 알아보자

노드js에선 쓰고있는거 같던데?


멀티코어에서의 JVM과 멀티스레드

과거에는 싱글코어의 성능을 극한으로 올렸다. 하지만 전력소모, 코어의 면적을 넓히는 방식으로의 해결등이 한계에 다다르면서 코어 수를 늘려서 CPU 전체의 성능을 향상하는 쪽으로 발전했다.

  • 멀티코어 : 2개 이상의 독립코어를 단일 직접회로로 이루어진 하나의 CPU로 통합한 것
  • 프로세서 : 컴퓨터 내에서 프로그램을 수행하는 하드웨어 유닛 즉, 중앙처리장치 혹은 CPU
    • 소프트웨어적으로는 데이터포맷을 변환하는 역할을 수행하는 프로세싱 시스템
      • 워드프로세서, 컴파일러, 어셈블러 등
  • 프로세스 : 메모리에 적재되어 프로세서에 의해 실행중이거나 대기중인 프로그램


  • 멀티스레드
    • 프로세스를 생성하여 자원을 할당하는 운영체제의 시스템콜이 줄어든다.
    • 프로세스간 통신보다 스레드간의 통신의 비용이 적다.
    • 힙영역을 공유하므로 동기화 문제에 안전하지 않다.
    • 하나의 스레드에 문제가 생기면 전체 프로세스 전체가 영향을 받는다.
  • 멀티프로세스
    • 하나의 프로세스에 문제가 생겨도 다른 프로세스에 영향을 주지 않는다.

JVM은 하나의 프로세스에서 동작한다.
하나의 프로세스는 여러 작업단위를 가질 수 있는데 이것이 스레드 이다.
해당 JVM에서 스레드를 생성해 사용할 때는 OS의 스레드를 사용하는데 이 OS의 스레드는 다른 코어에 있고,
결국 하나의 JVM에서 멀티코어를 사용할 수 있는것이다.


근데 이말은 무슨말인지 자세히 알아보자

  • 프로세스는 독자적인 메모리를 할당받아서 서로 다른 프로세스끼리는 일반적으로 서로의 메모리 영역을 침범하지 못한다?


로드밸런싱에 대해서 자세히 알아보자






댓글남기기