플라이웨이트 패턴이란?
플라이웨이트 패턴은 많은 수의 유사한 객체를 효율적으로 관리하기 위한 구조적 디자인 패턴입니다. 객체의 내부 상태를 공유함으로써 메모리 사용을 최적화합니다. 코드를 보면서 이해해봅시다.
재사용 가능 여부
플라이웨이트 패턴은 자원의 재사용 여부를 먼저 따져봐야합니다. 같은 자원을 공유해도 문제가 없는 데이터를 재사용함으로써 메모리를 절약할 수 있으니까요.
예제로 전쟁시뮬레이션 게임을 만든다고 가정해보겠습니다. 하나의 객체를 만들때 텍스쳐, 사운드, 폴리곤, 객체의 좌표가 사용된다고 해보겠습니다.
플라이웨이트 패턴 적용 전
먼저 메모리 사용량을 표시하기 위해 메모리 클래스를 하나 만들겠습니다.
public class Memory {
public static int size = 0;
public static void print() {
System.out.println("메모리 사용량 : " + size + "MB");
}
}
public class Soldier {
private final int size = 10;
private final String texture;
private final String sound;
private final int polygons;
private double positionX;
private double positionY;
public Soldier(String texture, String sound, int polygons, double positionX, double positionY) {
this.texture = texture;
this.sound = sound;
this.polygons = polygons;
this.positionX = positionX;
this.positionY = positionY;
// 메모리 사용량 증가
Memory.size += size;
}
}
public class Field {
// 지형 타일 크기
public static final int CANVAS_SIZE = 1000;
public void render(String texture, String sound, int polygons, double positionX, double positionY) {
Soldier soldier = new Soldier(texture, sound, polygons, positionX, positionY);
System.out.println("병사 생성 : x : " + positionX + ", " + " y : " + positionY);
}
}
public static void main(String[] args) {
Field field = new Field();
for (int i = 0; i < 1000; i++) {
field.render(
"robe.png",
"solider.wav",
200,
Field.CANVAS_SIZE * Math.random(),
Field.CANVAS_SIZE * Math.random()
);
}
Memory.print(); // 메모리 사용량 : 10000MB
}
Solider 1000명을 Field에 rendering하는 코드를 간단하게 작성해보았습니다. 이때 무려 메모리를 10000mb나 사용되었네요. 만약 병사의 수가 증가한다면 기하급수적으로 메모리를 사용할 것입니다. 이 코드를 플라이웨이트 패턴을 이용해 메모리 사용량을 줄여보겠습니다.
플라이웨이트 패턴 적용 후
public class Soldier {
private final int size = 2;
private SoliderModel model;
private double positionX;
private double positionY;
public Soldier(SoliderModel model, double positionX, double positionY) {
this.model = model;
this.positionX = positionX;
this.positionY = positionY;
// 메모리 사용량 증가
Memory.size += size;
}
}
public class SoliderModel {
private final int size = 8;
private final String texture;
private final String sound;
private final int polygons;
public SoliderModel(String texture, String sound, int polygons) {
this.texture = texture;
this.sound = sound;
this.polygons = polygons;
Memory.size += size;
}
}
Solider에서 재사용이 가능한 텍스쳐, 사운드, 폴리곤의 수를 SoliderModel 클래스로 새로 만들었습니다.
메모리 사용량은 SoliderModel = 8, Solider = 2 로 분배되었습니다.
public class SoliderModelFactory {
private static final Map<String, SoliderModel> cache = new HashMap<>();
public static SoliderModel getModel(String texture, String sound, int polygons) {
String key = texture + sound + polygons;
return cache.computeIfAbsent(key, k -> new SoliderModel(texture, sound, polygons));
}
public static void clear() {
cache.clear();
}
}
Factory를 만들고 한번 만들었던 모델데이터는 이곳에서 재사용됩니다.
public class Field {
// 지형 타일 크기
public static final int CANVAS_SIZE = 1000;
public void render(String texture, String sound, int polygons, double positionX, double positionY) {
SoliderModel model = SoliderModelFactory.getModel(texture, sound, polygons);
Soldier soldier = new Soldier(model, positionX, positionY);
System.out.println("병사 생성 : x : " + positionX + ", " + " y : " + positionY);
}
}
필드에서 SoliderModelFactory.getModel 로 모델데이터를 가져와 Solider를 만들어 렌더링합니다.
public static void main(String[] args) {
Field field = new Field();
for (int i = 0; i < 1000; i++) {
field.render(
"robe.png",
"solider.wav",
200,
Field.CANVAS_SIZE * Math.random(),
Field.CANVAS_SIZE * Math.random()
);
}
Memory.print(); // 메모리 사용량 : 2008MB
SoliderModelFactory.clear();
}
메모리 사용량을 출력해본 결과 2008MB 가 나왔습니다. 플라이웨이트 적용전보다 무려 80% 감소되었습니다. 반복이 많아질 수록 경량화될겁니다.
주의점
렌더링이 끝났다면 factory를 초기화해줄 필요가 있습니다. 관리되고있는 인스턴스는 GC에서 제거하지 않기 때문에 메모리 누수가 발생할 수 있습니다. 참고로 이런 캐싱방식은 StringConstantPool과 같은 개념이라고 할 수 있습니다.
[JAVA] String의 불변성(immutability)
들어가며JAVA를 처음 접할 때, String은 불변객체라고 배우고 지나갔습니다. 하지만 실제 String을 다루다보면 어째서 불변객체인지 의문이 들곤합니다. String str = "apple"; str = "banana";왜냐하면, 재할
tmd8633.tistory.com
String Constant Pool과 String 불변성에 대해서 더 알고싶다면 해당 게시물을 읽어보시는걸 추천드립니다.
마치며
플라이웨이트 패턴은 메모리 사용량을 크게 줄일 수 있는 강력한 도구입니다. 하지만 모든 상황에서 적합한 것은 아니며, 다음과 같은 상황에서 고려해볼 만합니다:
- 애플리케이션에서 많은 수의 유사한 객체를 사용할 때
- 객체의 상태를 내부와 외부로 명확히 구분할 수 있을 때
- 메모리 사용량이 중요한 제약 조건일 때
'디자인패턴 > 구조' 카테고리의 다른 글
자바(JAVA) - 복합 패턴(Composite Pattern) (0) | 2024.12.05 |
---|---|
자바[JAVA] - 퍼사드 패턴(Facade Pattern) (0) | 2024.11.09 |
자바[JAVA] - 데코레이터 패턴(Decorator Pattern) (0) | 2024.10.02 |
자바(JAVA) - 브릿지 패턴(Bridge Pattern) (0) | 2024.09.30 |
자바(JAVA) - 어댑터 패턴(Adapter Pattern) (0) | 2024.09.24 |