Elasticsearch를 처음 설치하다 보면 항상 보는 에러들이 몇 가지 있습니다.

 1) root 계정으로 실행할 수 없으며

 2) openfiles, max process 값을 수정해야하고

 3) swappiness 값 설정 변경 요청도 있습니다.

Elasticsearch 실행 시 발생한 Max user processes Error

그 중에 elasticsearch openfiles max process는 최소 65535로 설정하라며, 커널에서 해주는 기본 설정보다 큰 값을 요구하는데요,

오늘은 해당 파라미터가 정확히 어떤 것을 가르키는지 그리고 설정된 값 이상이 되면 어떤 일이 일어나는지 알아보겠습니다.

 

 

1. Max user processes

Max user processes의 의미는 하나의 계정에서 최대로 실행할 수 있는 process의 개수를 말합니다. OS에서의 확인은

# ulimit -a (sofe ulimit)

# ulimit -aH (hard ulimit)

위 명령으로 확인 가능합니다. (현재 제 서버의 설정 값은 아래와 같습니다.)

max user processes

그럼 아래 테스트 코드를 통해 설정된 max user processes의 값을 넘기면 어떻게 되는지 확인해보겠습니다.

4000개 Thread 생성 후 유지

대략적인 코드의 내용은 HTTP 요청이 오면 비동기로 4천개의 Thread를 동시에 생성하고 20분간 유지하는 코드입니다.

그리고 실제로 Thread를 늘려보면,

Thread 생성 중 OOM 발생

그림처럼 ulimit -a 명령의 max user processes 결과와 같이 ec2-user 계정은 1024개까지 스레드를 생성한 후, OOM 에러 메세지를 발생하며 멈춰버리게 됩니다. (이 경우 ec2-user 계정에서는 쉘 명령어 입력 등 어떤 작업도 동작하지 않습니다.)

<리눅스에서는 스레드와 프로세스를 동일하게 봅니다.>

 

 

2. Open files

다음은 Open files입니다. 이 값은 프로세스가 가질 수 있는 최대 파일의 개수를 의미합니다.

리눅스는 모든 프로세스나 세션(소켓)들을 파일로 관리하기 때문에 이 값의 설정은 서버에서의 Connection연결 작업에 매우 중요한 내용이 됩니다.

(설정 값을 넘어가게 되면 리눅스에서 파일을 열 수 없어 프로세스 실행이나 Connection 연결이 안되기 때문!)

그럼 소켓을 open files 값 보다 많이 만들어 테스트를 해보겠습니다.

 

(1) Java 환경 TEST

 - RestTemplate로 소켓 생성 요청을 보내고, 요청을 받은 서버에서 20분간 응답을 대기 시킵니다.

 - 요청을 동시에 1100개를 보내 open files 1024로 제한인 서버에 에러가 발생하면 테스트 성공입니다.

   (기본적으로 java, spring 관련 file이 몇 개 오픈됩니다.)

아래는 테스트 코드 일부입니다.

connection 소켓 생성 후 응답 대기 Code

그리고 동시에 요청을 보낼 API 요청 스크립트를 작성합니다.

API 요청 쉘

쉘을 실행하여 테스트를 진행하면

openfiles 개수가 1024 값을 넘어 1516개 까지 열림!

위 그림처럼 소켓이 1024개가 넘어도 에러 메시지 없이 동작하는 것을 볼 수 있습니다. 그럼 한번 더 스크립트를 실행해보겠습니다.

2048개 까지 생성된 모습

이제 에러가 발생했습니다. 파일은 딱 2048개 까지만 열린 것을 확인할 수 있는데요, openfiles의 값은 soft로 설정된 값이 아닌

그림처럼 Hard 옵션 값까지 file이 생성된다는 것을 알 수 있습니다.

그런데…! 알고 보니 java 환경에서는 hard 값을 따라가는게 맞지만 Python에서는 아니라고 합니다

 

(2) Python 환경 TEST

빨간 박스 : 1024개의 열린 파일 생성 / 파란 박스 : 1024개 이후 파일을 하나 더 열어본다

위 처럼 파이썬 스크립트로 file을 임의로 열어봅니다. 1021개 까지는 파일이 문제없이 열리지만(stdin, stdout, stderr 표준 입/출력 3개 제외)

이후로 여는 파일에서는 Too many open files 에러가 발생합니다!

 

(3) Java Python 환경의 차이?

테스트 결과 Java hard 옵션까지 파일이 오픈되고 Python soft 값까지 오픈됨을 알 수 있었습니다. 그 이유를 찾기 위해 strace 명령을 통해 java 프로세스 동작을 추적해보면

kernel 5.15ver에서 테스트 (이전 버전은 prlimit64 함수 대신 getrlimit, setrlimt 함수 사용)

위 그림처럼 java 프로세스가 시작됨과 동시에 prlimit64라는 함수가 rlim_cur=1024값을 rlim_max=2*1024(hard limit) 값까지 업데이트하는 로그가 찍혀있는 것을 볼 수 있었습니다.

JDK에서 MaxFDLimit 옵션 활성화 (기본값)

그 이유는 설치된 JVM에서 MaxFDLimit 옵션을 통해 limit을 올려주었기 때문이라고 합니다! (기본적으로 활성화된 옵션)

, 리눅스 OS에서는 JDK 실행 시 커널에서 prlimit64 함수를 통해 자동으로 limit 사이즈를 증가시켜준다는 것을 알 수 있었습니다.

 

 

3. 정리

 - (Linux 환경) JAVA에서 동시에 생성 가능한 쓰레드 수는 Max user processes를 따라간다.

 - (Linux 환경) JAVA에서 소켓 통신(API나 모든 Connection) open files 옵션을 따라간다.

  -> JAVA에서는 JDK 코드에서 자동으로 soft limit값은 hard limit값까지 업데이트 됨

  -> Python soft limit 값에서 제한이 걸림

 

나의 개발 환경에 맞게, Max user processes open files의 적절한 값을 찾아 설정하는 것이 중요하겠습니다!

 

 

 

<공동저자>

https://giraffe-lee.tistory.com/7

 

<참조>

https://techblog.woowahan.com/2569/

자바는 OS에 독립적인 특징을 가지고 있다.
JVM이 OS와 프로그램의 사이에서 기계어로 행석앻주는 역할을 하기 때문이다.

 

JVM JAVA Virtual Machine

컴파일된 바이트 코드를 기계가 이해할 수 있는 기계어로 변환

스택기반의 가상 머신 

메모리 관리와 GC를 수행

 

자바 코드의 실행 과정

1. 개발자가 자바코드를 작성한다. 

2. ( .java ) 인 파일을 자바 컴파일러를 통해 자바 바이트 코드로 컴파일한다.

3. 컴파일 된 바이트 코드를 JVM의 Class Loader에 전달한다. 

4. Class Loader는 동적 로딩을 통해 필요 클래스들을 로딩 및 링크하여 Runtime Data Area에 올린다. (JVM의 메모리)

5. 실행 엔진은 JVM메모리에 올라온 바이트 코드들을 명령어 단위로 하나씩 가져와서 실행한다.

 

 

 

 

 

JVM
Class Loader + Runtime Data Areas + Execution Engine

 

 

 

 

Class Loader 특징

1. 계층 구조 

클래스 로더는 여러 클래스 로더끼리 부모-자식 관계를 이루어 계층적인 구조로 되어 있다.

 

  • 부트스트랩 클래스 로더 Bootstrap Class Loader
    • 최상위 클래스로더, 유일하게 JAVA가 아닌 네이티브 코드로 구현
    • JVM이 실행될 때 같이 메모리에 올라감
    • Object 클래스를 비롯하여 JAVA API들을 로드함

 

  • 익스텐션 클래스 로더 Extension Class Loader
    • 기본 JAVA API를 제외한 확장 클래스들을 로드함 - 다양한 보안 확장 기능 로드

 

  • 시스템 클래스 로더 System Class Loader
    • (부트 스트랩과 익스텐션 클래스로더가 JVM 자체의 구성 요소들을 로드한다) 시스템 클래스 로더는 어플리케이션의 클래스들을 로드함
    • 사용자가 지정한 $CLASSPATH 내의 클래스들을 로드함

 

  • 사용자 정의 클래스 로더 User-Defined Class Loader
    • 어플리케이션 사용자가 직접 코드 상에서 생성하여 사용하는 클래스 로더
    • WAS와 같은 프레임워크는 서로 독립적으로 동작하게 하기 위해서 이를 위해 사용자 정의 클래스 로더들을 사용하여 클래스 로더의 위임 모델을 통해 어플리케이션의 독립성을 보장함

 

2. 위임모델 

처음 바이트 코드를 넘겨받은 클래스로더.

필요한 클래스를 로드할 때 혹은 실행 엔진에서 명령어 단위로 바이트 코드를 실행하다가 처음으로 참조하는 클래스에 대해 클래스 로더에게 로드를 요청할 때.

로드를 요청받은 클래스 로더는 다음 순서대로 요청받은 클래스가 있는지 확인함

  1. 클래스 로더 캐시
  2. 상위 클래스 로더
  3. 자기 자신

이전에 로드된 클래스인지 클래스 로더 캐시를 확인한다.

없다면 상위 클래스 로더를 하나씩 거슬러 올라가며 확인하는데, 이 때 올라가는 도중에 클래스를 발견하더라도 부트 스트랩 클래스 로더(최상단 로더)까지 확인을 해서 부트 스트랩 클래스 로더에도 해당 클래스가 존재한다면 그 클래스를 로드하게 된다.

 

 

3. 가시성 제한

클래스 로더가 클래스 로드를 요청받았을 때 위임 모델에 의해서 클래스 로더 캐시를 확인하고 없으면 상위 클래스 로더를 확인한다. 이때 하위 클래스는 확인이 불가능한 특성이 바로 가시성 제한이다.

 

 

4. 언로드 불가

클래스를 로드하는 것은 가능하지만 그 반대로 unload하는 것은 불가능하다.

 

5. 이름 공간

네임 스페이스는 각 클래스 로더들이 가지고 있는 공간으로 로드된 클래스를 보관하는 공간

위임 모델을 통해서 상위 클래스 로더들을 확인해야할 때 확인하는 공간이 바로 이름 공간

네임 스페이스에 보관되는 기준은 FQCN Fully Qualified Class Name 으로 패키지 명까지 포함되어있는 식별자를 뜻한다.

 

각각의 클래스 로더가 각자 네임 스페이스를 가지고 있기 때문에 FQCN이 같은 클래스라도 네임스페이스가 다르면 다른 클래스로 갖주한다. ( 이 기능을 통해 로드한 클래스 로더를 제거하고 언로드의 효과를 줄 수 있다. )

 

 

클래스 로드 과정 

 

1. 로드 - 클래스 파일을 가져와서 JVM의 메모리에 로드한다.

2. 검증 - 클래스 로드 전 과정 중에서 가장 복잡하고 시간이 많이 걸리는 과정으로, 읽어들인 클래스가 자바 언어 명세 및 JVM 명세에 명시된 대로 구성되어 있는지 검사한다.

3. 준비 - 클래스가 필요로 하는 메모리를 할당한다. 

4. 분석 - 클래스의 상수 풀 내 모든 심볼릭 레퍼런스를 다이렉트 레퍼런스로 변경한다.

5. 초기화 - 클래스 변수들을 적절한 값으로 초기화한다. (static 필드들은 설정된 값으로 초기화 )

 

 

 

 

 

 

런타임 데이터 영역 Runtime Data Area

JVM이 OS 위에서 실행되면서 할당받는 메모리 영역이 바로 런타임 데이터 영역이다.

이 영역은 크게 5가지, 세분화시 6가지로 나눠 볼 수 있다. 

PC 레지스터, JVM 스택, 네이티브 메서드 스택은 스레드마다 하나씩 생성되고 

힙, 메서드 영역은 모든 스레드가 공유해서 사용된다.

 

 

  • PC 레지스터 
    • Program Counter 레지스터는 현재 수행 중인 명령의 주소를 가지며 스레드가 시작될 때 생성되며, 각 스레드마다 하나씩 존재함
  • JVM 스택 
    • 스택 프레임이라는 구조체를 저장하는 스택
    • 예외 발생 시 printStackTrace( ) 메서드로 보여주는 Stack Trace의 각 라인 하나가 스택 프레임을 표현함
    • 스레드가 시작될 때 생성되며 각 스레드마다 하나씩 존재함
  • 네이티브 메서드 스택
    • 자바 외의 언어로 작성된 네이티브 코드를 위한 스택
    • JAVA Native Interface를 통해 호출하는 C/C++ 등의 코드를 수행하기 위한 스택으로, 언어에 맞게 스택 생성
    • 인스턴스 또는 객체를 저장하는 공간
    • Garbage Collection의 대상
    • JVM 성능 등의 이슈에서 가장 많이 언급되는 공간
    • 힙 구성 방식이나 가비지 컬렉션 방법 등은 JVM 벤더들의 재량
  • 메서드 영역 
    • 모든 스레드가 공유하는 영역
    • JVM이 시작될 때 생성된다.
    • JVM이 읽어 들인 각각의 클래스와 인터페이스에 대한 런타임 상수 풀, 필드와 메서드에 대한 정보, static 변수, 메서드의 바이트 코드들을 보관함
  • 런타임 상수 풀
    • JVM 동작에서 가장 핵심적인 역할을 수행하는 곳
    • 각 클래스와 인터페이스의 상수 뿐만 아니라, 메서드와 필드에 대한 모든 레퍼런스까지 담고 있는 테이블
    • 어떤 메서드나 필드를 참조할 때 JVM은 런타임 상수 풀을 통해 해당 메서드나 필드의 실제 메모리상 주소를 찾아서 참조한다.

 

 

 

 

 

실행 엔진 Execution Engine

실행 엔진은 클래스 로더를 통해 런타임 데이터 영역에 배치된 바이트 코드를 명령어 단위로 읽어서 실행

 

바이트 코드와 각 명령어는 1바이트 크기의 OpCode와 추가 피연산자로 이뤄져있음

실행 엔진은 하나의 OpCode를 가져와서 피연산자와 작업을 수행한 다음 OpCode를 수행하는 식으로 동작한다.

 

이 수행 과정에서 실행 엔진은 바이트 코드를 기계가 실행할 수 있는 형태로 변경한는데 두가지 방법을 사용한다.

  • 인터프리터 
    • 바이트 코드 명령어를 하나씩 읽어서 해석하고 실행한다. 하나하나의 해석은 빠르지만 전체적인 실행 속도는 느리다는 단점을 가진다. 
    • JVM 안에서 바이트 코드는 기본적으로 인터프리터 방식으로 동작한다.
  • JIT 컴파일러 Just-In-Time
    • 인터프리터의 단점을 보완하기 위해 도입된 방식
    • 바이트 코드 전체를 컴파일하여 네이티브 코드로 변경하고, 이후에는 해당 메서드를 더이상 인터프리팅 하지 않고 네이티브 코드로 직접 실행하는 방식
    • 전체적인 실행 속도는 인터프리팅방식보다 빠르다 ( 네이티브 코드는 캐시에 보관하기 때문에 한 번 컴파일된 코드는 캐시에서 바로 꺼내어 실행해 빠르게 수행된다 )
    • 하지만 JIT 컴파일러가 컴파일하는 과정은 바이트 코드를 하나씩 인터프리팅 하는 것보다 훨씬 오래 걸리기 때문에 JVM은 해당 메서드가 얼마나 자주 호출되고 실행되는 지를 체크하고, 일정 기준을 넘었을 때만 JIT 컴파일러를 통해 컴파일 하여 네이티브 코드를 생성한다.

 

 

 


참조

'🔥 > JAVA' 카테고리의 다른 글

[JAVA] 객체 지향 프로그래밍(OOP)이란?  (0) 2021.08.11
[JAVA] 자바가상머신, JVM이란?  (0) 2021.08.11
[JAVA] HashMap과 HashTable의 차이점  (0) 2021.08.04
[JAVA] Hash란?  (0) 2021.08.04

생성자의 목적

1. 힙 메모리 영역에 클래스 필드를 생성해주는 목적

2. 초기화 역할

 

메서드의 모양 :  name ( )

생성자의 모양 : class name ( )

 

위처럼 생성자도 메서드와 생김새가 비슷하네?

생성자도 메서드다

그렇다면 왜 생성자는 메서드라고 하지 않을까?

 


 

생성자는 메서드의 기능과 똑같지만 return이 없기 때문에 메서드라고 부르지 않는다.

 


클래스를 만드는 순간 굳이 생성자를 일부를 만들어주지 않아도 기본 생성자라는 것이 생기는데, 보이진 않지만 내부적으로 올라간 것이다.

따라서 선언없이 사용이 가능하다.

 

기본 생성자

1. 클래스 선언 시 자동으로 생성된다.

2. 사용자가 직접 선언하지 않아도 사용 가능하다.

3. 사용자가 직접 생성자를 선언하는 순간 그것을 기본 생성자로 여겨서 따로 기본 생성자가 생기진 않는다.

 


< 변수 >

매개변수 : { } 안, 닫는 중괄호를 만날 때 끝난다

매개 변수와 지역변수는 stack에 저장이된다.

전역 변수는 data 영역에 저장된다.

 


오버 로딩 Overloading

매개변수의 개수나 타입이 다를 때, 같은 이름으로 선언할 수 있다.

         →  메서드의 이름은 같으나 매개변수의 갯수 혹은 타입이 다르면 선언 가능

load : 나갔다가 다시 불러올 때

over : 넘치게

overload : 넘치게 불러온다 (같은 이름이라) 

 

《 오버 로딩은 메서드의 첫 번째 기능 ≫

 


Car와 Road를 만들어보자

 

package studyalone;

public class Car {
	//Car 클래스는 브랜드, 색, 가격, 비번을 갖고 있다
	String brand;
	String color;
	int price;
	String pw="1122";
	
	//생성자 , 비번 새로
	public Car(String b, String c ,int p,String pw) {
		this.brand=b; this.color=c; this.price=p;this.pw=pw;
	}
	
	//생성자 , 초기 비번 그대로
	public Car(String b, String c ,int p) {
		this.brand=b; this.color=c; this.price=p;
	}
	
	//외부에서 비밀번호 입력받기
	//입력받은 비밀번호와 자동차의 비밀번호를 비교하기
	//비밀번호가 일치한다면 시동 켜주기
	//이미 시동이 켜져있다면 "시동이 이미 켜져있습니다"출력
	//이미 시동이 꺼져있다면 "시동이 이미 꺼져있습니다"출력
	//비번 3회 오류 시 경찰 출동
	
	boolean isOn=false;
	int policeCnt;
	
	//시동키는 메서드
	boolean engineStart(String pw ){
		boolean policeCheck=false;
		
		if(this.pw.equals(pw)) {
			if(!isOn) {
				System.out.println(this.brand+" 시동 킴");
				isOn=true;
				policeCnt=0;
			}else {
				System.out.println(this.brand+" 시동이 이미 켜져있음");
			}
		}else {
			policeCnt++;
			if(policeCnt==3) {
				System.out.println("경찰 출동");
				policeCheck=true;
			}else {
				System.out.println("비밀번호 오류");
			}
		}
		return policeCheck;
	}
	
	
	//시동끄는 메서드
	void engineStop(){
		if(!isOn) {
			System.out.println(this.brand+" 시동 끔");
		}else {
			System.out.println("시동이 이미 꺼져있습니다.");
		}
	}
	
	
	//자동차 정보 출력 메서드
	void show() {
		System.out.println(brand+", "+color+", "+price+"만원");
		//지역변수가 같은 이름이 아니라서 굳이 this.brand등등으로 하지 않은 것
	}

	
}
package studyalone;

import java.util.Scanner;

public class Road {
	public static void main(String[] args) {
		Car myCar=new Car("벤틀리","blue",1500,"981122");
		
		String menu= "1. 시동 켜기\n2. 시동 끄기";
		Scanner sc =new Scanner(System.in);
		int choice=0;
		String tryPw="";
		
		myCar.show();
		
		//무한반복 
		//시동을 한번이라도 킨 후 시동을 끄면  break
		while(true) {
			System.out.println(menu);
			
			choice=sc.nextInt();
			
			if(choice==1) {
				System.out.println("비밀번호를 입력하세요");
				tryPw=sc.next();
				
				if(myCar.engineStart(tryPw))
					break;
			}else if(choice==2) {
				//시동을 한번이라도 켜기 전엔 isOn이 false니까 break안함
				//한번이라도 시동을 겼다면 isOn은 true니까 break함
				if(myCar.isOn) {
					myCar.engineStop();
				}else {
					myCar.engineStop();
				}
			}
		}
		
	}
}

 

alt shift n 새로운 프로젝트 만들기
ctrl n 새로운 클래스 만들기 ( 해당 프로젝트의 src 선택 후 )
alt ㅁ 프로젝트, 클래스, 인터페이스에서 해당 이니셜로 선택할 수 있음
ctrl shift + 글씨 키우기
ctrl shift - 글씨 줄이기
ctrl m 전체화면
insert 쓰는것이 겹쳐지면서 지워지게하는 커서
alt shift s o 생성자 만들기
alt shift r 전체적으로 이름 바꾸기 ( class 이름도 )
ctrl space바 자동완성
ctrl z 뒤로가기
ctrl s 저장
ctrl x 저장하고 해당 영역 삭제
ctrl t 가족 관계도
ctrl shift o 자동 import
ctrl shift f 줄 맞춤
alt 위 아래 해당영역 위 또는 아래로 이동
ㅊ한자 제곱근이나 1/2같은 기호 사용

 


 

전역 변수는 data 영역에 저장된다.

전역변수는 자동 초기화가 된다.

 


%f 를 쓰면 기본적으로 소수점 6자리가 나온다.

float는 4바이트이자 32비트다.

 

3.7이란 숫자를 float로 저장한다고 하면

1bit 8bit 23bit
양수면 0 / 음수면 1 exp 진수 영역 f 가수 영역
3.7은 양수니까 0 3.7의 정수는 3이니까 0011

3.7의 0.7이 가수영역이니까 이진수로 교환 // 가수영역 * 2 한 값이 1보다 크면 1을 넣고 (계산값-1)*2를 해준다. 1.0이 되는 순간까지 무한 반복을 하거나 24비트에서 반올림 된다.

0.7*2=1.4 ---> 1써짐 ---> 1.4-1=0.4

0.4*2=0.8 ---> 0써짐

0.8*2=1.6 ---> 1써짐 ---> 1.6-1=0.6

           . . .

0 0000 0011 10110...

 

이런 식으로 비트가 반올림되는 컴퓨터 연산의 오류가 있다.

 

그래서 7자리 까진 정확하지 않고 기본적으로 6자리까지 정확하다.

따라서 %f 는 기본적으로 6자리까지 출력이 되는 것이다.

 


eclipse를 처음 깔면 perspective를 java로 선택이 되어있는지 확인해야 한다.

java로 선택해놓으면 package explorer이 켜지는 것이다.

+ Recent posts