Java ‐ Java 애플리케이션 성능 튜닝 - dnwls16071/Backend_Summary GitHub Wiki
❗Intellij - VisualVM 연동
(1). Intellij MarketPlace에서 VisualVM Install
(2). 플러그인 설치 후 JDK Path와 VisualVM을 설치한 디렉토리로 간 후 visualvm 프로그램 경로 입력
-
Indicator 1. Memory(메모리)
- Heap Memory Usage
- Non-Heap Memory Usage
-
Indicator 2. Garbage Collection(가비지 컬렉션)
- Collection Count
- Collection Time
-
Indicator 3. Threads(쓰레드)
- Thread Count
- Deadlocked Threads
- Thread States
-
Indicator 4. CPU & System(CPU 및 시스템)
- Process CPU Load
- Open File Descriptor Count
- static 영역은 필요한 경우에만 사용하고 불필요한 경우라면 클래스를 호출하지 않도록 처리해야 한다.
- static 변수는 프로그램이 종료될 때까지 메모리에 계속 남아있다. 이 변수가 더 이상 사용되지 않는 객체를 계속 참조하고 있다면 그 객체와 관련된 모든 메모리 공간이 해제되지 않아 메모리 누수가 발생하게 된다.
- 하나의 클래스 안에 편하다는 이유로 몸집을 부풀리게 된다면 불필요하게 메모리를 소모할 수 있게 되니 불필요한 로직은 제거하고 별도로 분리할 수 있는 내용이라면 분리하는 것을 추천한다.
✅ Singleton 패턴
// 싱글톤 패턴 미적용
public class DateUtil {
public static String getNow() {
SimpleDateFormat fmt = new SimpleDateFormat("yyyyMMdd");
return fmt.format(new Date());
}
}// 싱글톤 패턴 적용(단, 싱글톤 패턴의 경우 테스트 코드를 작성하기 어렵다는 단점이 있으니 이 점 주의하자.)
public class DateUtil2 {
private static String date; // 싱글톤 패턴 적용
public static String getNow() {
if(date == null) {
SimpleDateFormat fmt = new SimpleDateFormat("yyyyMMdd");
date = fmt.format(new Date());
}
return date;
}
}- 용량이 큰 File을 자주 읽게 될 때 가급적 파일을 분할 후에 읽도록 기능을 구현하는 것이 좋다.
- 최대 메모리가 적을수록 GC가 자주 일어나고 최대 메모리가 클수록 GC가 적게 일어난다.
- GC가 자주 일어나게 되면 CPU 성능을 더 쓰게 되는 것이고 프로그램 실행 속도가 느려지게 된다.
- 2중 for문 대신 Map을 사용한다.
✅ 중복 필터링 로직 예시
public class Main {
public static void main(String[] args) {
long start = System.currentTimeMillis();
List<Integer> list1 = ListUtil.getList(1, 500000);
List<Integer> list2 = ListUtil.getList(5000, 7000);
List<Integer> duplList = new ArrayList<>();
Map<Integer, Object> map = new HashMap<>();
for(Integer n2 : list2) {
map.put(n2, "");
}
for (Integer n1 : list1) {
if(map.get(n1) != null) {
duplList.add(n1);
}
}
System.out.println(duplList.size());
long end = System.currentTimeMillis();
long diff = end - start;
System.out.println("성능(ms): " + diff);
}
}- MyBatis에서 foreach문을 통해 대량의 Insert 시 한 번의 쿼리로 처리할 수 있게 된다.
- 그러나 트랜잭션이 처리할 수 있는 쿼리의 용량에는 제한이 있다.
- 하나의 트랜잭션이 너무 많은 쿼리를 담거나 너무 오랫동안 실행하면 여러 문제가 발생할 위험이 있다.
(1). 락과 동시성 문제 측면
- 만약 하나의 트랜잭션이 너무 많은 데이터를 수정하거나 오랜 시간 동안 실행되면 락이 걸린 데이터가 많아지고 락이 유지되는 시간도 길어지게 된다.
- 이로 인해 다른 작업들이 해당 데이터를 사용하지 못하고 기다리는 병목 현상이 발생해 전체 시스템 성능이 크게 저하될 수 있다. 최악의 경우 여러 트랜잭션이 서로의 락이 해제되기를 기다리는 교착 상태에 빠질 수 있다.
(2). 로그 파일 용량 측면
- 데이터베이스는 트랜잭션의 변경 사항을 로그 파일로 기록한다. 이는 문제가 발생했을 때 데이터를 복구하기 위함이다.
- 트랜잭션의 규모가 크면 클수록 기록해야 할 로그의 양도 많아지게 된다. 이로 인해 로그 파일이 저장되는 디스크 공간이 가득 찰 수 있으며, 이는 데이터베이스 시스템 전체의 장애로 이어지게 된다.
(3). 메모리 사용량 측면
- 트랜잭션이 처리하는 데이터는 데이터베이스 서버의 메모리에 임시로 저장된다.
- 거대한 트랜잭션은 많은 메모리를 사용하게 되어 서버 메모리 부족을 유발할 수 있다. 이는 다른 프로세스에 영향을 주어 서버 전체 성능을 떨어뜨리는 원인인 된다.
- SpringBoot Starter Caching
- The Spring Framework provides support for transparently adding caching to an application.
- At its core, the abstraction applies caching to methods, thus reducing the number of executions based on the information available in the cache.
- The caching logic is applied transparently, without any interference to the invoker.
- Spring Boot auto-configures the cache infrastructure as long as caching support is enabled by using the @EnableCaching annotation.
- 개발환경에서 사용할 경우라면 자세한 디버깅을 위해 debug 모드로 사용하는 것을 추천한다.
- 그러나 운영환경에서 사용할 경우라면 info 모드로 변경하고 운영에 올리는 것을 권장한다.(속도 이슈)
- 과도한 로깅, 특히 요청마다 상세한 DEBUG 로그를 남기면, 디스크 I/O와 처리 오버헤드가 증가해 애플리케이션의 응답 시간이 길어지고 전반적인 성능이 저하될 수 있다.
- 프로세스에 속한 모든 쓰레드들의 상태를 기록한 것으로 발생된 문제들을 진단, 분석하고 JVM 성능을 최적화하는데 필요한 정보들을 보여준다.
- Heap 영역은 객체 인스턴스들이 위치하는 영역이다. 덤프 파일은 운영중인 애플리케이션의 힙 메모리 영역을 스냅샷으로 기록한 내역을 저장한 파일을 말한다.