반응형
반응형

 

병렬 작업 처리가 많아지면 스레드 개수가 무한정으로 증가하고 CPU가 바빠지며 메모리 사용량이 늘어난다.

따라서 애플리케이션의 성능이 급격하게 저하되게 된다.

 

스레드 풀

작업 처리에 사용되는 스레드를 제한된 개수 만큼 미리 생성해서 작업 큐에 쌓여지는 작업들을 하나씩 스레드가 맡아서 처리한다. 제한을 걸기 때문에 무한정으로 증가하지 않으며 서버가 뻗지 않게 된다.

 

코어 스레드 수  

최소 스레드 수 (60초 동안 추가된 스레드가 아무 작업 하지 않으면 놀고 있는 스레드가 존재해 없애야한다.)

 

스레드풀의 스레드는 기본적으로 데몬 스레드가 아니기 때문에 main 스레드가 종료되더라도 계속 실행 되므로

반드시 종료시키려면 스레드 풀을 따로 종료시켜야한다.

 

 

 

반응형
반응형
package runnable_jar;

public class PrintThread extends Thread {
	@Override
	public void run() {
		while (true) {
		}
	}
}
package runnable_jar;

import java.util.Map;
import java.util.Set;

public class InterruptExample {

	public static void main(String[] args) {

		PrintThread printThread = new PrintThread();
		printThread.setName("Print Thread");
		printThread.setDaemon(true);
		printThread.start();
		
		Map<Thread, StackTraceElement[]> map = Thread.getAllStackTraces();
		Set<Thread> threads = map.keySet();
		for(Thread thread : threads) {
			System.out.println("Name : " + thread.getName() + ((thread.isDaemon() ? " 데몬" : "주")));
			System.out.println("\t" + "소속그룹 : " + thread.getThreadGroup().getName());
			System.out.println();
		}
	}
	/** 기본 스레드 그룹 **/
	// system : JVM 운영에 필요한 스레드를 포함
	// main : 메인 스레드를 포함 (Print Thread는 Main Thread의 데몬 스레드이기 때문에 main 소속)
	
//	Name : Notification Thread 데몬
//	소속그룹 : system
//
//	Name : main주
//		소속그룹 : main
//	
//	Name : Signal Dispatcher 데몬
//		소속그룹 : system
//	
//	Name : Common-Cleaner 데몬
//		소속그룹 : InnocuousThreadGroup
//	
//	Name : Finalizer 데몬
//		소속그룹 : system
//	
//	Name : Print Thread 데몬
//		소속그룹 : main
//	
//	Name : Reference Handler 데몬
//		소속그룹 : system
//	
//	Name : Attach Listener 데몬
//		소속그룹 : system


}

 

 

 

 

package runnable_jar;

public class PrintThread extends Thread {

	// 스레드 그룹 정해주는 생성자
	public PrintThread(ThreadGroup threadGroup, String threadName) {
		super(threadGroup, threadName);
	}
	
	@Override
	public void run() {
		while (true) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				System.out.println(getName() + " interrupted");
				break;
			}
		}
		System.out.println(getName() + "종료됨");
	}
	

}
package runnable_jar;

public class ThreadGroupExample {

	public static void main(String[] args) {

		ThreadGroup myGroup = new ThreadGroup("myGroup"); // main - myGroup (계층도)
		PrintThread printThreadA = new PrintThread(myGroup, "printThreadA"); // main - myGroup - printThreadA
		PrintThread printThreadB = new PrintThread(myGroup, "printThreadB"); // main - myGroup - printThreadB
		
		printThreadA.start();
		printThreadB.start();
		
		System.out.println("[main 스레드 그룹 list() 메소드 출력 내용]");
		ThreadGroup mainGroup = Thread.currentThread().getThreadGroup();
		mainGroup.list();
		System.out.println();
		

		
//		[main 스레드 그룹 list() 메소드 출력 내용]
//				java.lang.ThreadGroup[name=main,maxpri=10]
//				    Thread[main,5,main]
//				    java.lang.ThreadGroup[name=myGroup,maxpri=10]
//				        Thread[printThreadA,5,myGroup]
//				        Thread[printThreadB,5,myGroup]

		myGroup.interrupt(); // 상위 스레드 그룹에서 interrupt 발생시 하위 스레드도 전부 interupt 발생
		
//		printThreadA interrupted
//		printThreadA종료됨
//		printThreadB interrupted
//		printThreadB종료됨
	}

}
반응형
반응형

1. Runnable
Runnable은 이름부터 인터페이스의 느낌이 강하다.
implements Runnable 을 통해서 Runnable 인터페이스를 구현할 수 있다.

Runnable 인터페이스를 구현하게되면 재사용성이 높고, 코드의 일관성을 유지할 수 있어서 Thread보다 더 효율적인 방법이라 할 수 있다.

2. Thread
상속을 받아 사용해야 하기 때문에 다른 클래스를 상속받아 사용할 수 없다는 단점이 있다.
따라서 일반적으로는 Runnable 인터페이스를 구현해서 스레드를 사용한다.

반응형
반응형

- 제네릭

/** Generic 사용 안 할 시 **/
//public class Box {
//	
//	private Object t;
//	public Object get() {return t;}
//	public void set(Object t) {this.t = t;}
//	
//}

/** Generic 사용 할 시 **/
public class Box<T> {
	
	private T t;
	public T get() {return t;}
	public void set(T t) {this.t = t;}
	
}

// 1. 실행시 타입 에러가 나는 것보다는 컴파일시에 미리 타입을 강하게 체크해 에러를 사전에 방지할 수 있다.
// 2. 타입 변환을 제거할 수 있다.
// → 이와 같은 방법으로 컬렉션프레임워크가 탄생하게 되었다.

Box.java

public class GenericBox {

	public static void main(String[] args) {

		/** Generic 사용 안 할 시 **/
//		Box box = new Box();
//		box.set("Apple");
//		String boxContent = (String) box.get();
//		
//		Box boxInteger = new Box();
//		boxInteger.set(10);
//		int boxContentInteger = (int) boxInteger.get();
		
		
		
		/** Generic 사용 할 시 **/
		Box<String> box = new Box();
		box.set("Apple");
		String boxContent = box.get();

		Box<Integer> boxInteger = new Box();
		boxInteger.set(10);
		int boxContentInteger = boxInteger.get();
		
		
	}

}

GenericBox.java

 

- 제네릭<멀티타입>

public class Product<T, M> {

	private T kind;
	private M model;
	
	public void setModel(M model) {
		this.model = model;
	}

	public void setKind(T kind) {
		this.kind = kind;
	}
	
	public T getKind() {
		return kind;
	}
	
	public M getModel() {
		return model;
	}
	
}

// Product<Tv, String> product = new Product<>(); 이와 같이 사용

Product.java 이렇게 멀티타입도 가능하다.

 

 

- 제네릭 메소드, 제네릭 <멀티타입> 메소드

public class Util {
	// 기본 구조 : <T> 리턴타입 메소드명
	// return type : box<T> 제너릭으로 선언된 Box Class
	// 동작 : box 객체 선언 후 T 타입의 값을 box에 넣는다.
	public <T> Box<T> boxing(T t){ 
		
		Box<T> box = new Box<T>();
		box.set(t);
		return box;
	}
	
	// 멀티타입 제너릭 메소드 (두 객체의 키와 값을 비교하기)
	public <Key,Value> boolean compare(Pair<Key,Value> p1, Pair<Key,Value> p2) {
		
		boolean isSameKey = p1.getKey().equals(p2.getKey());
		boolean isSameValue = p1.getValue().equals(p2.getValue());
		
		boolean isSame = (isSameKey && isSameValue) ? true : false; 
		System.out.println("isSame ? : " + isSame);
		
		return isSame;
	}
	
}

Util.java

/** Generic 사용 할 시 **/
public class Box<T> {
	
	private T t;
	public T get() {return t;}
	public void set(T t) {this.t = t;}
	
}

Box.java

 

import java.util.HashMap;

public class GenericMethod {

	public static void main(String[] args) {

		/**  제너릭 메소드  **/
		Util util = new Util();
		Box<Integer> box = util.<Integer>boxing(100);
		int intValue = box.get();
		System.out.println(intValue);
		
		Box<String> box1 = util.<String>boxing("홍길동");
		String intValue1 = box1.get();
		System.out.println(intValue1);
		
		
		/**  멀티타입 제너릭 메소드  **/
		// 어디서 많이 본듯한 형태 → 컬렉션프레임워크 hashMap
		Pair<Integer,Integer> pair = new Pair<>(100,200);  
		Pair<Integer,Integer> pair1 = new Pair<>(100,200); 
		
		System.out.println(util.compare(pair, pair1));
		
		HashMap<String, String> at = new HashMap<>();
	}

}

GenericMethod.java

 

 

 

- 제네릭 상속

public class Util {
	// Number로 구현된 클래스만 사용 가능 int, double 등...
	// Number에서 상속받은 메소드 사용 가능 
	// return type : int
	public static <T extends Number> int compare(T t1, T t2) {

		
		double v1 = t1.doubleValue();
		double v2 = t2.doubleValue();
		
		return (int) (v1 + v2);
	}
	
}

Util.java

 

 

 

 

- 와일드카드

public class WileCard {

	public static void registerCourse(Course<?> course) {

	}

	public static void registerCourseStudent(Course<? extends Student> course) {

	}

	public static void registerCourseWorker(Course<? super Student> course) {
		
	}
	public static void main(String[] args) {

		/** 모든 클래스 사용 가능 **/
		registerCourse(new Course<Person>("일반인 과정", 5));
		registerCourse(new Course<Student>("학생", 5));
		registerCourse(new Course<Worker>("근로자", 5));
		registerCourse(new Course<HighStudent>("고등학생 과정", 5));

		/** 하위 클래스만 사용 가능 **/
		registerCourseStudent(new Course<Person>("일반인 과정", 5)); // Student 클래스 상속 안 받은 Person 클래스 이기 때문에 사용 불가
		registerCourseStudent(new Course<Student>("학생", 5));
		registerCourseStudent(new Course<Worker>("근로자", 5)); // Student 클래스 상속 안 받은 Worker 클래스 이기 때문에 사용 불가
		registerCourseStudent(new Course<HighStudent>("고등학생 과정", 5));

		/** 상위 클래스만 사용 가능 **/
		registerCourseWorker(new Course<Person>("일반인 과정", 5));
		registerCourseWorker(new Course<Student>("학생", 5));
		registerCourseWorker(new Course<Worker>("근로자", 5)); // Student의 상위 클래스가 아니라 사용 불가
		registerCourseWorker(new Course<HighStudent>("고등학생 과정", 5)); // Student의 상위 클래스가 아니라 사용 불가
	}

}

WildCard.java

 

public class Course<T> {
	
	private String name;
	private T[] students;
	public Course(String name, int capacity) {
		
		this.name = name;
		students = (T[]) (new Object[capacity]); 
		// T가 정해지지 않았는데 바로 사용 불가능 최상위 클래스 Object로 구현 한 다음 타입 변환 필요
	}
	
	public String getName() {
		return name;
	}
	
	public T[] getStudents() {
		return students;
	}
	
	
}


Course.java

 

public class Person {

	private String name;

	public Person(String name) {
		this.name = name;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

}

Person.java

public class Worker extends Person{
	public Worker(String name) {
		super(name);
	}
}

Worker.java

public class Student extends Person{
	public Student(String name) {
		super(name);
	}
}

Student.java

public class HighStudent extends Student{
	public HighStudent(String name) {
		super(name);
	}
}

HightStudent.java

 

 

-  제네릭 상속

public class Product<T,M> {

	private T kind;
	private M model;
	
	public T getKind() {
		return kind;
	}
	public void setKind(T kind) {
		this.kind = kind;
	}
	public M getModel() {
		return model;
	}
	public void setModel(M model) {
		this.model = model;
	}	
}

Product.java

 

// 자식 클래스는 무조건 부모 클래스의 제너릭 타입 이상을 가져야한다. (K, V 반드시 포함)
public class ChildProduct<K,V,C> extends Product<K, V> {

	
}

ChildProduct.java

 

public class ExtendsGeneric {

public static void main(String[] args) {

		ChildProduct<String, String, Integer> childProduct = new ChildProduct<>();
		
	}

}

ExtendsGeneric.java

반응형
반응형

 

- 입력스트림

데이터가 들어올 때 (입력)

- 출력스트림

데이터가 나갈 때 (출력)

 

스트림은 2가지로 나뉘게 된다.

1. 바이트 기반 스트립 : 그림 멀티미디어 문자 등 모든 종류의 데이터를 받고 보낼 수 있다.

2. 문자 기반 스트림 : 문자만 받고 보낼 수 있게 특화되어 있다.

 

- 바이트 기반 스트림

InputStream (최상위 입력 스트림 클래스)

XXX InputStream (하위 클래스 스트림) ex) FileInputStream

OutputStream (최상위 출력 스트림 클래스)

XXX OuputStream (하위 클래스 스트림) ex) FileOutputStream

 

- 문자 기반 스트림

Reader (최상위 입력 스트림 클래스)

XXXReader (하위 입력 스트림) ex) FileReader

Writer (최상위 출력 스트림)

XXXWriter (하위 출력 스트림) ex) FileWriter

 

파일에 글을 읽고 싶을 때 (파일이 입력될 때) → 입력 스트림

파일을 저장하고 싶을 때 (파일이 저장되어서 출력 될 때) → 출력 스트림

 

- InputStream

리턴 메소드 설명
int  read()  1바이트씩 읽고 읽은 바이트(UTF-8)를 리턴한다.
int read(byte[] b) 읽은 바이트들을 b(바이트 배열[UTF-8 값들)에 저장하고 실제 읽은 바이트 수를 리턴한다.
int read(byte[] b, int off, int len) off의 인덱스 번호부터 len의 크기만큼 읽어 b(바이트배열에 저장후에 출력한다.
void  close()  사용 후에 스트림을 가비지 컬렉터가 수집할 수 있게 닫아야 한다.

 

 

 

 

- OutputStream

리턴 메소드 설명
void write(int b) 1 바이트를 저장 또는 화면 따위에 보낸다
void write(byte[] b) 바이트 배열 전체를 저장 또는 화면 따위에 보낸다.
void write(byte[] b, int off, int len)  off의 인덱스 번호부터 len의 크기만큼 b(바이트배열)을 보낸다
void flush() 버퍼에 잔류하는 모든 바이트를 출력한다. (이 행위를 하지 않으면 바이트는 할당량[메모리버퍼]만큼 찰 때까지 출력하지 않는다.)
void close() 사용 후에 스트림을 가비지 컬렉터가 수집할 수 있게 닫아야 한다.

 

 

- Reader

리턴 메소드 설명
int reade() 한 개의 문자를 읽고 리턴한다.
int read(char[] cbuf) 읽은 문자들을 배열에 저장하고 읽은 문자 수를 리턴한다.
int read(char[] cbuf, int off, int len) off의 인덱스 번호부터 len의 크기만큼 문자를 리턴한다
void close() 사용 후에 스트림을 가비지 컬렉터가 수집할 수 있게 닫아야한다.

 

 

 

- Writer

리턴 메소드 설명
void write(int c) 한 문자를 파일이나 화면 따위에 보낸다
void write(char[] cubf) 읽은 문자의 배열을 파일이나 화면 따위에 보낸다
void write(char[] cbuf, int off, int len) off의 인덱스 번호부터 len의 크기만큼 문자의 배열을 파일이나 화면 따위에 보낸다.
void write(String str) 문자열을 파일이나 화면 따위에 보낸다
void write(String str, int off, int len) off의 인덱스 번호부터 len의 크기만큼 문자열을 파일이나 화면 따위에 보낸다.
void flush() 버퍼에 잔류하는 모든 바이트를 출력한다. (이 행위를 하지 않으면 바이트는 할당량[메모리버퍼]만큼 찰 때까지 출력하지 않는다.)
void close() 사용 후에 스트림을 가비지 컬렉터가 수집할 수 있게 닫아야한다.

 

import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;

public class Stream {

	public static void main(String[] args) throws IOException {

		/** -- inputStream -- **/
		// 1. is.read() 1 byte 씩 읽기
//		InputStream is = new FileInputStream("C:/test.txt"); // 'abcd' 내용이 저장 됨
//		int readByte;
//		while((readByte=is.read()) != -1) {
//			System.out.println(readByte); // 97 98 99 100
//		}
//		is.close();

		// 2. is.read(byte[] b) 내가 설정한 byte의 크기 읽기
//		InputStream is = new FileInputStream("C:/test.txt"); // '안녕하세요' 내용이 저장 됨
//		int readByte;
//		byte[] readBytes = new byte[3];
//		while( (readByte = is.read(readBytes)) != -1) {
//			System.out.println(new String(readBytes,0 , readByte)); // 안 녕 하 세 요 (한글은 UTF-8에서 주로 3byte이기 때문에 잘 읽어온다.)
//		}
//		is.close();

		
		/** -- outputStream -- **/
		// 1. os.write() 1 바이트씩 출력
//		OutputStream os = new FileOutputStream("C:test2.txt");
//		byte[] data = "안녕하세요".getBytes(); // '안녕하세요'의 바이트를 가져온다. (UTF-8 코드)
//		for (int i = 0; i < data.length; i ++) {
//			os.write(data[i]); // '안녕하세요'가 test2.txt에 저장된다.
//		}
//		os.flush();
//		os.close();
		
		
		// 2. os.write(byte[] b) → os 1번에서 for문 여러번 사용하는 것보다 효율적
//		OutputStream os = new FileOutputStream("C:test2.txt");
//		byte[] data = "안녕하세요".getBytes(); // '안녕하세요'의 바이트를 가져온다. (UTF-8 코드)
//		
//		os.write(data); // '안녕하세요'가 test2.txt에 저장된다.
//		os.flush();
//		os.close();
		
		
		/** -- reader -- **/
		// 1. reader.read() 한 개의 문자씩 읽기
//		Reader reader = new FileReader("C:/test.txt"); // '안녕하세요' 내용이 저장 됨
//		int readData;
//		while ( (readData = reader.read()) != -1 ) {
//			System.out.println((char) readData); // 안 녕 하 세 요 출력
//		}
//		reader.close();
		
		// 2. reader.read(char[] cbuf) 여러개의 문자열로 읽기
//		Reader reader = new FileReader("C:/test.txt"); // '안녕하세요' 내용이 저장 됨
//		int readData;
//		char[] cbuf = new char[2];
//		while ( (readData = reader.read(cbuf)) != -1 ) {
//			System.out.println(new String(cbuf, 0, readData)); // 안녕 하세 요 출력
//		}
//		reader.close();

		
		/** -- writer -- **/
		// 1. writer.write() 한 개의 문자씩 출력
//		Writer writer = new FileWriter("C:/test3.txt");
//		char[] data = "홍길동".toCharArray();
//		for(int i = 0; i < data.length; i++) {
//			writer.write(data[i]);
//		}
//		writer.flush();
//		writer.close();
		
		
		// 2. writer.write(char[] cbuf) 여러개의 문자열 출력
//		Writer writer = new FileWriter("C:/test3.txt");
//		char[] data = "홍길동".toCharArray();
//		writer.write(data);
//		writer.flush();
//		writer.close();
		
		// 3. writer(String)
		Writer writer = new FileWriter("C:/test3.txt");
		String data = "스프레이";
		writer.write(data);
		writer.flush();
		writer.close();

		
	}

}

 

 

출처 : https://www.youtube.com/watch?v=ISO3iy4aOh8&t=503s

반응형
반응형

 

출처 : https://coding-factory.tistory.com/830

 

메소드(Method) 영역

Static 영역이라고도 하며 전역 변수와 정적 멤버변수(static 변수)가 저장되는 영역입니다.

공통으로 사용 가능한 영역이라 Static 영역에는 다 같이 쓰이는 객체를 선언하면 좋습니다.

Static 영역의 데이터는 프로그램의 시작부터 종료가 될 때까지 메모리에 남아있게 됩니다.

예) 사용자 50명이 주소검색 API를 요청할 때 주소API코드 = “000000001“ 공통적인 부분이 쓰일 경우 50명의 객체를 만들어서 힙 메모리에 쌓이게 하는 방법보다는 공통적으로 다 사용하는 메소드 영역에 선언하는게 더 효율적이다.

 

스택(Stack) 영역

지역변수, 인자값, 리턴값저장되는 영역이고 메소드 안에서 사용되는 기본형 변수들이 값과 함께 저장되고 Heap 영역에 생성된 객체들을 참조하는 주소값이 할당됩니다.

예) String name = “Annna” 일 경우 name 이라는 변수가 힙 영역에 생성된 Annna라는 값을 참조하게 됩니다.

 

 

힙(Heap) 영역

자바 프로그램에서 사용되는 모든 인스턴스 변수(객체)들이 저장되는 영역입니다.

힙 영역은 메모리 공간이 동적으로 할당되고 해제되며 메모리의 낮은 주소에서부터 높은 주소로 할당이 이루어집니다

예) Annna라는 값은 여기에 생성되게 됩니다.

 

출처 : https://pjh3749.tistory.com/255

 

String 은 매우 자주 쓰이게 때문에 특별 대우를 받습니다.

String의 특징으로는 불변의 값을 넣는게 좋습니다.

문자열 리터럴은 공통 풀(공통 영역)에 공유 데이터로 저장 되기 때문에 불변의 값을 넣도록 설계 되어 있습니다.

 

String s1 = "Hello"; // String literal

String s2 = "Hello"; // String literal

String s3 = s1; // 같은 참조

String s4 = new String("Hello"); // String object (객체)

String s5 = new String("Hello"); // String object (객체)

 

s1, s2, s3의 경우 Hello라는 공통의 값을 참조하기 때문에 메모리 효율성이 좋습니다. (공통 풀에 저장된 값을 참조)

반면 s4, s5와 같이 new로 생성한 경우 Heap 영역에 따로 따로 생성되기 때문에 메모리 관리에 비효율적입니다.

 

만약 String이 계속 수정 되어야 한다면 StringBuffer나 StringBuilder를 사용하세요

 

StringBuffer와 StringBuilder의 결과는 동일하지만 상황에 따라 쓰이는게 약간 다릅니다.

StringBuffer의 경우는 멀티스레드의 환경에서 안전하게 돌아가도록 만든 것이고

StringBuilder의 경우 싱글스레드의 환경에서 안전하게 돌아가도록 만든 것입니다.

 

 

결과적으로 String 객체들의 연산이 이루어지면, 새로운 객체를 계속 만들어내기 때문에 메모리 관리 측면에서 상당히 비효율적이다.
이러한 이유로 만들어진 메모리영역이 Heap 안에 있는 String Constant Pool이다. 

여기에는 기존에 만들어진 문자열 값이 저장되어 있고, s1과 s2처럼 리터럴로 생성된 같은 값을 가지는 객체는 같은 레퍼런스를 가지게 됩니다.

 

출처: https://ict-nroo.tistory.com/18 [개발자의 기록습관:티스토리]

 

메모리 주소 값 확인하는 법

<dependency> 
    <groupId>org.openjdk.jol</groupId> 
    <artifactId>jol-core</artifactId>    
    <version>0.10</version> 
</dependency>
System.out.println("Memory address: " + VM.current().addressOf(obj));

HashCode의 값과 addressOf의 값은 다르다

(글을 찾아보면 HashCode값이 주소값이라고 하지만 정확히 상기 라이브러리를 사용해 addressOf 메소드를 사용할 시 정확하게 나온다.)

 

String hashCodeStr1 = "abcde";
String hashCodeStr2 = new String("abcde");

long hashCode1 = hashCodeStr1.hashCode(); 
long hashCode2 = hashCodeStr2.hashCode(); 

System.out.println("hashCode1 : " + hashCode1); // hashCode1 : 92599395
System.out.println("hashCode2 : " + hashCode2); // hashCode2 : 92599395	
System.out.println(hashCodeStr1 ==  hashCodeStr2 ? "hashCodeStr1 == hashCodeStr2" : "hashCodeStr1 != hashCodeStr2");
// hashCodeStr1 != hashCodeStr2

hashCode의 값은 같지만 == 로 비교한 결과 다르다고 나온다.

 

참고로 == 의 경우 주소값을 비교하고 equals의 경우 해당 객체의 값을 비교합니다.

 

참고로 com.foo.Person@2f92e0f4 이렇게 객체값을 나오는 경우의 아래처럼 해석하시면 됩니다.

com.foo.MyType - 클래스의 이름. 즉, com.foo. 패키지의 MyType 클래스

@ - 문자열 구분자

2f92e0f4 - 객체의 hashcode

 

hashCode에 대한 참고 사이트 : https://brunch.co.kr/@mystoryg/132

hashCode에 대한 참고 사이트 : https://brunch.co.kr/@mystoryg/133

 

 

 

 

 

메모리릭이란

ld 영역에 계속 누적된 객체로 인해 Major GC가 빈번하게 발생하게 되면서,
프로그램 응답속도가 늦어지면서 성능 저하를 불러온다. 이는 결국 OutOfMemory Error로 프로그램이 종료
쓴 객체에 대한 참조를 해제하지 않으면 가비지 컬렉션의 대상이 되지 않아 계속 메모리가 할당 되는 메모리 누수 현상이 발생

 

 

메모리 누수 팁

일단 정확히 어디에서 메모리가 누수되는지는 각자가 코딩한 코드내용에 따라 다 다르기 때문에 한정짓기는 힘들지만 개발할 때 지양해야할 사항 및 지향해야할 사항 및 알아두면 좋은 내용으로 구성했습니다.

 

정확한 원인 파악은 메모리 덤프 후 메모리 덤프를 분석해야 합니다.

 

기본적으로 Stack에서 Heap에 있는 객체를 참조하고 있는 동안에는 해당 객체는 GC에 의해 소멸되지 않습니다. 그에 대한 해결 방안입니다.

 

1. 불변의 값인 경우 String을 선언하고 값이 변동하는 경우 StringBuilder나 StringBuffer를 사용한다.

2. Http 객체 및 DB Connection 등... 사용 후 close()를 잘 해준다.

3. 모두 공유하는 값이 있고 불변의 경우 static final로 static에 선언해준다.

4. 다 쓴 객체에 Null을 할당한다.

 

참고로 XML과 JSON 파싱은 메모리를 가장 많이 사용합니다.

 

제가 봤을 때 가장 중요한건 4번입니다.

웬만한 객체들은 JVM에서 GC를 해주기 때문에 문제가 없지만 메소드를 호출하며 객체 잔뜩 만들고 그 이후에 사용 안 되는 경우 및 엄청난  사용자가 이용할 경우 복합적인 이유로 인해 참조가 남아서 GC 대상에서 제외되어서 OOM(Out of Memory)가 나게 됩니다

물론 코딩하면서 메모리 구조가 어떻게 만들어지고 다 머리에 그려지고 GC타이밍 등 다 파악 해 코딩하는 경우 필요한 부분만 null 사용하면 됩니다.

 

 

 

참고로 2번도 close() 메소드 내용 까보면 null을 할당해주는 짓을 하고 있습니다.

private final static int ALLOC_SIZE = (int) (Runtime.getRuntime().maxMemory() * 0.60);

public void allocate() {

    System.out.println("Before first allocation");
    byte[] b = new byte[ALLOC_SIZE];
    System.out.println("After first allocation");

    System.out.println("Before second allocation");
    byte[] b2 = new byte[ALLOC_SIZE];
    System.out.println("After second allocation");

}

public static void main(String[] args) {
    new GCTest().allocate();
}

Null 처리를 안 한 경우

 

재할당이 안 됩니다.

 

private final static int ALLOC_SIZE = (int) (Runtime.getRuntime().maxMemory() * 0.60);

public void allocate() {

    System.out.println("Before first allocation");
    byte[] b = new byte[ALLOC_SIZE];
    System.out.println("After first allocation");
    b = null;
    System.out.println("After assign null to b");
    System.out.println("Before second allocation");
    byte[] b2 = new byte[ALLOC_SIZE];
    System.out.println("After second allocation");
}

public static void main(String[] args) {
    new allocate();
}

Null 처리를 한 경우

 

재할당이 가능해집니다.

 

 

참고로 배열의 경우 모든 인덱스의 null을 넣어줘야 합니다.

 

이렇게 함으로써 얻을 수 있는 부가적인 장점은 누군가가 악의적으로 혹은 실수로 해당 객체를 다시 사용하려 했을 때 NullPointException이 발생하며 사전에 에러를 발생시킬 수 있다는 점이 있습니다만 단점으로는 코드가 지저분하게 됩니다.

 

이러한 방법을 깔끔하게 해결할 수 있는 객체들이 나오기 시작했는데요 WeakHashMap 등이 있습니다.

 

 

출처 : https://js2prince.tistory.com/entry/Java-%EA%B0%9D%EC%B2%B4-%EC%82%AC%EC%9A%A9%ED%9B%84-null-%ED%95%A0%EB%8B%B9-%ED%95%B4%EC%95%BC%ED%95%98%EB%82%98-%EB%A7%90%EC%95%84%EC%95%BC-%ED%95%98%EB%82%98

 

 

GC 구조

 

GC 일반적 동작순서

 

 

Mark And Sweep Sometimes Compact
Mark - 참조 객체 파악
Sweep - 참조 안 하는 객체 삭제
Compact - Sweep하면서 비어있는 메모리의 단편화를 막기 위해 응집시키는 역할 (때때로 발생)

 

 

WAS(톰캣)을 사용해 구동시킬 때 메모리 크기를 정할 수 있다.

메모리크기가 큰 경우
  1. GC 발생횟수는줄어든다.
  2. GC 수행시간은길어진다.

메모리크기가 작은 경우
  1. GC 수행시간은적어진다.
  2. GC 발생횟수는증가한다.

 

메모리 크기에 따라 일장일단이 있어 잘 설정해야합니다.

크기를 무제한으로 두는 경우 서버의 메모리를 다 차지해 서버가 뻗는 경우도 존재하지만 이미 WAS가 뻗어버리면

서비스가 안 되므로 그거나 그거나 인 거 같습니다.

 

GC 종류

 

Minor GC

Young 영역에서 발생하는 GC

 

Major GC

Old 영역에서 발생하는 GC

 

Full GC

Young과 Old영역에 생성된 객체를 모두 지웁니다.

Full GC는 속도가 매우 느리고, Full GC가 발생하는 순간, 자바 어플리케이션이 멈춥니다. 
따라서 Full GC는 성능과 안정성에 아주 큰 영향을 미칩니다.

Old 영역으로 이동하는 객체의 수를 줄이면 Full GC가 발생하는 빈도를 많이 줄일 수 있습니다.
최대한 FULL GC는 나오면 안 되며 웬만해서 Minor GC에서 다 처리하도록 해야합니다.

그렇다고 Full GC 실행 시간을 줄이기 위해서 Old 영역의 크기를 줄이면 자칫 OutOfMemoryError가 발생하거나 Full GC 횟수가 늘어난다. 
반대로 Old 영역의 크기를 늘리면 Full GC 횟수는 줄어들지만 실행 시간이 늘어난다. Old 영역의 크기를 적절하게 잘 설정해야 한다.

FULL GC에는 다양한 FULL GC가 존재한다. (Parallel GC Concurrent GC Train GC....등)

 

 

JAVA7까지 PERM 영역이 존재하는데 이는 영구적일 것이라고 생각하는 객체들을 관리합니다.

이 영역은 GC 대상에서 제외됩니다. 하지만 이 때문에 GC가 안 되어 OOM이 발생해 뻗어버리는 경우가 발생합니다.

 

최근 Java 8에서는 JVM 메모리 구조적인 개선 사항으로 Perm 영역이 Metaspace 영역으로 전환되고 기존 Perm 영역은 사라지게 되었다.  
Metaspace 영역은 Heap이 아닌 Native 메모리 영역(서버 메모리)으로 취급하게 된다. 

(Heap 영역은 JVM에 의해 관리된 영역이며, Native 메모리는 OS 레벨에서 관리하는 영역으로 구분된다) 

Metaspace가 Native 메모리를 이용함으로서 개발자는 영역 확보의 상한을 크게 의식할 필요가 없어지게 되었다.)
즉 개발자는 이 영역에 대해서 신경쓰지 않아도 JVM이 알아서 늘려주는 역할을 해줍니다.

 

 

출처 : https://www.youtube.com/watch?v=Fe3TVCEJhzo&t=260s

반응형
반응형

List<String> list = new ArrayList<String>();

ArrayList<String> list = new ArrayList<String>();

 

List = 인터페이스

ArrayList = 클래스

 

ArrayList를 쓰다가 LinkedList를 써야하는 경우가 오는 경우 코드를 많이 손봐야하는 경우가 생길 수 있다.

(List를 상속받아서 만든 클래스인 LinkedList의 경우 삽입 삭제에 유용)

하지만 인터페이스 객체를 선언하는 경우 DI가 쉬워지고 코드를 객체 선언하는 부분만 바꾸면 된다.

 

즉, 인터페이스를 통해 객체를 생성하는게 좋다.

반응형
반응형

 

public class Chatting {

	public static void main(String[] args) {
		String name = args[0];
		
		System.out.println(name + "님 어서오세요");
	}
}

file - export - Runnable JAR file

프로젝트 설정과 디렉토리 설정을 합니다.

실행 방법

java -jar jar파일명 파라미터[args[0]] .....

반응형
반응형

로깅(Logging)

  정보를 제공하는 일련의 기록인 로그(log)를 생성하도록 시스템을 작성하는 활동.

 

System.out.print()

  하지만 이는 프로그램의 성능을 떨어트리고 로그를 파일에 저장하는 것이 불가능


java.util.logging

  외부 라이브러리 사용 없이 로깅이 가능

  다른 라이브러리와 비교했을 때 퍼포먼스 (속도) 가 느리다.

  나만의 custom 레벨을 만들면 메모리 누수가 일어난다.
  타 라이브러리에 비해 기능이 부족하다.

 

log4j

  log4j는 가장 오래된 로깅 프레임워크로써 Apache의 Java기반 로깅 프레임 워크이다.

  콘솔 및 파일 출력의 형태로 로깅을 도와주며 xml, properties로 환경을 구성할 수 있다.

  현재는 2015년 기준으로 개발이 중단되었다.

  퍼포먼스가 최적화되어있다.

  log4j2 가장 최신에 나온 로깅 프레임워크로써 Apache의 log4j의 다음 버전

 

 

SLF4J

  SLF4J(Simple Logging Facade for Java)는 이름에서 확인할 수 있듯이.

  java.util.logging, logback 및 log4j와 같은 다양한 로깅 프레임 워크에 대한 추상화(인터페이스) 역할을 하는 라이브러리

  즉 DI할 수 있게 있는 상위 인터페이스

  (slf4j를 사용하여 설정에 따라 다른 로깅 라이브러리를 사용할 수 있게 된다.)

  (slf4j 사용 후 Implementation은 logback이나 log4j2를 사용)

 

참고자료 : https://www.fwantastic.com/2019/12/javautillogging-vs-log4j-vs-slf4j.html

반응형