자바 성능 튜닝 이야기 (이상민 저) 를 읽고 난 후의 감상평 및 정리를 해보고자 합니다.
어중간한 지식으로 프로그래밍을 하며 List를 선언해도 자동 완성으로 불러지는 아무거나를 사용하고
for 와 if로 하면 프로그램은 만들 수 있다 라는 말도안되는 자만에 빠져 초짜 중의 초짜 포지션을 못 벗어나고 있습니다.
그래서 이런 모습에서 벗어나보고자 똑똑한 선배님들이 본인의 경험을 아주 이쁘게 기록해두신 기술 도서를 읽어보자 라고 생각을 하고
책을 빌릴 겸 찾다가 책 제목과 목차에 이끌려 해당 책을 읽게 되었습니다.
아래는 책을 읽으며 정리할 만한 내용을 정리해두었습니다.
GC와 JVM 내부 동작은 저와 같은 시작하는 사람에게는 어려운 내용이라 좌절하기 전에 덮었습니다. 😂
다음에 더 읽어보도록 하겠습니다!
- 디자인 패턴 (추가 조사 필요)
- Business delegate
- Session Facade
- Data Access Object
- Database에서 커넥션을 하나만 두고(싱글톤), 여러 사용자가 DAO의 인터페이스를 사용하여 필요한 자료에 접근하도록
- Service Locator
- Transfer Object
- Templete Method
- 어떤 작업을 처리하는 일부분을 서브 클래스로 캡슐화해 전체 구조는 바꾸지 않으면서 역할을 수행하도록 하는 패턴
- 실제 동작은 인터페이스를 상속받은 메서드에서 구현하고 인터페이스는 큰 틀만 잡아 주도록 함
- 상속을 통해 부모 클래스의 기능을 확장할 때 사용하는 가장 대표적인 패턴
- 일부는 추상 메서드나 오버라이딩이 가능한 메서드로 구현
- method 1은 public abstract String method1();
- method 2는 public String method2();
- Factory Method
- 객체 생성 처리를 서브 클래스로 분리해 처리하도록 캡슐화하는 패턴
- 객체생성을 직접 하지 않고 하위 클래스가 어떤 객체를 생성할지 결정하도록 위임하는 패턴
- 템플릿 메소드 패턴을 활용 하여 오버라이드된 메서드가 객체를 반환하는 패턴
- 생성자 호출을 다른 메서드를 만들어서 진행
- 이렇게 될 경우 사용자는 생성자 호출의 내용을 몰라도 나오는 결과 만으로 사용 할 수 있음(캡슐화)
- 객체 생성 처리를 서브 클래스로 분리해 처리하도록 캡슐화하는 패턴
- String 쓰기를 자제하자
- StringBuffer : ThreadSafe 하다
- StringBuilder : 단일 스레드에서의 안정성만을 보장한다
- 아래 코드는 새로운 String 객체가 만들어지면서 더해지므로 GC의 대상이 되어 응답 속도와 메모리에 영향을 준다.
String a =""; a += "1"; a += "2";
- 결론
- String은 짧은 문자열을 더할 경우 사용한다
- StringBuffer는 스레드에 안전한 프로그램이 필요할 때나, 개발 중인 시스템의 부분이 스레드에 안전한지 모를 경우 사용하면 좋다. 만약 클래스에 static으로 선언한 문자열을 변경하거나, singleton으로 선언된 클래스에 선언된 문자열일 경우에는 이 클래스를 사용해야만 한다.
- StringBuilder는 스레드에 안전한지의 여부와 전혀 관계없는 프로그램을 개발할때 사용하면 좋다. 만약 메서드 내에 변수를 선언했다면, 해당 변수는 그 메서드 내에서만 살아있으므로, StringBuilder를 사용하면 된다.
- 자바가 발전하면서 String + String 의 성능이 좋아졌다고 한다.
- 디컴파일 하여 확인 필요~
- 속도 측정
- APM 혹은 프로파일링 툴을 사용하면 좋지만 복잡하고 비용이 듦
- System.currentTimeMillis()
- 1970년 1월 1일 부터의 시간을 long 타입으로 반환
- 나노 보다 느림
- 클래스가 로딩되면서 성능 저하도 발생하고
- JIT Optimizer가 작동되면서 성능 최적화도 되기 때문에
- System.nanoTime()
- 실제 시간측정용은 해당 메소드를 사용하는게 더 정확함
- 자료구조 어떤걸 써야하나?
- Sun(자바 만든 곳)에서 추천하는 클래스
- Set - HashSet
- List - ArrayList
- Map - HashMap
- Queue : LinkedList
- Collection
- Set : 중복없는 집합 객체
- HashSet : 데이터를 해쉬 테이블에 담는 클래스로 순서 없이 저장
- TreeSet : red-black 트리에 데이터를 담음, 값에 따라 순서 보장(정렬로 인해서 성능 저하)
- 내구 구현 인터페이스 중 NavigableSet이 있는데 해당 인터페이스는 특정 값보다 큰,작은 값을 추출하는 메서드를 선언 해놓음
- 데이터를 순서에 따라 탐색하는 작업이 필요할 때는 TreeSet을 사용
- LinkedHashSet : 해쉬 테이블에 담지만 저장된 순서에 따라 저장됨
- 데이터 크기를 알고 있을 경우, 객체 생성시 크기를 미리 지정하는 것이 성능상 유리
- Set : 중복없는 집합 객체
- Sun(자바 만든 곳)에서 추천하는 클래스
Set의 동작시간 (문자열 더하기 - 이하 동일)
읽기 평균 동작 시간(ms) : HashSet(32) = LinkedHashSet < TreeSet(841)
저장 평균 동작 시간(ms) : HashSet(375) < LinkedHashSet(378) < TreeSet(1249)
-
-
- List : 중복이 허용되는 집합 객체 (크기가 자동으로 증가되는 배열)
- Vector : 크기를 따로 지정할 필요가 없는 배열 클래스
- ArrayList와 성능 차이가 나는 이유 : 내부의 get() 메서드에 syncronized가 선언 되어 있음
- ArrayList : 벡터와 비슷하지만, 동기화 처리가 되어있지 않음
- LinkedList : 어레이 리스트와 비슷하지만 Queue 인터페이스를 구현했기 때문에 FIFO 큐 작업을 수행한다.
- Queue 인터페이스를 상속 받으므로 인해 동작 속도가 느림
- peek()메서드 와 poll()메서드를 사용하면 빠른 성능으로 사용 가능
- Vector : 크기를 따로 지정할 필요가 없는 배열 클래스
- 삭제에서는 ArrayList 와 Vector가 처음 부터 시작하면 느렸고 마지막 부터 삭제를 진행했으면 빨랐다. LinkedList는 동일
- List : 중복이 허용되는 집합 객체 (크기가 자동으로 증가되는 배열)
-
List 동작시간
읽기 평균 동작 시간 : ArrayList(4) < Vector(105) < LinkedList(1512)
저장 평균 동작 시간 : ArrayList(28) < Vector(31) < LinkedList(40)
-
-
- Queue
- PriorityQueue : 큐에 추가된 순서와 상관 없이 먼저 생성된 객체가 먼저 나오도록 되어있다.
- LinkedBlockingQueue : 저장할 데이터의 크기를 선택적으로 정할 수 있는 FIFO 기반의 링크노드를 사용하는 블로킹 큐
- ArrayBlockingQueue : 저장되는 데이터의 크기가 정해져 있음
- PriorityBlockingQueue : 저장되는 데이터의 크기가 정해져있지 않고, 객체의 생성 순서에 따라 순서가 저장되는 블로킹큐
- DelayQueue : 큐가 대기하는 시간을 지정하여 처리
- SynchronousQueue : put() 메서드를 호출하면, 다른 스레드에서 take() 메서드가 호출 될 때까지 대기하도록 되어 있는 큐, 해당 큐에는 데이터가 저장되어 있지 않다.
- Blocking queue : 크기가 지정되어 큐에 더이상 공간이 없을 시, 공간이 생길 때 까지 대기
- Queue
-
-
- Map : 키와 값의 쌍으로 구성된 객체의 집합 (중복 불허) : 컬렉션 아님
- HashTable : 데이터를 해쉬 테이블에 담는 클래스, 내부에서 관리하는 해쉬 테이블 객체가 동기화 되어있으므로 동기화가 필요하면 해당 클래스를 사용하면 됨
- HashMap ; HashTable과 비슷한데, null을 허용한다는 것과 동기화가 되지 않는 다는 차이점이 있다.
- TreeMap : red-black 트리에 데이터 저장, TreeSet과 다른점은 키에 의해 순서가 정해짐.
- LinkedHashMap : hashmap 과 거의 동일하고 , Double linkedlist 방식을 사용한다.
- Map : 키와 값의 쌍으로 구성된 객체의 집합 (중복 불허) : 컬렉션 아님
Map 동작시간
읽기 평균 동작 시간 : HashMap < LinkedHashMap < Hashtable < TreeMap
- for 루프를 빠르게 하는 방법
- if vs switch
- if : 아주 큰 성능저하가 생긴다고 보기엔 어렵다
- switch : if보다 가독성이 좋기 때문에 사용을 추천하고, 숫자들이(case안의 실행문..?) 정렬되어 있을때 빠르게 실행된다.
- for vs while
- while은 무한루프에 빠질 수도 있기 떄문에 for문을 사용하는 것을 추천한다.
- for 추천 사용법
- list.size()를 for문의 조건으로 넣고 사용하지 않기
int size = list.size(); for(int i=0;i<size;i++)
- For-each는 처음부터 마지막 값까지 사용해야할때 사용하는 것이 좋다.
- if vs switch
- static 사용법
- static을 사용하면 동일한 주소 값을 가진 객체 혹은 클래스가 만들어지기 때문에 조심해야함
- GC의 동작에서 제외됨
- 메모리 릭 발생 가능성 농후
- 잘 사용하는 방법
- 자주 사용하고 절대 변하지 않는 변수는 final static으로 선언
- 여러 객체에서 한번에 접근해도 문제가 없음
- 설정 파일 정보도 static으로 관리하면 좋음
- 코드성 데이터는 DB에서 한번 만 읽기 (잘 변하지 않는 데이터들)
- 생성될때 1회만 실행하는 상황에서 좋음
- 자주 사용하고 절대 변하지 않는 변수는 final static으로 선언
- 클래스 정보 확인
- reflection 클래스
- 클래스
- 메소드
- 필드
- this.getClass().getSimpleName()
- 이 코드는 getClass() 실행시 Class 객체를 만들고 이름을 가져오는 메소드를 사용
- 클래스 이름으로 equals를 사용하기 보단 instanceof를 사용하는 것을 권장
- reflection 클래스
- synchronized 잘 사용하기
- 메소드 및 동작의 동기화를 위해 사용하는 식별자로 특정 부분만 지정할 수 있다.
- 언제 동기화를 사용하는 것이 좋을까? → 해당 경우가 아니면 굳이 사용할 필요 없음
- 하나의 객체를 여러 스레드에서 동시에 사용할 경우
- static으로 선언한 객체를 여러 스레드에서 동시에 사용할 경우
- 중요한 코드라고 해서 & 스레드 사이에서 잘 동작해야한다고 해서 무분별한 synchronized 를 막 쓰지 말자
- 로그 잘 사용하기
- System.out.println() → slf4j 사용
- System.out.println() 은 내용이 완전히 프린트 되거나 저장 될 때 까지 대기 할 수 밖에 없다
- 그러므로 애플리케이션에서는 대기시간이 발생 할 수 밖에 없다 -> 성능 저하!!
- 사용하려면 if(trace == true) 이런식으로 사용해서 쓰는게 좋음
- System.out.println() → slf4j 사용
- 스프링
- Dependency Injection(의존성 주입)
- 어떤 객체가 필요로 하는 객체를 자기 자신이 직접 생성하여 사용하는 것이 아니라 외부에 있는 다른 무언가로부터 필요로하는 객체 주입을 받는 기술이다.
public class A { private B b = new B(); } //잘못된 예
public class A { private B b; public A(B b){ this.b = b; } } // 의존성 주입을 위한 코드
- Aspect Oriented Programming(관점 지향 프로그래밍)
- OOP를 더 OOP스럽게 만들어주는 기술
- 비슷한 코드 블럭을 실제 비즈니스 로직과 분리할 수 있도록 도와주는 기술
- AspectJ 라는 프레임워크를 사용
- Portable Service Abstraction
- 비슷한 기술을 모두 아우를 수 있는 추상화 계층을 제공하여, 사용하던 기술이 변경되더라도 비즈니스 로직의 변화가 없도록 도와준다.
- Dependency Injection(의존성 주입)
- DB
- Connection 과 Connection Pool
- DB를 사용 하여 쿼리문을 실행 시킬 때, 자바코드에서 가장 소요시간이 긴 부분이 Connection 부분이다. (DB와 WAS가 통신해야하기 때문에)
- 그러므로 Connection 객체를 생성하는 부분에서 발생하는 대기 시간을 줄이고, 네트워크의 부담을 줄이기 위해서 사용하는 것이 DB Connection Pool 이다.
- Connection 과 Connection Pool
- 안드로이드에서 자바 성능 개선 (잘 써먹어보기)
- 구글에서 권장하는 방법
- Avoid creating unnecessary objects
- String 대신 StringBuffer 사용
- Integer 배열 대신 int 배열 사용 (오토박싱 때문에 성능저하)
- 다차원 배열 대신 1차원 배열 사용
- Avoid creating unnecessary objects
- 구글에서 권장하는 방법
이렇게 작은 부분부터 신경써서 개발해야 객체가 적게 생성되고 GC도 적게 발생한다
-
-
- Prefer static over virtual
- static을 적절히 사용하자
- 인스턴수 변수에 접근할 일이 없을 경우엔 static메서드를 선언하여 호출하는 것이 15~20%의 성능 개선이 발생 할 수 있다.
- Use static final for constant
- 상수에는 static final을 사용하자
- static으로 선언 할 때, 저장되고 참조되는 위치가 달라진다.
- Static final이 접근 속도가 훨신 빠르다
- Avoid internal getters/setters
- inner class 내부에서는 getter와 setter 사용을 피하자
- 인스턴수 변수에 직접 접근하는 것이 게터, 세터 메서드를 사용하는 것보다 빠르다.
- JIT 컴파일러가 적용 안 될 경우 3배, 될 경우 7배
- Use Enhanced For loop syntax
- 개선된 for (foreach)을 사용하자
- Iterable 인터페이스를 사용하는 대부분의 Collection에서 제공하는 클래스들은 전통적인 for루프를 사용하는 것 보다는 for-each 루프를 사용하는 것이 성능상 유리하다
- 하지만 ArrayList는 전통적인 for루프가 개선된 for문보다 3배 빠르다
- Consider package instead of Private access with Private inner classes
- private한 inner 클래스의 private 접근을 피하자
- Inner class를 감싸고 있는 class에서 private 으로 선언한 변수에 접근하려고 하면 접근이 가능하다
- 이것은 자동으로 private 변수에 접근 할 수 있도록 하는 메서드를 만들어서 사용하므로 성능이 저하 된다.
- Avoid using Floating-point
- 안드로이드 기기에서는 정수 연산보다 소수점 연산이 2배 느리다
- double 보다 float가 2배의 저장공간을 사용한다. float 사용하기
- Know and Use the libraries
- 라이브러리를 알고 사용하자
- ex : 배열 복사시 System.arraycopy() 메서드를 사용하면 루프를 사용하여 복사하는 것보다 9배 이상 빠르다
- Use native methods carefully
- Native 메서드는 유의해서 사용하자
- 안드로이드 NDK를 사용한 Native 코드 호출 할 시, 신중하게 접근해야함
- JIT 컴파일러가 최적화를 못할 수도..
- Prefer static over virtual
-
반응형
'책 읽기 > IT 기술 도서' 카테고리의 다른 글
[이펙티브자바 3/E] 아이템 1,2,3 : 정적 팩토리 메서드,Builder, 싱글턴 패턴 (0) | 2023.07.03 |
---|