자바에서 GC.. 한번쯤 들어봤을 것이다.

그런데 책에서 보니 GC 가 Garbage Collector 인지, Garbage Collection 인지 잘 구분이 안 간다. 


그래서 Oracle doc 에서 대충 검색을 때려보니(?)

..... 얘네도 그렇게 뚜렷히 구분하는 것 같지는 않은 듯.

Garbage Collector (GC) 해놓은 문서도 있고, Garbage Collection (GC) 해놓은 문서도 있다.

음....뭐 그런가 보다. 지금 이 얘기 하려는 건 아니니까.



C와는 다르게 JAVA 는 메모리 해제 작업을 따로 안 해주는 게 특징이다.

GC 가 알아서 '사용했다가 더 이상 사용되지 않는 녀석들'을 치워주기 때문이다.


JDK 7부터 G1(Garbage first) 를 제외한 나머지 JVM 은 다음과 같이

Heap 이라는 공간에 객체들을 관리한다고 한다. - 여기서부터는 책의 내용 일부를 그대로 가져온다 -


< 자바의 힙 영역 >


Eden 

Survivor 

Space 

Virtual 

 

Virtual 

 

Virtual 



초록색 부분은 Young 영역, 파란색 부분은 Old 영역, 분홍색 부분은 Perm 영역 이라고 한다.


가장 왼쪽의 Young 영역에는 젊은 객체들이, Old 영역에는 늙은 객체들이,

Perm 영역에는 클래스나 메소드에 대한 정보가 쌓인다. (Perm 에는 더 많은 정보들을 담고있지만 생략)


- Young 영역은 Eden 과 2 개의 Survivor 영역으로 나뉘는데, 이 중에서 객체를 생성하자마자

저장되는 장소는 Eden 이다. 일반적으로 Java 에서 메모리가 살아가는 과정은 다음과 같다.


1) Eden 영역에서 객체가 생성된다.

2) Eden 영역이 꽉 차면 살아있는 객체만 Survivor 영역으로 복사되고 나서 다시 Eden 영역을 채우게 된다.

3) Survivor 영역이 꽉 차면 다른 Survivor 영역(Space)로 객체가 복사된다.

이때, Eden 영역에 있는 객체들 중 살아있는 객체들도 다른 Survivor 영역으로 간다.

즉 ! Survivor 영역의 둘 중 하나는 반드시 비어 있어야만 한다.


지금까지 말한 것이 minor GC 혹은 Young GC 라고 부른다. (여기서 GC는 가비지 콜렉션)


그러다가 오래 살아있는 객체들은 Old 영역으로 이동한다. 지속적으로 이동하다가 Old 영역이 꽉 차면

GC 가 발생하는데 이것을 major GC 혹은 Full GC 라고 부른다.



Young GC 와 Full GC 중 Young GC 가 더 빠르다.

일반적으로 더 작은 공간이 할당되고 객체들을 처리하는 방식도 다르기 때문이다.

그렇다고 전체의 Heap 영역을 Young 영역으로 만들면 장애가 생길 확률이 높으니 신중하자.



자바의神 Vol.1 내용은 여기까지 이다. - 이 포스팅은 부록 부분에서 발췌 -

사실 더 많은 내용들이 있어도 굳이 포스팅 안 한 이유는 귀찮아서

대부분 알고 있는 내용이고... (하지만 누가 물어보면 잘 대답할 수 있을지는 조금 걱정) 

지금 공부할 건 많고.. 디자인 패턴 포스팅 하는 것도 버겁다 ㅠㅠㅋㅋㅋ


아무튼 Vol.2 로 넘어갈지 고민중이다.


by kelicia 2014. 5. 26. 21:25


비트 시프트 연산자란 말그대로 bit 를 shift 시켜주는 녀석이다.

연산자는 다음과 같이 3가지가 있다.

(1) << : 왼쪽으로 이동

(2) >> : 오른쪽으로 이동

(3) >>> : unsigned, 오른쪽으로 이동


좀 더 쉽게 이해해보자.

'A << B' 라는 수식이 있다고 하면, A 라는 녀석의 비트 값을 B 만큼 왼쪽으로 이동시키겠다.

라고 이해하면 된다. (단, A와 B는 정수이다)



이제 하나씩 알아보자.


(1) << 연산자

int a = 0x00000010;    // 10진수 '16' 을 16진수로 표현

a = a << 2;


연산 前, a = 0000 0000 0000 0000 0000 0000 0001 0000    // 16

연산 後, a = 0000 0000 0000 0000 0000 0000 0100 0000    // 64


색상으로 표시된 부분에 주목하자.

비트 연산을 할 때는 비트가 rotation 되는 게 아니라 

왼쪽 끝부분이 그냥 잘리고 없어진다. C언어도 마찬가지이다.


음수의 경우도 살펴보겠다. 

(참고로 int 는 32 비트지만 맨 앞의 비트는 부호 역할을 하므로 - 양수는 0, 음수는 1 -

 최소값은 -2^31, 최대값은 2^31-1 이다)


a = -2147483648    // -2^31

a <<= 1;


연산 前, a = 1000 0000 0000 0000 0000 0000 0000 0000    // -2147483648 = -2^31

연산 後, a = 0000 0000 0000 0000 0000 0000 0000 0000    // 0


<< 연산자를 정리하자면,

비트 값들을 왼쪽으로 이동시킨 후에 오른쪽의 빈 공간에는 모두 0 으로 채운다.



(2) >> 연산자

int a = 2147483647;    // 2^31-1

a >>= 1;


연산 前, a = 0111 1111 1111 1111 1111 1111 1111 1111    // 2147483647 = 2^31-1

연산 後, a = 0011 1111 1111 1111 1111 1111 1111 1111    // 1073741823 = 2^30-1


이번엔 오른쪽 끝부분이 잘리고 없어진다.

하지만 << 연산자와 똑같다고 생각하면 오산이다.

음수의 경우를 살펴보겠다.


a = -2147483648    // -2^31

a >>= 1;

a >>= 30;


연산 前, a = 1000 0000 0000 0000 0000 0000 0000 0000    // -2147483648 = -2^31

연산 中, a = 1100 0000 0000 0000 0000 0000 0000 0000    // -1073741824 = -2^30

연산 後, a = 1111 1111 1111 1111 1111 1111 1111 1111    // -1 = -2^0


양수와 음수의 >> 연산 결과가 다르다는 것을 눈치챘을 것이다.


>>연산자를 정리하자면,

비트 값들을 오른쪽으로 이동시킨 후에 왼쪽의 빈 공간에는

양수는 모두 0 으로,

음수는 모두 1 로 채운다.



(3) >>> 연산자

a = -2147483648    // -2^31

a >>>= 1;

a >>>= 30;


연산 前, a = 1000 0000 0000 0000 0000 0000 0000 0000    // -2147483648 = -2^31

연산 中, a = 0100 0000 0000 0000 0000 0000 0000 0000    // 1073741824 = 2^30

연산 後, a = 0000 0000 0000 0000 0000 0000 0000 0001    // 1 = 2^0


>>> 연산자는 부호를 신경쓰지 않는다.

그러므로 정리하자면,

비트 값들을 오른쪽으로 이동시킨 후에 왼쪽의 빈 공간은 모두 0 으로 채운다.



* 주의점 *

1. 연산 가능한 타입이 byte, short, int, long 타입과 char 타입이 있는데..

int 보다 작은 비트의 타입 byte, short, char 는 int 로 변환되서 Shift 가 된다고 한다.

그래서 결과값을 int 로 받지 않고 기존의 타입 그대로 받으면 문제가 발생하게 된다.


byte b = -1;

b = b >>> 2;    // error

b = (byte) b >>> 2;    // non-error but...


연산 前, b = 1111 1111 1111 1111 1111 1111 1111 1111;    // -1

연산 中, b = 0011 1111 1111 1111 1111 1111 1111 1111;    // 1073741823 = 2^30-1

연산 後, b = 1111 1111;                                              // -1, 위 결과의 오른쪽으로부터 8 bit 만 끊어서 캐스팅.


-1 값을 갖는 byte 변수에 >>> 연산을 했는데... 음수가 나오는 결과가 나오게 된다.


2. 비트 연산자의 피연산자 ( A << B 에서 B에 해당하는 부분 ) 그대로의 값으로

시프트 하는 것이 아니라 나머지 연산(%)의 결과값으로 시프트를 한다.


이해가 잘 안되니 예제를 보자.

int c = -1;

c = c >>> 32;


c 의 결과값은 32 % 32 = 0 이므로 아무런 시프트가 되지 않은 값, 똑같이 -1 을 갖는다.

byte 타입이라면 % 8, short 타입이라면 % 16, long 타입이라면 % 64 의 결과값으로 시프트 된다.



(출처 : 자바의神 Vol.1 , http://blog.naver.com/salagswk/150046360210,

http://docs.oracle.com/javase/tutorial/java/nutsandbolts/op3.html)


by kelicia 2014. 5. 26. 20:57

* 어노테이션(Annotation) 이란 ?

- 클래스나 메소드 등의 선언 시에 @ 를 사용하는 것을 말한다.

참고로 클래스, 메소드, 변수 등 모든 요소에 선언할 수 있다.

메타 데이터(Metadata) 라고도 불리며, JDK 5 부터 등장했다고 한다.



* 언제 사용할까?

- 컴파일러에게 정보를 알려주거나

- 컴파일 할 때와 설치 시의 작업을 지정하거나

- 실행할 때 별도의 처리가 필요할 때



* 일반적으로 사용가능한 Annotation 3가지 (JDK 6 기준)


(1) @Override

- 해당 메소드가 부모 클래스에 있는 메소드를 오버라이드 했다는 것을 명시적으로 선언한다.

자식 클래스에 메소드가 여러 개 있을 때 어떤 메소드가 오버라이드 된 것인지

쉽게 알 수 있고, 깜빡하고 빼먹은 매개변수가 있는지 컴파일러에게 알려달라고 생각하면 된다.

이클립스 같이 편리한 개발툴을 사용하다 보면 자주 볼수 있는 키워드다.


(2) @Deprecated

- 더 이상 사용되지 않은 클래스나 메소드 앞에 추가해준다.

'그냥 지워버리면 되는 거 아닌가?' 라고 생각할 수 있지만 여러 사람이 개발하는 프로젝트에서

갑자기 있던 걸 훅 지워버리면... 욕 엄청 먹을 수 있다ㅋ 이런 알림을 통해 서서히 지우자.

나의 경우 안드로이드 개발할 때 API 문서에서 이 키워드를 몇 번 본 적이 있다.


(3) @SuppressWarnings

- 프로그램에는 문제가 없는데 간혹 컴파일러가 경고를 뿜을 때가 있다.

내 성격상 경고 뿜어대는 게 은근 신경쓰이고 거슬릴 때가 있다. 

그럴 때는 이 Annotation을 추가해서 컴파일러에게 

'나도 알고 있고 일부러 그런 거다 그러니 좀 닥치고 있어'

라고 말해주는 거라고 생각하면 된다. 하지만 거슬린다고 너무 남용하진 말자.


- 참고로 다른 Annotation 과 다르게 속성값을 지정해 줄 수도 있다.

ex) @SuppressWarnings("deprecation"), @SuppressWarnings("serial")

개발하다보면 특히 저 serial 어쩌구 저쩌구를 자주 보는데 그럴 때는

http://blog.naver.com/fochaerim/70105895049 여기 링크를 통해 해결하자.



* Meta Annotation

- Annotation 을 선언할 때 사용한다. 

메타 어노테이션은 다음과 같이 4가지가 존재하고 반드시 모두 사용할 필요는 없다.


(1) @Target 

- 어떤 것에 어노테이션을 적용할 지 선언할 때 사용한다. 적용 가능 대상은 아래와 같다.


요소 타입 

대상 

CONSTRUCTOR 

생성자 선언시 

FIELD 

enum 상수를 포함한 필드값 선언시 

LOCAL_VARIABLE 

지역 변수 선언시 

METHOD 

메소드 선언시 

PACKAGE 

패키지 선언시 

PARAMETER 

매개 변수 선언시 

TYPE 

클래스, 인터페이스, enum 등 선언시 


(2) @Retention

- 얼마나 오래 어노테이션 정보가 유지되는 지를 선언한다.


대상

SOURCE

어노테이션 정보가 컴파일시 사라짐 

CLASS 

 클래스 파일에 있는 어노테이션 정보가 컴파일러에 의해 참조 가능함.

 하지만 가상 머신에서는 사라짐.

RUNTIME 

실행시 어노테이션 정보가 가상 머신에 의해서 참조 가능 


(3) @Documented

- 해당 어노테이션에 대한 정보가 JavaDocs(API) 문서에 포함된다는 것을 선언한다.


(4) @Inherited

- 모든 자식 클래스가 부모 클래스의 어노테이션을 사용할 수 있다는 것을 선언한다.


+ 추가로 @interface 도 존재한다.

이 어노테이션은 어노테이션을 선언할 때 사용한다.



솔직히 메타 어노테이션은 당최 무슨 말인지 잘 이해가 안 갔다.

이럴 때는 코드를 보는 게 더 빠르니 예제 코드 ㄱㄱ~

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface UserAnnotation {
    public int number();
    public String text() default "This is first annotation"
}

@interface로 선언했으니 이제 @UserAnnotation 으로 사용 가능한

어노테이션이 하나 만들어진 셈이다.


이제 만든 것을 사용해보고, 어노테이션에 선언한 값들을 확인해보자.

@UserAnnotation(number=0)
public class UserAnnotationSample 
{
    @UserAnnotation(number=1)
    public static void main(String[] args) {
        UserAnnotatonSample sample = new UserAnnotation();
        sample.checkAnnotations(UserAnnotationSample.class);
    }

    @UserAnnotation(number=2)
    public void annotationSample1() {
    }
    @UserAnnotation(number=3, text="second")
    public void annotationSample2() {
    }
    @UserAnnotation(number=4, text="third")
    public void annotationSample3() {
    }

    public void checkAnnotations(Class useClass){
        Method[] methods = useClass.getDeclaredMethods();
        for ( Method tempMethod:methods ){
            UserAnnotation annotation = tempMethod.getAnnotation(UserAnnotation.class);
            if ( annotation != null ){
                int number = annotation.number();
                String text = annotation.text();
                System.out.println(tempMethod.getName() + "() : number=" + number + " text=" + text);
            } else {
                System.out.println(tempMethod.getName() + "() : annotation is null");
            }
        }
    }
}

바로 위의 checkAnnotations() 에서

Class, Method 라는 것은 Java 의 Reflection 이라는 API 에서 제공하는 클래스들이라고 한다.


그리고 결과 값은 아래와 같이 얻을 수 있다.


checkAnnotations() : annotation is null

annotationSample1() : number=2 text=This is first annotation

annotationSample2() : number=3 text=second

annotationSample3() : number=4 text=third

main() : number=1 text=This is first annotation



* Annotation 은 상속이 안 된다.

- enum 클래스가 상속을 지원하지 않듯이 Annotation 도 마찬가지로 상속이 안 되므로 확장이 불가능하다. 



어노테이션을 직접 만들 일은 거의 없지만...

그래도 상위의 Annotation 3개는 꼭 알아두자 - @Override, @Deprecated, @SuppressWarnings

나같이 초보 개발자도 자주 마주치는 키워드라서 그리 생소하지 않은 요소이다.



by kelicia 2014. 5. 24. 18:05


Java 에서는 클래스 안에 클래스가 들어갈 수 있다.

이를 Nested Class 라고 부르고 다음과 같이 분류할 수 있다.



* NestClass 를 만드는 이유 :

1) 코드를 간단하게 표현하기 위함

1-1) 소스의 가독성과 유지보수성을 높이고 싶을 때

2) Static Nested Class : 

- 한 곳에서만 사용되는 클래스를 논리적으로 묶어서 처리할 필요가 있을 때

3) Inner Class : 

- 캡슐화가 필요할 때 (ex. class A에 private 변수가 있다고 하자. 

이 변수에 접근하고 싶은 class B를 선언하고 B를 외부에 노출시키고 싶지 않을 경우가 이에 속한다) 



그런데 오해가 있을 수 있는데 아래의 코드를 보자. (PublicClass.java)

public class PublicClass {
}
class JustNotPublicClass {
}

1개의 자바 파일 안에 2개의 클래스가 선언되어도 문제될 건 없다.

하지만 JustNotPublicClass 는 단순히 Public Class 가 아닐 뿐이지, 이를 내부 클래스라고 할 수 없다.



* Static Nested Class :

Static 이기 때문에 외부 클래스 변수라고 다 참조하지 않는다. static 변수만 참조 가능.

길게 설명할 것 없이 어떻게 선언되는지 살펴보자.

public class OuterOfStatic { 
    static class StaticNested { 
        private int value = 0; 
        public int getValue() { 
            return value; 
        } 
    }
}

위의 클래스를 컴파일 하면 2개의 클래스가 만들어진다.

(OuterOfStatic.class, OuterOfStatic$StaticNested.class)


이렇게 선언해놓고 StaticNested 클래스 객체를 만들어 보겠다.

public static main(String[] args) { 
    OuterOfStatic.StaticNested staticNested = new OuterOfStatic.StaticNested(); 
    System.out.println(staticNested.getValue()); 
}

위와 같이 Static스럽게(?) 객체를 생성하면 된다.


왜 이렇게까지 하면서 만들어야 될까?

답변을 하자면, 클래스를 묶어서 가독성을 높이기 위해서이다.



* Inner Class :

여기서는 객체 생성 방법이 조금 다르다.

public class OuterOfInner { 
    class Inner { 
        private int value = 0; 
        public int getValue() { 
            return value; 
        } 
    }
}
public static main(String[] args) {
    OuterOfInner outer = new OuterOfInner();
    OuterOfInner.Inner inner = outer.new Inner();
    System.out.println(inner.getValue()); 
}


캡슐화를 위해 Inner Class 를 사용한다고 했었다.

A 클래스에서 어떤 공통적인 작업을 수행하는 B 클래스가 필요한데,

다른 클래스에서는 B 클래스가 전혀 필요가 없을 때는 이렇게 내부 클래스를 만들어 사용한다.


내부 클래스는 GUI 관련 프로그램을 개발할 때 가장 많이 사용된다고 한다.

(ex. EventListener)



다음은 익명 내부 클래스(Anonymous Inner Class)의 예제 코드이다.

public class NestedSample {
    public void setButtonListener() {
        Button btn = new Button();
        btn.setListener(new EventListener() {
            public void onClick() {
                System.out.println("Button was Clicked");
            }
        });
    } 
}

예제 코드라 코드가 조금 요상하지만_-_ 

어쨌든 주목할 부분은 Line 4 부터다. Java 로 GUI 코딩해보신 분들은 익숙할 법한 코드이다.


위와 같이 클래스의 이름도 없고, 객체의 이름도 없으면 외부에서 참조할 수가 없다. 


만약 재사용하고 싶다면

EventListener listener = new EventListener(){ // 생략 };

위와 같이 선언하면 된다. 또는 일반 내부 클래스처럼 만들어서 재사용해도 된다.



* 익명 클래스의 장점 :

클래스를 만들고 그 클래스를 호출하면 그 정보는 메모리에 올라간다.

즉, 클래스를 많이 만들면 만들수록 메모리는 많이 필요해지고 애플리케이션을 시작할 때 

더 많은 시간이 소요된다. 따라서 자바에서는 이렇게 익명으로 객체 생성이 가능하게끔 되어있다.


익명 클래스나 내부 클래스는 모두 다른 클래스에서 재사용할 일이 없을 때 만들어야 한다.

가독성을 높일 수 있다는 장점이 있지만 남용할 경우 가독성을 떨어뜨리기 때문에 주의하자.



* Nested Class 의 특징 :

- Static Nested 클래스는 외부 클래스의 static 변수만 참조할 수 있다.

- 그 외의 내부 클래스(Inner, Anonymous)는 외부 클래스의 어떤 변수라도 참조할 수 있다. (private도 가능!)

- 반대로 외부 클래스도 내부 클래스(Static, Inner, Anonymous)의 인스턴스 변수에 모두 접근할 수 있다.


위의 특징들은 꼭 기억하자.


by kelicia 2014. 5. 22. 18:03

* JAVA 의 접근 제어자 (Access Modifier)

- public : 

모든 클래스들이 접근 가능.

- protected : 

같은 패키지 내에 있거나 또는 상속받은 경우에만 접근 가능.

- package-private : 

아무런 접근 제어자를 명시해주지 않는 경우에 해당. 

같은 패키지 내에 있을 때만 접근 가능. (참고로 나는 default 제어자로 알고 있었다)

- private :

해당 클래스 내에서만 접근 가능.



* 클래스 접근 제어자 선언할 때 유의할 점

- 자바에서는 1개의 .java 파일 안에 여러 개의 클래스를 선언할 수 있다.

PublicClass.java

package test;
class PublicClass {
    public static void main(String[] args){
    }
}
class PublicSecondClass {
}

위의 코드 처럼 작성이 가능하고 컴파일도 정상적으로 된다.


package test;
public class PublicClass {
    public static void main(String[] args){
    }
}
class PublicSecondClass {
}

위 코드도 마찬가지이다.


하지만 아래의 경우 에러가 발생한다.

package test;
class PublicClass {
    public static void main(String[] args){
    }
}
public class PublicSecondClass {    // error
}


결국 3가지 코드를 통해 말하고자 하는 바는,

public 으로 선언된 클래스가 파일 내에 있다면

그 소스코드 파일의 이름은 public 인 클래스의 이름과 동일해야만 한다는 점이다.




* 오버라이딩 (Overriding) : 상속과 관련.

- 부모 클래스의 메소드를 자식인 자신의 클래스 내에서 재정의한다.

- 접근 제어자, 리턴 타입, 메소드 이름, 매개 변수 타입 및 갯수가 모두 동일.

- 단! 접근 제어자는 부모 클래스의 메소드와 반드시 동일할 필요는 없으나 접근 권한이 확장되는 경우만 가능하다.

(접근 권한 : public > protected > package-private > private)


예를 들어 부모 클래스의 메소드 접근 제어자가 public 일 때,

자식 클래스의 메소드 접근 제어자가 private 인 경우는 접근 권한이 축소된 경우라서 컴파일 에러.



* 오버로딩 (Overloading) : 한 클래스 내에서.

- 메소드 이름은 동일하되 매개 변수만 다르게 하는 것.

- 반환 타입은 상관 없다. 중요한 건 같은 메소드 이름, 매개 변수가 들어가는 '()' 안의 내용이다.


public int add(int x, int y) { }
public double add(double x, double y) { }
public void print(int intData, long longData, String strData) { }
public void print(int intData, String strData) { }
public void print(String strData, int intData) { }


위 코드 라인 4, 5와 같이 타입의 순서가 달라도 다른 메소드 처럼 인식된다는 점.


by kelicia 2014. 5. 21. 03:33
| 1 |