출간일 2016년 11월 28일
판매가 2000원
형태 ebook
이 책의 모든 내용은 http://ehpub.co.kr에 공개하고 있습니다.
학습에 도움이 되시면 ebook을 구입하여 소장하시면 감사하겠습니다.
언제나 휴일 출판사의 수익금의 대부분은 아프리카에 기부하고 있습니다.
4.4 개체의 생성과 소멸
앞에서 개체를 생성할 때 new 키워드와 함께 생성할 개체 형식 명과 생성자 메서드의 입력 인자를 전달한다는 것을 소개하였습니다. 이번에는 구체적으로 개체의 생성과 소멸에 관하여 살펴보기로 합시다.
4.4.1 개체의 생과 사
C언어나 C++언어에서는 동적으로 메모리를 할당하면 힙에 할당하고 개발자 코드에 의해 해제해야 합니다. 이에 반해 Java 언어에서는 개발자 코드에 의해 개체를 생성하지만 개발자 코드에 의해 개체를 소멸하지 않습니다.
이와 같이 개체의 생성은 개발자 코드에 의해 결정하고 소멸하는 코드를 작성하지 않는 이유는 Java 가상 머신의 관리화 힙(Managed Heap)에 개체를 할당하여 관리하기 때문입니다.
Java에서 개체를 생성하면 관리화 힙(Managed Heap)에 할당합니다. 관리화 힙은 Java가상 머신에서 개체를 참조하는 변수의 개수를 카운팅하여 관리합니다. 그리고 가상 머신의 쓰레기 수집기(Gabage Collector)에 의해 관리화 힙의 개체를 참조하는 변수의 카운터가 0이 되면 개체의 세대를 변경하여 수거 대상임을 표시하여 제거할 수 있음을 표시합니다. 만약 개발자가 특정 변수로 참조하고 있는 개체를 사용할 필요가 없으면 가상 머신에서는 참조 개수를 1 감소하여 효과적으로 개체를 관리할 수 있게 도움을 줄 수 있습니다.
쓰레기 수집기는 주기적으로 가동하며 개체를 할당할 때 메모리가 부족할 때도 가동합니다. 그리고 System 클래스의 정적 메서드 gc를 호출해도 쓰레기 수집기가 동작합니다.
쓰레기 수집기가 가동하면 관리화 힙의 개체를 참조하는 변수의 카운터가 0인 것이 있는지 확인하여 세대를 부여하는 작업을 수행합니다. 처음으로 발견한 개체는 세대 0으로 마킹하고 세대 0인 개체가 여전히 남아 있으면 1세대, 1세대인 것이 여전히 남아 있으면 2세대로 마킹합니다. 이와 같은 방식으로 세대 조사를 하는 이유는 개체를 참조하는 변수가 없을 때 바로 메모리를 해제하는 방식은 너무 많은 비용이 들기 때문입니다.
Java 가상 머신에서는 주기적인 세대 조사를 통해 관리화 힙의 메모리가 필요하면 세대 관리 정책에 따라 필요없는 개체를 수거합니다. 만약 개발자 코드에서 System 클래스의 정적 메서드 runFinalization을 호출하면 수집 대상 개체의 finalize 메서드를 수행합니다. 따라서 여러분이 정의하는 클래스에 finalize 메서드를 정의하면 개체를 수거할 때 수행합니다.
그리고 System 클래스의 runFinalizersOnExit 메서드를 호출하면 프로세스가 끝날 때 프로세스의 개체의 finalize 메서드를 호출하는 것을 활성화 혹은 비활성화할 수 있습니다. 가상 머신은 기본적으로 자동으로 호출하지 않도록 비활성화 상태입니다.
물론 Java 가상 머신은 매우 정교하게 만들어졌기 때문에 개발자 코드에서 의도적인 쓰레기 수집을 가동하거나 수거할 필요는 거의 없습니다. 여러분이 작성하는 프로그램이 서버 시스템의 주요한 서버 프로그램이며 자원 활용을 효과적으로 할 필요가 있는 특수한 프로그램일 때 이를 사용할 수 있지만 일반적인 프로그래밍에서는 사용할 필요가 없습니다.
1. public static void gc()
쓰레기 수집기 가동
2. public static void runFinalization()
쓰레기 수거(수거 대상 개체의 finalize 메서드를 호출함)
3. public static void runFinalizersOnExit(boolean value)
프로세스가 끝날 때 모든 개체를 수거함
여기에서는 대략적으로 쓰레기 수집기가 동작하는 것을 확인하는 테스트 코드를 작성합시다.
먼저 프로젝트를 생성하여 Unit 클래스를 추가하세요.
Unit 클래스에는 개체를 구분하기 위한 멤버 필드 num을 캡슐화하고 생성자에서 입력 인자로 받아 num 멤버 필드의 값을 설정할게요. 그리고 finalize 메서드를 정의하여 수거할 때 어떤 개체인지 출력하기로 할게요.
public class Unit { int num; public Unit(int num){ this.num = num; } public int getNum(){ return num; } protected void finalize(){ System.out.println(num+"번 개체 정리"); } } |
[소스 4.6] 쓰레기 수집 시기를 확인하기 위한 Unit 클래스 정의
첫 번째 테스트는 쓰레기 수집에 관한 코드를 작성하지 않는 예제입니다.
Program 클래스를 추가하여 진입점 메서드에 두 개의 유닛 개체를 생성하는 코드를 작성하여 실행해 보세요. 두 개의 Unit 개체를 생성하지만 수거하지 않음을 알 수 있습니다.
public class Program { public static void main(String[] args){ Unit unit1 = new Unit(1); System.out.println("유닛 생성"+unit1.getNum()); Unit unit2 = new Unit(2); System.out.println("유닛 생성"+unit2.getNum()); } } |
유닛 생성1 유닛 생성2 |
[소스 4.7] 쓰레기 수집에 관한 코드를 작성하지 않은 예
두 번째 테스트에서는 진입점 main 메서드에 System.runFinalizersOnExit(true); 코드를 추가하여 프로세스가 끝날 때 수거하지 않은 개체를 수거하도록 설정합니다. 실행하면 프로세스가 끝날 때 모든 개체를 수거하므로 Unit 클래스에 정의한 finalize 메서드가 동작하는 것을 확인할 수 있습니다.
public class Program { public static void main(String[] args){ Unit unit1 = new Unit(1); System.out.println("유닛 생성"+unit1.getNum()); Unit unit2 = new Unit(2); System.out.println("유닛 생성"+unit2.getNum()); System.runFinalizersOnExit(true); } } |
유닛 생성1 유닛 생성2 2번 개체 정리 1번 개체 정리 |
[소스 4.8] 쓰레기 수집에 관한 코드를 작성하지 않은 예
세 번째 테스트는 unit1 변수에 null을 설정하고 System의 정적 메서드 gc를 호출하는 코드를 추가하고 runFinalizersOnExit 메서드 호출은 제거합니다. 그리고 바로 개체를 수거하는지 확인하기 위해 출력문을 작성합니다. 실행하면 gc를 호출한 직후 바로 unit1을 수거하지 않고 나중에 수거하는 것을 알 수 있습니다. 이를 통해 gc를 호출하면 세대 조사를 수행함을 알 수 있습니다.
public class Program { public static void main(String[] args){ Unit unit1 = new Unit(1); System.out.println("유닛 생성"+unit1.getNum()); Unit unit2 = new Unit(2); System.out.println("유닛 생성"+unit2.getNum());
unit1 = null; System.gc(); System.out.println("음"); } } |
유닛 생성1 유닛 생성2 음 1번 개체 정리 |
[소스 4.9] System.gc()를 호출한 예
네 번째 테스트는 unit1 변수에 null을 대입하고 System 클래스의 정적 메서드 gc를 호출하는 코드 뒤에 System 클래스의 정적 메서드 runFinalization을 호출하는 코드를 추가합니다. 그리고 unit2 변수에 null을 대입하고 System 클래스의 정적 메서드 gc를 호출하는 코드를 작성하고 수거 시기를 확이하기 쉽게 화면에 출력문을 작성합니다.
실행해 보면 unit1 개체는 runFinalization 호출에 의해 수거함을 알 수 있습니다. 하지만 unit2 개체는 프로세스가 끝날 때 수거함을 알 수 있습니다.
public class Program { public static void main(String[] args){ Unit unit1 = new Unit(1); System.out.println("유닛 생성"+unit1.getNum()); Unit unit2 = new Unit(2); System.out.println("유닛 생성"+unit2.getNum());
unit1 = null; System.gc(); System.runFinalization(); unit2 = null; System.gc(); System.out.println("음"); } } |
유닛 생성1 유닛 생성2 1번 개체 정리 음 2번 개체 정리 |
[소스 4.10] System.gc()와 System.runFinalization()을 호출한 예
4.4.2 생성자 중복 정의, this, this()
앞에서 설명한 것처럼 개체를 생성할 때는 new 키워드와 함께 생성할 개체 형식 명과 생성자 메서드의 입력 인자를 전달합니다. 특히 생성자 메서드는 다른 메서드처럼 메서드 중복 정의 대상이므로 생성에 필요한 인자 종류를 다르게 정의할 수 있습니다.
예를 들어 회원을 클래스로 정의하고 멤버 필드로 회원 아이디, 이름, 나이를 캡슐화한다고 가정합시다. 이 때 아이디만 입력 인자로 받는 생성자, 아이디와 이름을 입력 인자로 받는 생성자, 아이디와 이름과 나이를 입력 인자로 받는 생성자를 정의하여 필요에 따라 원하는 형태의 개체를 생성할 수 있다는 것입니다.
그리고 멤버 메서드 내부에서 입력 인자와 멤버 필드의 이름이 같을 때 this 키워드를 이용하면 멤버 필드에 접근할 수 있습니다. 그리고 생성자에서 this()를 통해 다른 생성자를 호출할 수도 있습니다.
public class Member { String id; String name; int age; public Member(String id){ this(id,"",0); } public Member(String id,String name){ this(id,name,0); } public Member(String id,String name,int age){ this.id = id; this.name = name; this.age = age; } public String getId(){ return id; } public String getName(){ return name; } public int getAge(){ return age; } } |
public class Program { public static void main(String[] args){ Member m1 = new Member("abc"); Member m2 = new Member("def","강감찬"); Member m3 = new Member("ghe","홍길동",27); viewMember(m1); viewMember(m2); viewMember(m3); }
private static void viewMember(Member member) { String id = member.getId(); String name = member.getName(); int age = member.getAge(); System.out.println("아이디:"+id +" 이름:"+name+" 나이:"+age); } } |
아이디:abc 이름: 나이:0 아이디:def 이름:강감찬 나이:0 아이디:ghe 이름:홍길동 나이:27 |
[소스 4.11] 생성자 중복 정의 및 this, this() 사용 예
만약 생성자 메서드를 정의하지 않으면 매개 변수가 없는 기본 생성자가 자동으로 만들어지며 이를 디폴트 생성자라 부릅니다.
public class Unit { public void play(){ System.out.println("운동하다."); } } |
public class Program { public static void main(String[] args){ Unit unit = new Unit(); unit.play(); } } |
운동하다. |
[소스 4.12] 생성자 메서드를 정의하지 않을 때 디폴트 기본 생성자가 동작하는 예
물론 기본 생성자(매개 변수가 없는 생성자)를 정의할 때도 개체 생성할 때 인자를 전달하지 않는 기본 생성을 할 수 있습니다.
public class Unit { String name; public Unit(){ this("이름 없음"); } public Unit(String name){ this.name = name; } public void play(){ System.out.println(name+" 운동하다."); } } |
public class Program { public static void main(String[] args){ Unit unit = new Unit(); unit.play(); } } |
이름 없음 운동하다. |
[소스 4.13] 기본 생성자를 정의한 후 개체를 기본 생성 요청하는 예
하지만 생성자 메서드를 하나라도 정의하면 디폴트 생성자는 만들어지지 않습니다. 따라서 매개 변수가 있는 생성자 메서드만 정의한 클래스 형식의 개체를 생성할 때는 반드시 인자를 전달하여 개체를 생성해야 합니다.
public class Unit { String name; public Unit(String name){ this.name = name; } public void play(){ System.out.println(name+" 운동하다."); } } |
public class Program { public static void main(String[] args){ Unit unit = new Unit(); unit.play(); } } |
Exception in thread "main" java.lang.Error: Unresolved compilation problem: The constructor Unit() is undefined at Program.main(Program.java:4) |
[소스 4.14] 매개 변수 있는 생성자 메서드만 정의한 개체를 기본 생성 요청시 오류
'언어 자료구조 알고리즘 > 디딤돌 Java 언어 Part1' 카테고리의 다른 글
[Java] 5.4 인터페이스 (0) | 2016.11.14 |
---|---|
[Java] 5.3 추상화 (0) | 2016.11.14 |
[Java] 5.2 다형성 (0) | 2016.04.04 |
[Java] 5. OOP 상속과 다형성, 5.1 상속 (0) | 2016.04.04 |
[Java] 4. 5 정적 멤버와 static 키워드와 상수화에 사용하는 final 키워드 (0) | 2016.04.04 |
[Java] 4.3 중복 정의(Overloading) (0) | 2016.04.04 |
[Java] 4.2 접근 지정자, Java 언어 (0) | 2016.04.04 |
[Java] 4.1 클래스 정의, Java 언어 (0) | 2016.04.04 |
[Java] 4. OOP 캡슐화 (0) | 2016.04.04 |
[Java] 3.3 반복문 (while, for, break, break 레이블, continue) (0) | 2016.04.04 |