[패스트캠퍼스 백엔드 부트캠프 3기] 4주차 공부한 내용
* 8장 내용
예외 발생 - > 객체 생성
객체 : 정보 담음
* 문제가 생기면 나는 무엇을 해야 할까?
1. 기록
2. 보고
3. 재시도 ----- 1) 1248 원리 : 시도할때마다 시간을 두배로 늘려서 다시 도전 2) 절대 재시도 하면 안되는 경우 존재함--> 이때는 나중에 수동으로 처리
* 예외처리는 문제 발생시 내가 어떻게 대처할 것인지 생각하는 방법들이다
* 예외 선언은 호출한 쪽에 예외가 발생했다고 보고하는 것이다.
* finally 는 중복 코드 제거를 위하여 뒷정리 하는 작업
*9장 내용
Object클래스의 메서드들
Object클래스
1.clone() : 객체 복제
2. getClass() : 설계도 얻기
3. hashCode() : id 얻기
4,5 : equals(), toString()
객체 비교
1. 주소 --- 기본적으로 주소로 비교한다. 주소가 다르면 일단 다른 객체
2. 내용 --- 내용이 같으면 다른 객체지만 같은 객체라고 할까?
Q. Object클래스는 iv가 없는데 왜 equals()는
static메서드(클래스메서드) 가 아닌 인스턴스메서드를 사용할까 ?
1) this사용 (객체 비교)
2) 자손이 오버라이딩 시 iv쓸 가능성 존재
Equals()메서드가 타입이 Object인 이유

equals()메서드 의 매개변수가 Object 타입이면 --> guard/ filter로서의 제역할 못함 --> 컴파일러가 타입체크로 걸러주질 못하므로 안에서 무조건 if문을 작성해야 한다.
그런데 왜 Object 를 쓸까 ?
만약 Object 말고 Person 을 넣으면 if문이 필요없다. 컴파일러가 알아서 타입체크를 해주기 때문
그런데도 Object를 쓰는 이유(장점)은
만약 안쓰면 Person , aa, bb,,등등 여러가지 타입별로 equals()메서드를 복잡하게 만들어주어야 하기 때문에
하나로 통합하는 편리함이 있다.
Hashcode()의 원리
차번호 --> 위치 찾아주는 키오스크(함수)
차번호 : 고유값 --> 객체의 id
원래는 주소값을 이용해서 해시코드를 만듦
해시코드는 주소값이 아님
주소값으로 만든 int 타입 고유값
근데 문제는 hashcode는 int --> long을 사용해야 하는데 선언문 변경 못함
요즘에 64bit 사용하는데 int형 hashcode는 32bit --> 같은 값이 나오는 중복 문제 생김
64bit를 32bit로 변환하면 주소값이 [ 32bit ] [ 32bit ] 앞부분 32bit이 짤림
--> 서로 다른 객체인데 같은 주소값이 나올 수 있는 오류 생김
* String 은 내용으로 hashcode()를 만든다.
String 객체는 내용이 같으면 같은 hashcode()
String은 특이하게 hashcode()를 override해서 내용이 같으면 같은 hashcode()라고 사용한다.
.. 메모리가 커지면서 중복되는 경우 생김 (int형 문제)
그것 제외하고는 서로 다른 객체이면 다른 hashcode
toString()
기본적으로 클래스이름@ hashcode 반환 --> 오버라이딩해서 다르게 사용 (문자열로 변환하는데 사용함)
보통 오버라이딩 해서 객체의 iv값을 출력하는데 사용
clone()
객체 복제 -- 얕은 복사 (shallow copy)
: 객체에 저장된 값을 그대로 복사
깊은 복사 : 객체가 참조하고 있는 객체까지 복제
공변 반환타입(covariant return type)
: 오버라이딩할때 조상 메서드의 반환타입을 자손까지 가능하도록 변경을 허용하는것
반환타입
1. 일치 invariant
2. 공변 = 다형성 covariant = 자손까지 가능
3. 다형성의 반대/역공변/역변 contravariant = 조상까지 가능
장점 : 조상의 타입이 아닌, 실제로 반환되는 자손 객체의 타입으로 반환할 수 있어서
형변환이 줄어든다 !
형변환의 단점 : 1. 코드 복잡해짐
2. 에러 발생가능성 ↑ ( ClassCastException 에러 (런타임에러) 발생)
* 타입은 일치해야 한다.
그런데 유연성을 위해 공변, 반공변을 허용가능하게 할 수 있다.에서 출발한 개념
* 배열은 참조변수가 없으므로 얕은복사만 해도 충분하다. (객체배열인 경우 제외)
얕은 복사 : 단순히 객체에 저장된 값을 그대로 복사
--> 불완전 복사
깊은 복사 : 객체가 참조하고 있는 객체까지 복제
--> 오버라이딩 해서 읽기와 쓰기를 둘 다 할 수 있다.
getClass() 메서드
-> 클래스 템플릿!
-> 클래스가 기본으로 가져야 하는것을 다 가지고 있다.
템플릿이라는 것은 공통설계를 할 수 있다는 것이고, 최적화와 추상화와 관련이 있다.
- > 이 메서드는 자신이 속한 클래스의 Class 객체를 반환하는 메서드 /
Class 객체 : 이름이 Class인 클래스의 객체
Class 클래스 --> 설계도 객체 / 싱글톤(설계도는 하나만 있기 때문에)
1.클래스 객체는 클래스의 모든 정보를 담고 있으며, 클래스 당 1개만 존재한다.
2. 그리고 클래스 파일이 클래스 로더(ClassLoader)에 의해서 메모리에 올라갈때, 자동으로 생성된다.
3. 클래스 로더는 실행시 필요한 클래스를 동적으로 메모리에 로드하는 역할
ClassNotFoundException : 클래스 로더가 기존에 생성된 클래스 객체가 메모리에 존재하는지 확인해서 없을 경우 클래스 패스(classpath )에 지정된 경로에 따라 클래스파일을 찾는다. 만약 실패할 경우 발생하는 예외
클래스 로더가 클래스 파일을 읽어서 사용하기 편한 형태로 저장해놓은것이 클래스 객체
클래스 파일과 내가 정의한 클래스의 차이?
--> 클래스 파일이 생성자, object클래스등 모든 정보 ( 더 많은 정보)를 다 가지고 있다.
*클래스 파일은 그냥 쓸 수 없고 객체를 생성해서 사용해야 한다.
객체 생성 이유? 파일 vs 객체 누가 더 사용하기 편리한가?를 생각해보면 된다.
Class 객체를 얻는 방법
1. Class cObj = new Card().getClass(); //생성된 객체로부터 얻는 방법
2. Class cObj = Card.class; // 클래스 리터럴(*.class)로부터 얻는 방법
3. Class cObj = Class.forName("Card"); // 클래스 이름으로부터 얻는 방법 = 문자열로 가져오기 가능
1번은 Card 객체를 무조건 생성해야 한다. 이 방법은 그래서 잘 안쓰인다. 예시를 Singleton으로 들어보면
싱글톤은 설계도 1개
-- 메서드 호출하면 어차피 하나이므로 그냥 그 클래스 객체 가져오는데
굳이 new 키워드 사용해서 객체 생성하는 것은 잘 안쓰는 방법 . 무조건 객체 생성해야 함
--- 팩토리메서드
ex) Singleton s = new Singleton(); 잘 안쓰고
Singleton s = Singleton.getInstance로 메서드를 가져오는 이유 이다.
* 1,2번은 코드에 박혀있다. 바꾸러면 코드를 변경해야 한다.
3번은 코드에 안나와있는것이다. 더 유연하다.
다만 그 "Card"문자열이 있는지 컴파일러가 체크를 못하고, 실행 해야 있는지 없는지 알 수 있다. 여기에서 ClassNotFound에러가 발생한다 ---> 컴파일은 코드랑 같이 했는데 문자열 파일을 다른데로 옮겨 버림
코드가 바뀐다는 건 컴파일, 테스트, 배포 등을 다 다시해야 한다는 것을 의미한다. 차라리 3번인 외부파일(문자열 파일) 변경이 훨씬 나은 방법이므로 1,2번보다 3번을 사용한다.
<가비지 컬렉터>
책 내용중
"자신을 참조하고 있는 참조변수가 하나도 없는 인스턴스는 더 이상 사용되어질 수 없으므로 '가비지 컬렉터'에 의해서 자동적으로 메모리에서 제거된다"
라는 부분에서 궁금증이 생겼다
자신을 참조하는 참조변수가 하나도 없는 인스턴스는 더이상 사용되지 않으므로 가비지 컬렉터에 의해서 자동으로 메모리에서 제거되는데, 만약에 프로그래머가 메모리 효율을 위해서 일부러 참조변수가 참조하고있던 주소값을 null 등 다른값으로 변경해서 참조하지 않도록 해버리는 경우도 있을까 ?
-->
GPT 답변
가능은 함. 그러나 가비지 컬렉터의 자동 관리가 더 효율적
- 가비지 컬렉터의 자동 관리에 의존
- 현대의 JVM에서는 가비지 컬렉터가 메모리 관리를 효율적으로 수행하므로, 대부분의 경우 프로그래머가 명시적으로 null을 설정할 필요가 없습니다.
- 불필요한 null 설정은 오히려 혼란을 초래
- 코드 가독성이 떨어지고, NullPointerException 발생 가능성을 높일 수 있습니다.
- WeakReference 활용
- 명시적인 null 대신, WeakReference와 같은 메커니즘을 사용해 메모리 관리를 보다 안전하고 효과적으로 할 수 있습니다.
참조를 null로 설정하는 것이 가능하고 경우에 따라 유용하지만, 일반적으로 가비지 컬렉터의 메모리 관리에 의존하는 것이 더 바람직합니다. 꼭 필요한 경우에만 신중히 사용해야 합니다.
두번째 궁금한점
그러면 가비지 컬렉터는 프로그래머가 더 사용할건지 아니면 더이상 사용하지 않을 건지 어떻게 알까 ?
어느정도 기간을 잡고 그 기간내에 사용되지 않으면 버리는건가 ?
어떤 메커니즘으로 버릴 객체를 선정하는지 ?
가비지 컬렉터(GC)는 객체가 "더 이상 사용되지 않는다"는 것을 객체의 참조 여부를 기준으로 판단합니다. GC가 "사용되지 않는다"고 판단하는 메커니즘은 참조 그래프와 객체의 접근 가능성을 기반으로 동작하며, 특정 기간을 설정하는 방식은 아닙니다.
1. GC가 객체를 판단하는 기준: 도달 가능성(Reachability Analysis)
- GC는 **"도달 가능성(Reachability)"**을 기반으로 객체가 사용 중인지 판단합니다.
- 도달 가능성은 **GC 루트(GC Roots)**에서 해당 객체로 참조가 이어질 수 있는지로 결정됩니다.
GC 루트(GC Roots)의 예:
- 스택에 있는 지역 변수
- 정적 변수(static fields)
- 활성화된 쓰레드의 참조
- 네이티브 코드에서의 참조 등
동작 과정:
- GC 루트에서 시작:
GC는 루트 객체에서 모든 참조를 따라가며, "도달 가능한" 객체들을 탐색합니다. - 도달 불가능한 객체:
GC 루트에서 시작해 참조가 끊어진 객체들은 "도달 불가능"하다고 판단되어 가비지로 간주됩니다.
2. GC 알고리즘의 주요 메커니즘
GC는 다양한 알고리즘을 사용하여 메모리를 관리합니다. 대표적인 알고리즘은 다음과 같습니다:
(1) Mark-and-Sweep
- Mark 단계:
GC 루트에서 시작해 도달 가능한 객체를 표시합니다. - Sweep 단계:
표시되지 않은 객체를 메모리에서 해제합니다.
(2) Generational GC (세대별 가비지 컬렉션)
- 객체를 생성된 시간에 따라 다음 세 가지 세대로 나누어 관리합니다:
- Young Generation: 새로 생성된 객체들이 위치하며, 대부분의 객체는 여기에 존재하다가 빨리 수거됩니다.
- Old Generation: Young Generation에서 살아남은 객체들이 옮겨지며, 이곳에 있는 객체는 수명이 길다고 간주됩니다.
- Permanent Generation (또는 Metaspace): 클래스 메타데이터가 저장됩니다 (Java 8 이후는 Metaspace로 대체).
- Young Generation은 더 자주 수집하며, Old Generation은 상대적으로 덜 빈번히 수집합니다.
(3) Reference Counting (레퍼런스 카운팅)
- 각 객체가 참조된 횟수를 카운트하고, 카운트가 0이 되면 수거합니다.
- 하지만 순환 참조 문제 때문에 Java에서는 사용하지 않습니다.
(4) Concurrent Mark-Sweep (CMS)
- 애플리케이션의 실행과 병렬로 GC 작업을 수행하여 지연 시간을 최소화합니다.
(5) G1 Garbage Collector
- Java 9부터 기본 GC로 사용되며, 힙을 작은 영역으로 나누고, 가장 많은 가비지를 포함한 영역부터 수집합니다.
3. 특정 기간을 기준으로 수거하지 않음
GC는 객체가 일정 기간 사용되지 않는지에 따라 수거하지 않습니다. 대신, 객체가 참조되지 않고 도달 불가능한 상태인지 여부를 기준으로 판단합니다.
다만, Young Generation에서 Old Generation으로 객체가 이동하는 기준에는 객체의 생존 시간이 고려됩니다. 예를 들어, 특정 GC에서는 Young Generation에서 살아남은 객체가 몇 번의 GC를 통과한 뒤 Old Generation으로 이동합니다.
4. GC 동작 최적화와 개발자 역할
- GC는 자동으로 메모리를 관리하지만, 프로그래머도 다음과 같은 방식으로 GC가 더 효율적으로 동작하도록 도움을 줄 수 있습니다:
- 불필요한 객체 참조 제거: 더 이상 사용하지 않는 객체를 명시적으로 참조 해제 (null)할 수 있습니다.
- WeakReference 활용: 자주 사용하지 않는 객체는 약한 참조(WeakReference)를 사용해 GC가 쉽게 수거할 수 있도록 합니다.
- 객체 생명 주기 관리: 객체를 필요한 범위 내에서만 생성하고, 범위를 벗어나면 즉시 사용을 중지합니다.
- 프로파일링 도구 사용: VisualVM, JConsole 등 도구를 사용해 GC 동작을 분석하고 메모리 누수를 방지합니다.
5. 요약
가비지 컬렉터는 객체가 "더 이상 참조되지 않는다"고 판단될 때, 즉 GC 루트에서 도달 불가능한 상태일 때 해당 객체를 수거합니다. 이를 위한 기간은 따로 존재하지 않으며, 객체의 생존 시간, 메모리 압박 상황, GC 알고리즘에 따라 수집 시점이 결정됩니다.
필요하다면 GC 튜닝을 통해 애플리케이션 요구 사항에 맞는 메모리 관리 전략을 적용할 수도 있습니다.
자동 메모리 관리를 해주어서 프로그래머가 별로 신경 쓰지않아도 되는 것 같은데
그렇게 되면 아무래도 메모리에 부담이 큰 것 같다
GC를 어떻게 최적화하는지가 중요한듯
고마워 GPT야~
_____________________________________________________________________________________________
<객체 배열>
이게 자꾸 헷갈려서 예제를 보고 약간 변경해보면서 공부했다
참조변수의 주소값을 배열의 요소로 가지는 객체 배열 == 참조변수 배열에 대한 예제이다
객체를 생성해서 넣어주기 전은 null 이 기본값으로 들어있고,
객체를 생성해서 배열에 넣어주면 주소값이 배열에 저장되는데, println에 객체를 넣으면 toString()메서드를 호출해서
클래스이름@주소값 형태로 출력이 되는 것을 알 수 있다.
싫으면 오버라이딩 해서 다르게 출력되도록 하던가