Static의 올바른 사용법은?

DongHee Lee
12 min readJun 23, 2020

--

저번 포스팅(Strategy vs Factory Method Pattern in Java)에 이어 PDF 변환 라이브러리를 개발하면서 가졌던 의문중 하나인 ‘Static의 올바른 사용법은?’에 대해 다뤄보고자 합니다.
이번 포스팅은 Static의 정의 와 사용법, 장점 및 단점 그리고 언제 사용해야하는지에 대해 서술하도록 하겠습니다.

목차

  1. 서론
  2. Static 키워드
  3. Static의 사용법
  4. Static의 장점과 단점
  5. Static은 어느상황에 사용해야할까?
  6. 결론

서론

개발할 때 어디서든 편하게 접근하여 사용하기 위해 static 키워드를 사용하여 개발한 적이 있을 겁니다. 무엇보다 new 키워드를 통해 객체를 생성하지 않고도 호출할 수 있어 여러모로 편하다고 느껴 무분별하게 사용하곤 했습니다. 한 번씩 옛날에 했던 프로젝트 코드를 보면 클래스의 필드 변수 중 20%는 static을 사용한 것 같아 보였습니다.

static을 무분별하게 사용했던 과거의 나…

만약 지금과 같은 코드로 개발하여 코드리뷰를 받았다면…

그러나 Java 개념에 대해 좀 더 깊숙히 공부하다보니 static을 필요에 맞게 사용하지 않으면 많은 문제점이 발생한다는것을 알고나서는 최대한 지양을 하고자 했습니다. 그러나 이번 PDF 변환 라이브러리를 개발하면서 static의 올바른 사용법에 다시 한 번 궁금증이 터지게 되었습니다.

아래와 같이 PDF에 암호화를 설정하는 클래스를 생성하여 공통으로 사용하는 메서드를 static으로 설정한다던지

병합한 PDF에 랜덤한 이름을 생성하는 Class를 만들어 공통으로 사용하는 메서드를 static으로 설정한다던지…

PDF를 병합하는 Class를 만들어 공통으로 사용하는 메서드를 static으로 설정하는 등… 전체 코드를 살펴보면서 static이 너무 남발하여 사용되지 않았나? 라는 생각이 들었습니다.

그렇다면 ‘static 키워드는 언제 사용해야 적절한지?’ 그리고 ‘장점과 단점’이 무엇이 있는지 확실히 파악하기 위해 포스팅을하며 정리하였습니다.

Static 키워드

내용에 앞서 static에 대해 개념을 잡고 가보겠습니다.

Java에서 Static 키워드는 주로 메모리에서 관리되어집니다. 즉, 객체 생성을 하지 않고 클래스 변수나 메서드를 호출하여 사용하도록 하는 제어자입니다. Static 키워드를 통해 변수, 메서드, Block 그리고 Nested Class에 적용이 가능합니다.

JVM 메모리 영역 관리

Java 프로그램이 실행되면, JVM(Java Virtual Machine)은 운영체제로부터 프로그램이 필요한 메모리를 할당받습니다. Runtime Data Area를 보면 5가지로 나눠지는데 용도에 따라 관리하기 위해 별도로 공간을 생성하여 관리합니다.

여기서 우리가 눈여겨볼 필요가 있는 공간은 Method Area 입니다. JVM은 클래스 파일을 읽어서 분석한 후 클래스의 메소드, 인스턴스 변수 등 Method Area에 저장합니다. 저장되는 영역은 총 7가지로 분류되는데 그 중 static이 저장되는 공간은 다음과 같습니다.

  1. Field Information
    - Field Type을 저장합니다.
    - Field (public / private / protected / static / final / volatile / transient)
  2. Constructor And Method
    - 생성자를 포함한 모든 메서드를 저장합니다.
    - Method name, return type, parameter, byte
    - Method (public / private / protected / static / final / syncronized / native / abstract)
    - Method의 ByteCode 정보를 저장합니다.
  3. Class Variable
    - Static 키워드로 선언된 변수를 저장합니다.
    - 기본형이 아닌 static 클래스형 변수는 Reference 변수만 저장됩니다.
    - 실제 인스턴스는 Heap에 저장됩니다.
    - 모든 인스턴스에 공유 되며 인스턴스가 없어도 직접 접근이 가능합니다.

그렇다면 static이 아닌 객체들은 어디에 저장이 될 까요? new 키워드를 통해 생성된 객체는 Heap 영역에 생성됩니다. Heap 영역의 메모리는 GC(Garbage Collector)를 통해 사용이 끝나면 자동으로 자원을 해제하도록 도와줍니다.

그러나 Static 영역에 할당된 메모리는 모든 객체에서 공유하기 때문에 Garbage Collector의 관리 영역에서 제외됩니다. 즉, 프로그램의 종료시까지 메모리에 할당된 상태로 존재하기 때문에 Class 객체를 생성하지 않더라도 언제, 어디서든 불러서 사용할 수 있습니다.

Static의 사용법

그렇다면 static 사용법에 대해 간단히 알아보도록 하겠습니다. 이번 예제는 자동차 연비계산 프로그램으로 설명드리겠습니다.

우선 자동차 정보를 저장하는 Model 클래스인 Car를 생성합니다.

다음으로 계산을 수행하는 클래스인 Calculate를 생성합니다.

여기서 연비를 계산하는 메소드인 calcFuelEfficiency는 static으로 선언되었습니다. 따라서 실제로 호출할 때 객체를 생성하지 않고 바로 호출하여 사용하는 것을 볼 수 있습니다.

만약 static을 선언하지 않고 사용한다면 다음과 같이 객체를 선언하여 호출해야합니다.

14번째 라인을 확인하면 Calculate 객체를 생성한 후 calcFuelEfficiency 메소드를 호출하는 모습을 볼 수 있습니다.

Static의 장점과 단점

누구나 코드를 보면 new 키워드 없이 사용할 수 있게 해주는 static이 엄청나게 편하다고 보일 겁니다. 편하다고만 해서 static을 사용해서는 안 되겠죠? 그렇다면 장단점이 무엇이 있는지 같이 확인해보겠습니다.

장점

  1. 코드를 간결하게 작성할 수 있습니다.
    - 위의 예시에서 봤듯이 new 키워드를 통해 객체 생성없이 호출할 수 있기에 코드를 좀 더 간결하게 작성할 수 있습니다.
  2. 어디서든 접근이 가능합니다.
    - 어느 클래스든 static으로 선언된 변수나 메서드를 불러와 사용할 수 있습니다.
  3. GC 오버헤드를 줄여줄 수 있습니다.
    - 만약 공통으로 사용해야하는 메서드를 10,000번 호출한다고 가정하겠습니다. new 키워드를 통해 객체를 생성 후 동작하도록 작성하였다면, 반복되는 동안 객체를 Heap영역에 생성하고 사용이 끝나면 메모리를 해제해야합니다. 그러나 이러한 동작을 10,000번 하게 되면 GC에 오버헤드가 발생됩니다. Static은 Heap영역에 생성 및 해제를 할 필요없이 사용가능합니다.

이번 포스팅의 핵심은 Static의 단점입니다. 단점을 상세히 살펴본 후 이에 맞게 적절하게 사용할 수 있도록 해야합니다.

단점

  1. 무분별한 Static의 사용은 Memory Leak의 원인이 됩니다.
    - Static으로 선언된 모든 것들은 프로그램이 시작될 때 메모리를 할당받습니다. 메모리를 해제하기 위해선 프로그램을 종료해야하는데, 만약 무분별하게 Static을 사용하게 된다면 GC가 메모리 해제를 하지 못해 메모리 부족현상이 발생될 수 있습니다.
  2. 객체지향적이지 않습니다.
    - 많은 개발자들이 static을 악으로 규정하고 있는 이유 중 하나입니다. 객체지향개발요소 중 하나인 캡슐화가 존재합니다. 캡슐화란 한 객체가 가지고 있는 데이터들은 외부에서 함부로 접근하여 수정할 수 없도록 하는 원칙입니다. 그러나 Static을 사용할 경우 어디서든 접근이 가능하기 때문에 캡슐화의 원칙을 위배하게 됩니다.
  3. Static은 재사용성이 떨어집니다.
    - Static으로 선언된 메소드는 Interface를 구현하는데 사용될 수 없습니다. 따라서, 객체지향개발 특징 중 한가지인 재사용성을 살리지 못하기 때문에 객체지향개발에 방해가 됩니다.
  4. Static은 Thread-safe하지 않습니다.
    - Static은 Thread-safe하지 않기 때문에 멀티 스레드 동작에서 충돌이 발생될 수 있습니다.

Static은 어느상황에 사용해야할까?

Static의 단점에서 보았듯이 잘못사용하면 Application이 다운이 되는 문제가 발생될 수 있습니다. 따라서 이러한 문제를 없애기 위해선 어느상황에 사용해야하는지 잘 파악을 해야겠죠?

  1. 클래스 설계시, 공통적으로 사용되는 변수나 메소드는 static을 붙입니다.

위에서 설명한 자동차 연비계산을 예로 다시 들어보겠습니다. 각 차들에 대해 연비를 계산할 때 공통적으로 사용했던 메서드가 있습니다.

public static int calcFuelEfficiency (int mileage, int fule) {
return mileage / fule;
}

이렇게 특정 동작에 대해 공통적으로 처리하는 부분에 대해선 static 키워드를 붙여사용합니다.

또한 Car 메소드에서 다음과 같은 변수를 추가한다고 가정하겠습니다.

Car는 공통적으로 현대 자동차만 생성한다고 했을 땐 companyName을 static으로 두고 공통으로 호출하여 사용할 수 있습니다. 캡슐화 원칙을 지키기 위해서 companyName은 final키워드를 같이선언해주면 좋겠죠?

2. Static 메소드 생성을 고려할 때, 인스턴스 변수를 사용하는지 확인합니다.

이번 포스팅을 준비하면서 새로 알 수 있었던 부분입니다. 어떻게 보면 당연한 내용이지만 직접 생각하지 못했다면 그냥 넘어갈 수 있는 부분인 것 같습니다.

위에서 설명했듯이 Static 메서드는 인스턴스 생성없이 호출이 가능합니다. 그러나 인스턴스 변수는 인스턴스를 생성해야만 값이 존재합니다. 만약 Static 메서드를 호출할 때 생성되지 않은 인스턴스 변수를 사용하면 어떻게 될까요? 당연히 NullPointer문제가 발생되겠죠? 따라서 Static 메소드에선 인스턴스변수의 사용을 허용하지 않습니다.

따라서 메소드 구성을 설계할 때 인스턴스 변수를 필요하는지, 필요하지 않은지를 고려한 후 필요하다면 Static은 사용하지 않습니다. 반대로 인스턴스변수를 사용하지 않으면 Static을 붙이는게 좋습니다. Non-Static 메소드는 호출시 메소드를 찾는 과정이 추가적으로 필요하기 때문에 처리시간이 조금 더 걸립니다.

따라서 Static 메소드를 사용하면 호출시간을 단축시킬 수 있습니다. Static은 클래스가 로딩될 때 한 번만 파싱작업을 하기 때문에, 이를 잘 활용만 한다면 성능을 향상시킬 수 있게됩니다.

결론

다음과 같이 조사하며 PDF변환 라이브러리의 유틸리티 코드 일부를 수정할 수 있었습니다.

PDF암호화 유틸리티

우선 PDF암호화 유틸리티와 관련한 소스코드는 static을 그대로 사용하였습니다. 해당 유틸리티는 Excel, Word, Html 변환뿐만 아니라 병합작업에 공통으로 사용될 수 있기에 인스턴스를 여러번 할당하는 것 보다 하나의 메모리에두고 사용하는것이 효율적이라 생각이 들었습니다.

병합 PDF 랜덤 이름 생성 유틸리티

다음으로 병합한 파일의 랜덤이름을 생성하는 유틸리티입니다. 해당 유틸리티 클래스는 클라이언트가 병합작업을 수행할 때만 호출이 됩니다. 문서변환 특성상 단일 파일변환이 더 많이 발생하기 때문에, 해당 유틸리티는 static으로 메모리를 계속 할당하기보단 인스턴스를 직접 생성하여 GC가 관리하는 게 효율적으로 느껴 Static을 제거하였습니다.

PDF 병합 유틸리티

마지막으로 PDF 병합 유틸리티입니다. 해당 유틸리티 마찬가지로 클라이언트가 병합요청이 있을때만 동작합니다. 자주 사용되지 않을 수 있는데 불필요하게 메모리에 계속 할당되어 있다면 비효율적이라 판단이 되었습니다. 또한, 공통적으로 사용되지 않고 특정 한 작업에서만 호출하여 사용되기 때문에 static을 제거하였습니다.

이렇듯 아무리 유틸리티 클래스라도 조금만 더 생각하면, 무분별하게 static 지시자를 사용하는 것을 방지할 수 있습니다. 이를 위해선 static에 대한 정확한 개념과 사용용도를 잘 파악하고 있어야합니다.

References

when to use static method?

why are static variables considered evil?

--

--