- 힙덤프
- OOM는 메모리 누수 상황이 발생했을 때 일어남
- OOM의 종류
- Java heap space : 힙 공간에 새로운 객체를 생성할 수 없는 경우. 지정한 힙크기가 애플리케이션에 충분하지 않을 경우 발생. 혹은 생명주기가 긴 애플리케이션의 경우. 혹은 finalize를 과도하게 사용하는 애플리에키션에서 발생하기도 함.
- GC Overhead limit exceede : GC에서 자바 프로그램이 느려지는 경우 발생함. GC후 자바 프로세스가 컬렉션 수행하는데 걸리는 시간의 약 98% 이상을 소비하고 힙의 2% 미만이 복구된 상태에서 지금가지 수행하는 과정에서 GC중 OOM이 5번 이상 생성되는 경우 발생.
- 힙의 크기를 늘리면 해결됨. -XX:-UseGCOverheadLimit 선택사항을 추가하여 GC제한 명령을 해제할 수 있음
- Requested array size exceeds VM limit : 요청 배열 크기가 VM 제한을 초과
- 원인 : 애플리케이션이 힙 공간보다 큰 배열을 할당할 때 발생.
- 힙 사이즈가 너무 작거나, 배열 요소를 계산하고 더하는 등의 배열의 크기가 계속해서 늘어나는 경우 발생
- MetaSpace
- 자바 메타데이터(VM에서 자바 클래스)는 원시메모리(메타공간)에 할당된다. 클래스 메타 데이터가 할당될 메타공간이 모두 소모되면 해당 예외 발생함
- 해결 : MaxMetaSpaceSize 값을 늘려 설정한다. 자바힙과 동일한 주소 공간에 할당된다. 자바 힙의 크기를 줄이면 더 많은 공간을 확보할 수 있다. 자바 힙 공간에 여유가 있는 경우 고려 가능
- Out of swap space
- HotSpot VM코드가 네이티브 힙이 고갈되어 네이티브 힙에 할당할 수 없을 경우 발생함. 이 메세지는 실패한 요청의 바이트 크기와 메모리 요청의 이류를 나타낸다. 대개의 경우 할당에 실패한 소스 모듈의 이름을 출력
- 해결방법 : 네이티브 힙 고갈의 경우는 힙 메모리 로그 및 메모리 맵 정보를 분석하는 것이 유용하다. 이런 유형은 운영체제의 문제 해결 유틸리티를 사용하여 문제를 진단할 수 있다.
- Compressed class space(압축된 클래스 공간)
- 64비트 플랫폼에서 클래스 메타데이터 포인트는 32비트 오프셋으로 표현된다. 이 방식은 기본값 활성화로 제어할 수 있으며 활성화되면 클래스 메타데이터가 사용할 수 있는 공간의 크기가 고정된다.
- 해결방법 : CompressedClassSpaceSize 크기를 키우거나, UseCompressedClassPointers를 비활성화 시킨다.
- stack_trace_with_native_method
- 원시 세머드에서부터 스택 트레이스가 출력되었다는 것을 의미하고, 네이티브 메서드에 할당 오류가 발생했음을 의미한다. 이 메세지가 이전 메세지들과 다른 점은 JVM코드가 아니라 Java Native Interface또는 원시 인터페이스에서 할당실패가 감지되었다는 것이다.
- 이 예외가 발생했다면 운영체제가 제공하는 유틸리티를 이용해서 문제점을 진단해야 한다.
- 힙덤프 분석 방법
- 주의점 : 힙 히스토그램을 생성하려면 어플리케이션에 부하가 발생하여 실제 프로덕션에서 절대 실행하면 안됨.
- 명령어 기반
- jmap
- $ jmap -histo [processId] : 죽은 객체까지 포함함. (GC동반하지 않음)
- $ jmap -histo:live [processId] : 풀 GC 발생.
- jcmd
- $ jcmd [processId] GC.class_histogram
- 풀 GC를 발생시켜 살아있는 인스턴스의 점유율만 보여줌
- $ jcmd [processId] GC.class_histogram
- JMX (Java Management eXtension) : 응용프로그램 및 서비스 를 감시관리하기 위한 도구를 제공하는 자바 API
- jmap
- GUI 기반
- MAT(Memory Analyze Tool) : 이클립스 기반 힙덤프 파일을 열어볼 수 있는 도구.
- VisualVM : 오라클 기반 JDK에 포함된 버전, 깃헙에서 제공하는 버전 2가지 존재.
- Java Mission Control : 오라클에서 제공되는 Java Advanced 제품의 일부. 엔터프라이즈 자바어플리케이션의 상태를 실시간으로 모니터링 할 수 있는 툴. 메모리 탭에서 GC갯수를 볼 수 있다.
- 스레드덤프
- JVM에서 현재 활성 상태인 모든 자바 스에드 목록. 스레드의 상태를 정확히 알 수 있음
- 스레드의 상태 : State라는 이름을 가진 Enum타입을 선언됨.
- NEW : 스레드가 생성되었지만 아직 실행되지 않은 상태
- RUNNABLE : 현재 CPU가 점유하고 있는 작업을 수행중인 상태, 자원 분배로 인해 WAITING 상태가 될 수 있다.
- BLOCKED : Monitor를 획득하기 위해 다른 스레드가 락을 해제하기를 기다리는 상태
- WAITING : sleep(), wait(), join(), park() 메서드를 이용해 대기하고 있는 상태
- TIMEWAITING : 차이점은 메서드의 인수로 최대 대기 시간을 명시할 수 있어 외부적인 변화 뿐만 아니라 시간에 의해서 WAITING상태가 해제될 수 있음
- TERMINATED : 스레드가 실행을 마친 상태.
- *모니터 : 멀티스레드에서 공유데이터의 동시접근을 막기 위해 컨트롤 하는 메커니즘. 모든 객체는 monitor를 가지고 있음
- wait : 다른 스레드가 객체에 대한 notify, notifyAll을 호출하기 전까지 대기하는 단계.
- join : 해당 스레드가 종료되기 전까지 기다림.
- 스레드간의 경합(공유자원에 대해서 여러 스레드가 동시에 요구를 할 때 일어나는 현상)과 데드락의 원인을 분석하기 위해서 주로 사용.
- 스레드 덤프 분석 방법
- jstack을 이용하는 방법
- $ jstack {pid}
- jcmd이용
- $ jcmd MyProgram help Thread.print
- Java VisualVM을 이용하는 방법
- kill -3 을 이용하는 방법(리눅스)
- ps -ef | grep java를 통해 pid를 구한다.
- kill -3 {pid} 을 통해 구할 수 있음
- MBean 이용하기
- jstack을 이용하는 방법
- 스레드 덤프의 패턴
- 락을 획득하지 못한 경우(BLOCKED)
- 한스레드가 락을 소유하고 있어 다른 스레드가 락을 획득하지 못해 애플리케이션의 전체적인 성능이 느려지는 경우
- 데드락 상태 (서로의 락을 얻으려고 하는상태
- 스레드 A가 작업을 계속하려면 B의 락을 획득해야하고, B가 작업을 계속하려면 A가 소유한 락을 획득해야 하는 상황
- 원격 서버로부터 메세지 수신을 받기 위해 계속 대기하는 경우
- RUNNABLE 상태에서 문제가 없어 보이지만, socketReadThread가 계속 소켓을 읽으려 무한정 대기하고 있는 상태
- WAIT 상태에 있는 경우
- 스레드 리소스를 정상적으로 정리하지 못하는 경우
- 불필요한 스레드가 계속해서 늘어나는 경우. 스레드 리소스를 정상적으로 정리 못하고 있는 경우여서 각 스레드를 정리하는 모습 혹은 스레드가 종료되는 조건을 확인하는 것이 좋다.
- 락을 획득하지 못한 경우(BLOCKED)
- 스레드 덤프를 이용한 문제 해결 예제
- CPU 사용률이 높을 때 : CPU를 가장 많이 점유하는 스레드가 무엇인지 추출
- 수행 성능이 비정상적으로 느릴 때 : BLOCKED 상태인 스레드가 원인인 경우가 많음.
- DBMS에서 주로 나타나는데, DBCP 등의 설정이 적절하지 못해 충분한 성능을 내지 못하는경우, 이 경우 스레드 덤프를 여러번 얻어서 비교하면 BLOCKED 상태에 있던 스레드 중 몇 개는 다른 상태인 경우가 많을 걸이다.
- DBMS와의 연결이 비정상적인 상태라 계속 타임아웃 시간까지 대기하는 경우. 이 경우에는 스레드 덤프를 여러번 추출해 비교해도 DBMS와 관련된 스레드는 계속해서 BLOCKED 상태에 있다. 타임아웃 값을 적적하게 변경해서 문제 발생 시간을 줄일 수 있음.
- 스레드 덤프의 정보
- 스레드 이름 : 스레드의 고유 이름. Thread 클래스를 이용해 스레드를 생성하면 Thread-N 형식으로 스레드 이름이 생성됨
- ThreadFactory 클래스를 이용했으면 pool-N-thread-N 형식으로 생성됨
- 우선순위 : 스레드의 우선순위
- 스레드 ID : 해당정보를 이용해 스레드의CPU사용,메모리 사용등 확인가능
- 16진수임
- tid : 자바레벨 스레드 id,
- nid : 네이티브 스레드 id, OS 영역이고 유니크함. ps 명령어를 이용해 cpu 사용율 알 수 있음
- 스레드 상태
- runnable
- blocked
- deadlock의 심 가능
- wait
- 콜스택 : 스레드의 콜스택 정보
- 스레드 이름 : 스레드의 고유 이름. Thread 클래스를 이용해 스레드를 생성하면 Thread-N 형식으로 스레드 이름이 생성됨
- 코어 덤프
- 특정 시점에 작업중이던 메모리 상태를 기록한 것.
- JVM이 문제가 생기면 hs_err_pid.log라는 파일을 남기고 죽음.
- 스레드풀
- 스레드를 미리 생성해두고 사용자의 요청을 작업큐에 넣고, 작업큐에서 태스크를 미리 생성해놓은 스레드를에게 할당하는것.
- 사용자로부터 들어온 요청을 작업큐에 넣고 스레드풀은 작업큐에 들어온 Task를 미리 생성해놓은 스레드들에게 할당하고, 다 처리한 스레드
- 기본적으로 코어의 갯수와 동일한 갯수의 스레드를 생성함.
- 병목현상이 발생하는 I/O와 데이터베이스 작업이 주로 해당됨
- (스레드 생성비용 64비트 java8 기준 메모리 1MB 예약할당)
- 적정 스레드풀 갯수 = CPU수 *(CPU 목표 사용량) * (1 + 대기시간 / 서비스 시간)
- 종류
- newFixedThreadPool : 주어진 스레드 갯수만큼 생성. 그 수를 유지함. 일부가 종료되면 다시 생성.
- ExecutorService executor = Executors.newFixedThreadPool()
- newCachedThreadPool : 처리할 스레드가 많아지면, 그만큼 스레드를 증가시킴 (최대 : Integer.MAX_VALUE)
- newSingleThreadExecutor : 스레드를 하나만 생성
- newScheduledThreadPool : 특정 시간 이후, 또는 주기적 작업 스레드 사용시 활용
- newFixedThreadPool : 주어진 스레드 갯수만큼 생성. 그 수를 유지함. 일부가 종료되면 다시 생성.
- 사용법
- Executors로 ExeturosService를 생성하였다면, ExecutorService.submit()를 통해 작업을 추가할 수 있다.
- SingleThreadExecutor : 스레드가 1개인 Executor.시퀀셜하게 처리함.
- Future : 예약된 작업에 대한 결과를 알 수 있음.
- 목적(장점)
- 매번 발생하는 작업을 병렬처리 하기 위해 스레드를 생성/수거 할때의 오버헤드(어떤 처리를 할 때 드는 간접적인 시간, 메모리 비용)를 줄일 수 있다.
- 다수의 사용자 요청을 처리하기 위해서
- 단점
- 너무 많은 스레드를 풀에 만들어 놓으면 놀고있는 스레드로 인한 메모리가 낭비되는 상황 발생 가능
- (이에대한 해결책으로 자바에서는 ForkJoinPool을 지원함)
- 데드락 발생 위험.
- 너무 많은 스레드를 풀에 만들어 놓으면 놀고있는 스레드로 인한 메모리가 낭비되는 상황 발생 가능
- 단점 개선 : Java7에서 ForkJoinPool를 Work Stealing 알고리즘을 통해 구현하였음. 스레드풀에서 Fork(나눠서)Join(합친다)ThreadPool이 사용됨 (스레드가 노는 시간을 최대한 줄이기 위한 시도)내부적으로 WorkStealing알고리즘 구현
- Work Stealing Algorithm
- 병렬처리를 위해 전체 작업 목록을 관리하는 작업큐를 사용하면, 작업큐에 접근하는 것 자체가 경쟁이므로 성능 저하 발생할 수 있다.
- 따라서 일정한 갯수의 스레드를 유지하면서, 스레드마다 독립적인 작업큐를 관리하여, 하나의 스레드 큐가 비게 되면 다른 스레드에서 task를 훔쳐올 수 있게 한다.
- 작업을 하나의 큰 작업들로서 제공함
- 첫 스레드가 작업을 가져와 자신의 로컬 큐에 할당 분할한다.
- 로컬큐에서는 덱(양쪽 끝으로 넣었다 뺐다 할 수 있는 구조)으로 구성됨. 각 쓰레드는 한쪽끝에서만 일을함. (스택처럼) 하지만 나머지 한쪽 끝에서는 잡을 스틸하러온 다른 스레드가 접근하게 됨.
- 두번째 스레드가 가져올 작업이 없다면, 첫 스레드의 큐에 있는 분할된 작업을 훔쳐간다. 나머지 스레드도 반복한다.
- 100 -> 50 -> 25 -> 25 정도의 작업이 수행됨.
- 모든 스레드가 일을 종료하는 시간이 비슷해짐.
- Work Stealing Algorithm
- 사용법
- 생성
- ExecutorService구현 객체는 Executors 클래스의 다음 두가지 메소드 중 하나를 이용해 간편하게 생성가능
- 초기 스레드 수 : ExecutorService 객체가 생성될 때 기본적으로 생성되는 스레드 수
- 코어 스레드 수 : 스레드가 증가한 수 사용되지 않은 스레드를 스레드 풀에서 제거할 때 최소한으로 유지해야할 수
- 최대 스레드 수 : 스레드풀에서 관리하는 최대 스레드 수
- ExecutorService구현 객체는 Executors 클래스의 다음 두가지 메소드 중 하나를 이용해 간편하게 생성가능
- 종료
- 스레드풀에 속한 스레드는 비데몬 스레드이기 때문에 main() 메소드가 실행이 끝나도 애플리케이션 프로세스는 종료되지 않는다. 따라서 스레드풀을 강제로 종료시켜 스레드를 해제시켜줘야 한다. (메모리 릭 발생 가능)
- ExecutorService 구현객체에서는 3개의 종료 메소드를 제공함
- shutDown() : 작업큐에 남아있는 작업까지 모두 마무리 후 종료
- showdownNow() : 작업 잔량 상관없이 강제 종료.
- awaitTermination : timeout 시간안에 처리하면 true리턴, 처리하지 못하면 작업스레드들을 interrupt 시키고 false 리턴
- ForkJoinPool : 큰 태스크를 작은 태스크로 쪼개고 각기 다른 코어에서 병렬적으로 처리후 결과를 취합하는 방식. 내부적으로 Work Stealing 알고리즘이 구현되어 있음. (Devide and Conquer과 흡사.)
- 인터페이스 (Devide and Conquer 활용 재귀적으로 테스크를 쪼개고 합치는 방법). 다른스레드와의 작업 부하의 균형을 맞춤.
- ForkJoinPool는 두가지 방법 제공
- RecursiveAction 클래스
- 반환값이 없는 작업을 구현할 때 사용함
- RecursiveTask 클래스
- 반환값이 있는 작업을 구현할 때 사용함
- 공통점
- compute()라는 추상 메소드 제공.
- task를 분할하는 로직, 더이상 분할할 수 없을 때 subtask의 결과를 생산하는 로직 두개로 나뉜다
- compute()로 재귀적으로 작업을 나누고 fork()로 작업큐에 넣는 작업이 계속 반복된다.
- fork() : 해당 작업을 스레드 풀의 작업 큐에 넣는다. 비동기적으로 실행됨.
- join() : 해당 작업의 수행이 끝날 때 까지 기다렸다가, 수행이 끝나면 그 결과를 반환함. 동기 메서드
- compute()라는 추상 메소드 제공.
- RecursiveAction 클래스
- ForkJoinPool는 두가지 방법 제공
- 생성
- 서블릿이란
- 클라이언트의 요청을 처리하고 그 결과를 동적으로 생성하여 클라이언트에게 전송하는 자바 프로그램
- CGI(Common Gateway Interface) : 웹서버와 프로그램 사이에 데이터를 주고 받는 규칙
- 역사 : 1990년 초기 정적인 데이터를 전달하는 것만으로도 충분
- 웹이 발달하여 입력을 받아 처리하고, 그 결과를 화면에 보여주는 동적인 페이지가 필요하게 됨.
- Java Servlet
- 프로세스 단위로 실행되던 것을 부하를 줄이기 위해 더 작은 단위인 스레드로 부하를 줄이기 위해 탄생
- 톰캣이 이해할 수 있는 순수 자바 코드로만 이뤄진 웹서버용 클래스. 이를 통해 CGI 프로그래밍 가능.
- 스레드 단위로 실행되어 서버의 부하를 줄이고 추상화 시켜줌
- 하지만 자바를 사용해서 동적으로 String으로 HTML태그를 생성해야 하는 불편함이 있었음
- JSP Java Server Page
- HTML표준에 따라 작성되므로 웹페이지 작성이 편리해짐. 서비스가 요청되면, JSP의 실행을 요구하고 웹서버의 톰캣의 서블릿 컨테이너에서 서블릿 원시코드로 변환.
- 서블릿 컨테이너 : 서블릿의 생명주기를 고나리하고 요청에 따른 스레드를 생성해준다.
- 통신 지원 : 서블릿과 웹서버가 통신할 수 있는 손쉬운 방법을 제공함. 소켓을 만들고 특정 포트를 리스닝 하고 연결 요청이 들어오면 스트림을 생성해서 요청을 받는다.
- 생명주기 관리 : 서블릿 컨테이너가 기동되는 순간 서블릿 클래스를 로딩해서 인스턴스화 하고 초기화 메서드를 호출하고 요청이 들어오면 적절할 서블릿 메소드를 찾아서 호출함.
- 멀티스레딩 관리 : 서블릿 컨테이어는 요청이 들어오면 멀티스레딩 환경에서 동시적으로 ㅈ가업을 관리한다.
- 톰캣이 이에 해당함.
- 웹서버와 연동하여 실행할 수 있는 자바 환경을 제공하여, JSP와 서블릿을 실행할 수 있는 환경을 제공함.
- HTTP 서버 자체도 내장하기도 함.
- 서블릿의 생명주기
- init -> service -> doXXX -> destory -> 서블릿 종료
- 서블릿 클래스 객체가 실행되면 init이 실행되고 클라이언트 요청이 들어올 때 마다 서비스 메소드가 실행되어 요청에 맞는 메소드가 실행된다.
- 서버가 종료되거나 서블릿이 필요ㅇ벗어져 객체를 메모리에서 제거할 때, 메소드 destory()가 실행됨.
- init -> service -> doXXX -> destory -> 서블릿 종료
- MVC패턴
- 모델 1
- 뷰와 로직을 모두 JSP 페이지 하나에서 처리하는 구조
- 장점 : 구조 단순하고 쉬움
- 단점 : 출력을 위한 뷰로직과 비지니스 로직을 위한 코드가 섞여 유지보수가 어렵고 코드가 복잡해짐
- 모델 2
- JSP페이지와 서블릿(컨트롤러), 로직(모델)을 위한 클래스가 나뉘어 브라우저 요청을 처리함
- 서블릿은 브라우저 요청에 맞게 그 결과를보여줄 JSP 페이지로 포워딩 함.
- 포워딩을 통해 컨텍스트를 받은 JSP페이지 결과를 화면을 클라이언트에 전송한다.
- 모든 요청을 단일 진입점. 하나의 서블릿에서 처리함.
- MVC
- 모델 : 비지니스 로직을 담당. 컨트롤러에 의해 로직 처리 요청이 들어오면 수행하고 결과를 반환한다.
- 뷰: 클라이언트에 출력되는 화면
- 컨트롤러 : 어플리케이션의 모든 흐름 제어를 맡고 사용자의 처리 요청은 컨트롤러에 집중됨.
- 커맨드 패턴 : 하나의 명령어를 하나의 클래스에서 처리하도록 구현하는 패턴.
- 실행된 기능을 캡슐화 하여 주어진 여러 기능을 실행할 수 있는 재사용성이 높은 클래스를 설계하는 패턴.
- 모델 1
- JDBC
- 자바에서 데이터베이스에 상관없이 DBMS에 접근하기 위해 사용되는 API
- 인터페이스와 드라이버로 나눠짐.
- 커넥션 풀
- JSP 페이지를 실행할 때 마다 커넥션을 생성하고 닫는데 오버헤드가 있어 동시접속자가 많으면 전체 성능이 낮아질 수 있음.
- JSP필터란?
- 클라이언트가 서버로 요청할 때 서블릿으로 전달되기 전, 후에 필터링 하기 위한 기술
- 사용 예
- 로그인 여부나 권한 검사
- 캐싱필터
- 로그기록
- 데이터압축, 변환
- 인코딩 처리
- 공통기능 수행
*동시성 : 멀티 태스킹을 위해 싱글코어에서 멀티 스레드가 번갈아가며 실행하는 성질. 스레드가 코어보다 많은 경우. 동시에 실행되는 것 같이 보이는 것. 싱글코어와 멀티코어 모두에서 가능함. 논리적 개념
*병렬성 : 프로세스를 각각 수행함. 프로세서 하나에 코어 여러개가 달림 . 멀티 태스킹을 위해 멀티코어에서 개별 스레드를 동시에 실행하는 성질. 실제로 동시에 여러 작업을 처리하는 것. 멀티코어에서만 가능함. 물리적 개념
코어수 기준으로 보면 됨.
*멀티코어 프로그래밍 : 하나의 작업을 위해 여러개의 CPU 코어를 사용하기 위해 코드를 작성하는 작업
멀티프로세스 프로그래밍 : 여러 CPU코어를 사용하기 위해 코드를 작성하는 작업. fork를 통해 프로세스를 복사한다.
멀티 스레드 프로그래밍. 하나의 프로세스에서 여러개의 스레드를 생성하여 여러 CPU 코어를 사용하기 위해 코드를 작성하는 방법.
하이퍼스레딩 : 하나의 코어를 논리적으로 두개 이상의 코어처럼 동작하도록 설계한 기술
컨텍스트 스위칭
CPU가 어떤 하나의 프로세스를 실행하고 있는 상태에서 인터럽트 요청에 의해 다음 우선 순위의 프로세스가 먼저 실행되어야 할 때, 기존의 프로세스 상태 또는 레지스터 값을 저장하고 CPU가 다음 프로세스를 수행하도록 새로운 프로세스의 상태 또는 레지스터 값을 교체하는 작업
동시에 실행할 수 있는 스레드 수는 코어수와 동일하다.
www.wrapuppro.com/programing/view/jAuG3VNBCbGnQWU
honeymon.io/tech/2019/05/30/java-memory-leak-analysis.html
d2.naver.com/helloworld/1326256
'회고' 카테고리의 다른 글
스프링 스터디 5주차 때 공부할 것들 (1) | 2021.04.28 |
---|---|
자바 4주차 스터디 회고 (0) | 2021.04.27 |
스프링 스터디 3주차 회고 (0) | 2021.04.21 |
스프링 스터디 1주차에 공부한 것들 (0) | 2021.04.13 |