본문 바로가기
개발/Java

JAVA 성능 향상 시키기

by 방구쟁이 2021. 8. 11.
728x90

 개발을 하면 고민을 많이 하게 된다.

 과연 내가 작성한 코드는 효율성이 좋은가? 어떻게 하면 효율적인 코드를 작성할 수 있을까?

 물론 알고리즘과 자료구조를 잘 선택하여 개발하면 된다. 하지만 그 밖에 또 무엇이 있는지 알아보고 있다면 어떻게 해야하는지 공부해보았다.


1. String 대신 StringBuilder와 StringBuffer도 생각해보자.

일반적으로 사용하는 쿼리문 작성 방법

String query = "";
query += "select * ";
query += "from ( ";
query += "select column1, ";
query += "column2, ";

...

 

이를 다음과 같이 StringBuilder를 사용하면 응답 시간과 메모리 사용량을 줄일 수 있다.

StringBuilder query = new StringBuilder();
query.append(" select * ");
query.append(" from ( ");
query.append(" select column1, ");
query.append(" column2, ");

...

 

왜 그럴까?

String은 +로 값을 더하게 되면 새로운 String 클래스의 객체가 만들어지고, 전 객체는 필요가 없어지므로 GC의 대상이 된다. 이렇게 GC 작업을 하면 할수록 시스템 CPU를 많이 사용하게 되므로 응답 시간이 길어진다.

StringBuffer 클래스와 StringBulider 클래스에서 제공하는 메서드는 동일하다. StringBuffer는 스레드에 안전하게 설계되어 있어 멀티 스레드에서 문제가 되지 않으나 StringBuilder는 단일 스레드에서 안정성만 보장한다고 한다. 따라서 스레드에 안전하길 원하면 StringBuffer, 스레드 관계없거나 메서드 내에 변수를 선언했다면 StringBuilder를 사용하자.

 

2. for문의 length와 size를 변수로 만들어주자.

일반적인 경우

for (int i = 0; i < list.size(); i++) {
	// 내용 
}

 

반복문 밖에 변수를 만들어 list.size() 메서드의 호출을 줄여주자.

int listSize = list.size();
for (int i = 0; i < listSize; i++){
	// 내용
}

 

3. 분기문의 적잘한 사용 및 단축 평가를 생각해보기

 1) 자바 연산자의 우선순위 생각하기

 2) 단축 평가 : 컴파일러가 검사할 필요가 없다는 것을 다음 연산을 무시하고 넘어가는 것.

if( 조건1 && 조건2 && 조건3 && 조건4 )

 예시로 위의 경우 조건1이 false이면 나머지 2,3,4번의 조건의 결과와 상관없이 false이기 때문에 연산을 생략하고 넘어간다.

if( 조건1 || 조건2 || 조건3 )

 마찬가지로 이번엔 조건1이 true면 나머지 값과 관계없이 true이기 때문에 2,3번 조건을 연산하지 않고 넘어간다.

 단축 평가의 단점 : 단축 평가가 좋은 점만 있는 것은 아니다. 테스트할때 모든 조건을 생각해주어야 하는데 단축 평가로 인해 뒤에 조건을 정확히 테스트할 수 없다.

 

 3) 분기문의 가독성 높이기

 이런 식으로 작성하면 처음보는 사람이 코드를 분석할때 좀 더 수월할 것이다.

if ( 조건 ) {
    간단하고 긍정적인 내용
} else {
    복잡한 내용
    ...
    ...
    ...
}

 

 

 

4. 디자인 패턴 사용하기

많은 디자인 패턴이 있으며 참고해보자. 디자인 패턴 중에서 성능과 가장 관련있는 패턴은 Service Locator 패턴이다. 추가로 애플리케이션 개발 시에는 Transfer Object 패턴도 함께 확인하자.

Service Locator 패턴

 서비스를 구현한 클래스는 숨긴채로 어디에서나 서비스에 접근하도록 한다. 서비스가 누구인지, 어디에 있는지 몰라도 된다. 서비스 제공자는 서비스 인터페이스를 상속받아 구현하고, 서비스 중개자는 제공자의 자료형과 등록과정은 숨긴채 적절한 서비스 제공자를 찾아 중개해준다. 

 그러나 의존성을 숨기기 때문에 컴파일 중 오류가 나타나지 않고 대신 런타임에서 오류를 발생시킨다. 이전 코드와 호환이 되지 않도록 코드를 변경한 경우에는 어떤 부분에 의존성이 있는지 명확하지 않아 새로운 코드를 작성할 때마다 코드를 유지보수하는 일이 더욱 어려워진다고 한다.

 따라서 개발할때 사용하기 적절한지 고민해보고 적용하자.

Transfer Object 패턴

 우리가 흔히 알고있는 VO(Value Object), DTO(Data transfer object)이다. 서버에 데이터를 전송하거나, 파일로 객체를 저장할 경우 해당 인터페이스를 구현해야한다.

 추가로 Transfer Object를 생성할 때에는 getter, setter 메서드 뿐 아니라 toString 메서드를 구현하면 Junit 기반에서 테스트 시 데이터 확인에 좀 더 유용하다.

public String toString(){
	StringBuilder sb = new StringBuilder();
    sb.append("id").append(id)
    .append("email").append(email);
    
    return sb.toString();
}

 

 

5. Static과 GC의 관계 생각하기

static을 사용해 생성한 매서드나 변수는 동일한 주소의 값을 참조한다. 또한 GC(garbage collection)의 대상도 되지않는다.

프로젝트로 돌아가 자주 사용하며 변하지 않는 변수를 final static으로 선언하고, 설정 파일, 코드성 데이터들을 static으로 관리하자.

*주의*  만약 계속 늘어나는 데이터인 Collection 객체들을 static으로 선언하면 해당 객체는 GC를 하지 않아 메모리를 가득 잡아먹지 않게 조심하자.

 

6. synchronized의 잘 알고 사용하기

public synchronized void hello(){
  ...
}

public void hello2() {
  synchronized(obj) {
    ...
  }
}

 

언제 사용할까?

하나의 객체를 여러 스레드에서 동시에 사용할 경우

static으로 선언한 객체를 여러 스레드에서 동시에 사용할 경우

 

Synchronized의 동작 방법

 자바 모니터에서 스레드들이 '상호 배제 프로토콜'에 참여할 수 있도록 돕는다고 한다. 자바 모니터의 상태는 잠김, 풀림 중 하나이며 모니터 안에서는 단 하나의 스레드만 작업을 하기 때문에 Synchronized 블록에서의 작업을 마치면 다른 대기중인 스레드로 넘어간다.

 추가로 JDK 5부터는 -XX:+UseBiasedLocking 이라는 옵션을 통해 biased locking 기능을 제공한다고 한다. 이 옵션을 사용하면 스레드가 자기 자신을 향해 bias가 되고 많은 비용이 드는 인스트럭션 재배열 작업을 통해 작업을 수행할 수 있다. (진보된 적응 스피닝 - adaptive spinning 기술을 사용하여 처리량을 개선시켜 동기화 성능은 보다 빨라졌다고 한다.)

 

7. BufferdReader 고려해보기

자바는 Stream을 통해 입출력이 이루어진다. 자바에서 파일을 읽고 처리하는 작업을 IO라고 하는데 입력은 모두 java.io.InputStream 클래스로부터 상속받았다. (출력은 java.io.Reader)

BufferdReader 클래스도 FileRead 클래스처럼 문자열 단위나 문자열 배열 단위로 읽는 기능을 제공하지만, 추가로 readLine() 메서드로 라인 단위로 읽을 수 있다. BufferdReader를 사용하면 FileReader보다 더 빠른 응답속도를 가질 수 있다.

 

8. 로그를 최대한 줄이자.

의미없는 디버그용 로그를 print하기 위해 서버의 리소스와 디스크가 낭비될 수 있다. 따라서 운영 서버의 소스에 있는 시스템 로그를 제거하는게 좋다. 하지만 필요한 경우가 꼭 있다. 그럴때는 로그 처리 여부를 추가하자.

if (logger.isLoggable(Level.INFO)) {
  // 로그 처리 내용
}

 

이렇게 로그 처리를 위한 불필요한 메머리 사용을 줄여 효율적으로 메세지를 처리할 수 있다. 

또한 로그를 깔끔하게 처리해주는 slf4j LogBack을 사용해주자. slf4j를 이용하면 문자열 사이에 {}를 넣고 데이터들을 콤마로 구분해준다. 이렇게 하면 로그를 출력하지 않을 경우 문자열을 더해주는 연산을 하지 않는다.

 

마무리

 이상 자바 소스를 조금이나마 성능을 향상시켜보기 위한 노력으로 공부해보았다. 앞으로는 조금 더 신경써서 코드를 작성해보자.

 


오늘 성장에 도움을 주신 개발자분  

https://jeong-pro.tistory.com/138

https://edykim.com/ko/post/the-service-locator-is-an-antipattern/

 

https://sungjk.github.io/2019/03/28/java-performance-tuning-1.html

https://sungjk.github.io/2019/03/30/java-performance-tuning-2.html

오늘도 감사합니다. 

java 로고

 

728x90

'개발 > Java' 카테고리의 다른 글

[다시 공부하는 Java] Stream API  (0) 2023.04.18
java.util.Optional<T> 이란?  (0) 2021.12.22
[Java] StringTokenizer란?  (0) 2021.08.02
.class와 .java 확장자  (0) 2021.07.21
JVM(Java Virtual Machine) 이란?  (2) 2021.06.24

댓글