/** 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<>();
}
}
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 등이 있습니다.
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이 알아서 늘려주는 역할을 해줍니다.