item6 - 불필요한 객체 생성을 피하라
이번 주제에 대해서는 익히 들었던 이야기이다 객체를 생성하게 되면 JVM의 힙 영역에 객체들이 생성되게 되고, 객체의 사용이 끝나면 가비지 컬렉터(GC)의 스캔 대상이 되어, 가비지 컬렉터가 돈다 가비지 컬렉터의 특징 중 하나는 STW(Stop The World) 으로, 가비지 컬렉터가 돌면서 JVM이 멈춘다는 점이다 그렇기 때문에 이걸 줄이기 위해서 불필요한 객체를 줄여야 한다고만 듣고, 최대한 객체를 생성해서 사용하는 것보다는 autowired 나 정적 팩토리 메소드를 통해서 사용하려고 노력중이다
책에서 언급했던 예시로는 크게 3가지가 있다 String 과 정규식 그리고 박싱/언박싱의 개념이다
String
우리가 자주 사용하는 String을 사용할 떄 new String() 이렇게 사용하는 것은 살면서 본적이 없을 것이다 실제로 한 번도 그러한 코드는 본적이 없다... (물론 코드를 그렇게 많이 보지는 않았지만..) JVM에서는 String을 내부의 풀에서 캐싱을 해두고 있다 일종의 해쉬맵에 만든 모든 String을 담아두고, 또 동일한 문자열을 참조하려고 하면 이미 만들어둔 풀에서 참조해서 가져오는 방법이다 물론, 만약에 같은 JVM에서 생성된 문자열이 아니면 다를 수 도 있다 예를 들면, 외부 api에서 받아온 String과 내부에서 생성한 String 같은 경우에는 같은 JVM 태생이 아니기 때문에 다르다 -> 그래서 일반적인 equals을 통해서 비교 그래서 같은 문자열을 가지고 ==을 통해서 비교하게되면 같다고 나온다 하지만 new String을 통해서 만들어버리면 ==을 통해서 비교해보면 다르다고 나온다
정규식
정규식을 생성하는 방법인 Pattern.compile()이라는 함수는 객체 생성하는데 있어서 자원을 많이 먹는 객체 중 하나이다 String.matches()을 통해서 비교하는 과정을 그냥 기본적으로 생성해서 사용해보는 것과 따로 static final로 빼두고 가져와 사용하는 것과는 해당 메소드를 100번씩만 호출해도 벌써 100배 정도의 차이가 나는 것을 확인할 수 있었다
그래서 static final 와 같이 만들어두고 사용하는 것 처럼 캐싱을 통해 재사용을 하는게 좋다
박싱/언박싱
객체의 타입에는 박싱/언박싱 개념이 있다
크게 primitive 타입과 wrapper 타입 이렇게 나뉘어진다 primitive 타입을 우리가 알고 있는 int, boolean, long 등의 소문자 객체 타입을 의미한다 그럼 반대인 wrapper 타입은 생각보다 생소한 Integer, Boolean, Long 과 같은 박싱 타입을 의미하는데 이것들은 primitive 객체와는 다르게 null-safe한 장점이 존재한다 null-safe이라고 하니까 뭔가 거창해보이지만, wrapper 타입으로 사용하게되면 하나의 객체로 생성이되어, null이 들어갈 수도, 메소드를 사용할 수도 있다는 점이다
이렇게 2가지 타입을 동시에 사용하는 케이스에 의도치않게 박싱/언박싱 과정에서 불필요한 객체가 만들어진다는 것
가비지 컬렉션
가비지 컬렉션을 공부하는데 있어서 세 가지를 중점으로 알아보라고 했다 - 개념, 옵션들, 툴 여기서 툴이란, gd가 발생할 때 그것들을 모니터링 할 수 있는 그러한 툴을 이야기한다
객체의 사용이 끝나면 가비지 컬렉터가 객체를 수거해간다고 하는데, 어떠한 객체들을 기준으로 수거해가는걸까? -> 객체를 생성하게 되면, 그 객체를 생성한 메소드가 끝나는 순간, 그 객체를 일반적으로 더는 사용하지 않는다 그러면 그렇게 참조를 더 이상 받지 않는 그러한 객체들을 기준으로 가비지 컬렉션의 객체 수거 대상이 된다
가비지 컬렉션에서의 알아야 할 3가지
Mark : 더 이상, 객체가 참조되고 있는지 아닌지에 대한 표시를 해두는 그러한 개념이다. 가비지 컬렉션의 대상이냐 아니냐 이걸 판단하는 지표
Sweep : 필요 없는 객체를 메모리인 Heap에서 날리는 단계
Compact : 객체를 sweep 하기 위해서는 모든 메모리를 훑으면서 마크가 되어 있는 객체를 찾아서 뽑아가야 한다, 근데 이러한 단계는 비효율적이기 때문에 메모리를 한 번 쓱 훑으면서 마크된 객체들을 한 곳으로 몰아두었다가 한꺼번에 수거하기 위해서 객체를 모으는 그러한 개념이다
Young Generation, Old Generation 객체의 생명주기가 긴 건 그렇게 많지 않다 근데 가끔씩 애플리케이션이 돌면서 아주 오랜 시간 동안 남아있는 객체가 존재하긴 하기 때문에 이렇게 2가지의 영역으로 나누었다 Young Generation 에는 Eden, Space0(S0), Space1(S1) 이렇게 나누어져있으며, 이 공간들에서 객체들이 돌아다니게 된다 항상 처음으로는 Eden 으로 가지만, 만약 Eden 공간이 가득 차게 된다면 S0, S1 을 돌아가면서 영역 할당을 해서 채우게 된다 S0와 S1에 대한 순서는 상관없다 이렇게 Young Generation 에서 이리저리 왔다갔다 하다가 지속적으로 살아남는 객체를 확인하면 Old Generation으로 옮겨지는 그러한 순서로 되어 있다
Minor GC, Full GC Young Generation 에서 일어나는 GC가 Minor GC 이며, Young Generation 과 Old Generation 모두에서 일어나는 대규모 GC 가 Full GC 이다 여기서 full gc에는 다양한 종류가 존재한다 옵션처럼 원하는 알고리즘으로 돌아가도록 설정할 수 있는데, 기본적으로 java8 에서는 parallel GC 이다. 사실 serial GC 나 parallel GC 나 방식은 같지만 단지 스레드 차이이다 이외에도 CMS, G1, ZGC, Shenandoah 이렇게 존재하는데 이것들을 공부하면서 확인해야할 사항이 있다 위의 GC들을 공부하면서 바라봐야할 3가지 관점이 있다 -> Throughput, Latency(STW), Footprint
Throughput : 애플리케이션을 처리할 수 있는 처리량을 의미, 서버가 100이 있다고 가정했을 때 가비지 컬렉터는 일정 주기마다 돌지만, 원한다면 100 중에 5 정도만 빼두고 거기에서 GC 의 단계인 Mark-Sweep-Compact 만 돌리는 것이 가능하다 이렇게 되면 throughput 이 95만큼만 적용되는 것이고 각 full gc 에는 다른 throughput 을 가지고 있다는 점을 인지하고 확인하자
Latency(Stop The World) : 애플리케이션에서 GC가 일어나게 되면 애플리케이션이 멈춘다 오직 GC만 작업되고 있으며 모든 나머지 작업은 멈춘 상태이다 그리고 이러한 것을 보고 Stop The World 이라고 한다. 그래서 멈추는 것은 당연하게 좋지 않기 때문에 latency 를 어떻게 하면 최대한으로 줄일 수 있을지에 대한 고민을 해봐야 한다. 위에서 봤던 CMS gc 부터는 latency 가 애플리케이션의 용량과 상관없이 적게 나타난다고 한다 역시 최신
Footprint : 이건 GC 가 돌기 위해서 애플리케이션에서 얼마만큼의 공간을 잡아먹고 있느냐이다 즉, GC 알고리즘이 돌기 위해서 잡아먹는 메모리 양을 의미한다.
ZGC 을 추천해주시네 저 알고리즘이 latency를 많이 고려한 그러한 알고리즘인가 보네요 간단하게 보면 자바 11부터 옵션으로 설정해줄 수 있으며 특징으로는 Low Latency 으로, STW 시간이 10초 미만으로 확인됨 그리고 Scalable 으로, heap 사이즈나 라이브의 사이즈가 커져도 STW 시간이 늘어나지는 않는다 재밌는건 ZGC의 목표는 위의 특징 2가지도 가지고 있었으며 G1 보다 애플리케이션 처리율이 15% 이상 떨어지지 않을 것이였다 아무튼 G1 보다 짧은 Latency 를 가지면서 G1 보다 더 좋은 퍼포먼스를 가지기 위한 목표라는 점이다 언젠간 G1보다 많이 쓰일 날이 오긴 하겠구나...
그래서 객체를 줄이는게 베스트이다? 는 아니고, 객체 지향 언어인데 객체를 잘 사용하라는 의미이지만 그렇다고 줄이기에 급급하면 static 으로만 만들고, 이는 또 메모리에 부담을 주는 일이다 즉, 뭐든 과하지 않고 객체를 많이 많이 사용하되, 정말 필요 없는 케이스에는 사용하지 말라는 의미이다
@Deprecated 만약에 더이상 사용하지 않는 메소드가 있거나 대체제가 나와서 앞으로 사용되지 않을 예정인 그러한 케이스를 가진 메소드에다가 사용하는 애노테이션이다 앞으로 사용되지 않을 예정이거나 오직 과거의 하위호환성을 위해서 남겨둔 그러한 메소드나 필드에 달 수 있다
만약 그렇게 달아두게 되면, 컴파일러로 하여금 -(줄)을 이런식으로 그어지게 된다 이외에도 javadocs에서 @deprecated을 통해서 명시도 가능하다
Last updated
Was this helpful?