목록으로 돌아가기

[Java] Static 변수를 남용하면 안되는 이유 (단점 및 유의점)

학습목표

편리해 보이지만 위험한 Static 변수! Static 변수의 특성을 생각하며, 메모리 구조와 멀티스레드 환경에서의 치명적인 단점을 알아봅시다.

개발을 하다 보면 객체 생성 없이 바로 접근할 수 있는 static 변수의 편리함에 끌릴 때가 있습니다. 하지만 이 편리함 뒤에는 ‘공유’라는 치명적인 특성이 숨어 있습니다.

오늘은 Static 변수와 인스턴스 변수의 결정적인 차이를 알아보고, 왜 Static 변수를 상태 관리에 사용하면 안 되는지 구체적인 예시를 통해 알아보겠습니다.

Static 변수 vs. Instance 변수

가장 먼저 이해해야 할 것은 ‘데이터가 어디에 저장되고, 누구와 공유되는가’입니다. 이 차이가 모든 문제의 시작점입니다.

위험 (공유)
Static 변수

Method Area (클래스 로딩 시 1회 생성)

☁️
단 하나의 공유 공간
🤖 객체 A
👾 객체 B
🎃 객체 C
↘️➡️↗️
📦
실제 값
(모두가 여기를 봄)
안전 (독립)
Instance 변수

Heap Area (객체 생성 시 마다 생성)

📦 📦
객체별 독립 공간
🤖 객체 A
➡️
👜 A 주머니 속 값
👾 객체 B
➡️
🎒 B 주머니 속 값
  • Static 변수 (공용 칠판)
    • 프로그램이 시작될 때 Method Area에 딱 하나만 생성됩니다.
    • 모든 인스턴스(객체)가 이 하나의 공간을 공유합니다.
    • 쉽게 말해, 교실 앞에 있는 ‘공용 칠판’과 같습니다. 누군가 칠판에 낙서를 하면, 반 전체 학생이 그 낙서를 보게 됩니다.
  • Instance 변수 (개인 공책)
    • new 연산자로 객체를 생성할 때마다 Heap Area매번 새로 생성됩니다.
    • 각 객체는 자신만의 독립적인 값을 가집니다.
    • 이는 학생 개개인이 가진 ‘개인 공책’과 같습니다. 내가 공책에 필기를 해도 짝꿍의 공책에는 아무런 영향을 주지 않습니다.


Static 남용 시 발생하는 문제점

“공유한다”는 것은 효율적으로 보일 수 있지만, 값이 수시로 변하는 상황(가변 상태)에서는 문제가 될 수 있습니다. 대표적인 두 가지 문제 상황을 시뮬레이션 해보겠습니다.

1️⃣ 데이터 덮어쓰기 (Overwritting)

"나는 철수를 저장했는데, 왜 영희가 나오지?"

STEP 1
🤖
객체 A
name = 철수 ➡️
공유된 Static 변수
name = "철수"
⬇️
name = "영희"
STEP 2
👾
객체 B
⬅️ name = 영희
🤯 결과: 나중에 객체 A가 확인하면?
→ "철수"는 사라지고 "영희"만 남음.
2️⃣ 동시성 문제 (Race Condition)

동시에 접근해서 계산이 누락되는 문제

시간 흐름 ▼
공유 변수: 0
⚡️ 스레드 A
"현재 값 0 읽음"
STEP 1
STEP 2
⚡️ 스레드 B
"나도 0 읽음!"
⚠️ A 저장 전!
"0+1 = 1 저장"
STEP 3
STEP 4
"0+1 = 1 저장" (덮어씀)
🤯 결과: A가 값을 바꾸기도 전(Step 3)에
B가 옛날 값(Step 2)을 읽어서 둘 다 1을 저장함.

1) 객체 지향의 파괴 (캡슐화 위반)

객체 지향 프로그래밍(OOP)의 핵심은 객체가 자신의 상태를 스스로 관리하고 보호하는 것입니다. 하지만 static으로 변수를 선언하면, 모든 객체가 하나의 변수를 공유하게 됩니다.

위의 예시처럼 객체 A는 자신의 이름을 “철수”라고 저장했지만, 객체 B가 생성되면서 값을 “영희”로 바꾸면, 객체 A의 이름도 강제로 “영희”가 되어버립니다. 이는 객체 간의 독립성을 해치는 결과를 초래합니다.

2) 멀티스레드 환경에서의 Race Condition

웹 애플리케이션(Spring 등)은 기본적으로 멀티스레드 환경입니다. 만약 사용자 정보를 저장하는 변수를 static으로 선언한다면 어떤 일이 벌어질까요?

사용자 A가 로그인하는 도중에 사용자 B가 접속하면, 사용자 A의 화면에 사용자 B의 정보가 노출되는 사고가 발생할 수 있습니다. 스레드들이 공유 자원에 동시에 접근하면서 발생하는 이 경쟁 상태(Race Condition)는 디버깅하기도 매우 어렵습니다.


그럼 언제 Static을 써야 할까? : 올바른 사용법

static은 무조건 나쁜 것이 아닙니다. “공유해도 안전한 데이터”에 사용하면 메모리 효율을 높이고 코드를 간결하게 만들 수 있습니다.

💎 불변의 상수 (Constant)

변하지 않는 공통 값은 Static으로!

🏫
학교 이름 (School Name)
"학생이 100명이든 1000명이든
학교 이름은 하나로 똑같습니다."
public static final String SCHOOL = "JAVA HIGH";
안전한 공유 (Read Only)
👤 객체 고유 상태 (State)

개별적으로 다른 값은 Instance로!

👦
철수
👧
영희
"학생마다 이름, 나이, 성적은
모두 다릅니다."
private String studentName;
private int grade;
철저한 독립 (Capsulation)
  • 상수(Constant): 모든 객체가 공통적으로 사용하며 값이 변하지 않는 데이터는 static final로 선언하여 메모리를 절약하고 안전하게 공유합니다. (예: Math.PI, Integer.MAX_VALUE)
  • 유틸리티 메소드: 객체의 상태(인스턴스 변수)를 사용하지 않고 입력받은 값으로만 처리하는 메소드는 static 메소드로 만드는 것이 효율적입니다. (예: Math.random(), StringUtils.isEmpty())
  • 상태(State): 객체마다 달라져야 하는 데이터(이름, 나이, 잔고 등)는 반드시 인스턴스 변수로 선언하여 캡슐화를 지켜야 합니다.

댓글남기기