[Java] 오토 박싱 & 오토 언박싱

 

자바에는 기본 타입과 Wrapper 클래스가 존재한다.

  • 기본 타입 : int, long, float, double, boolean 
  • Wrapper 클래스 : Integer, Long, Float, Double, Boolean 

 

박싱과 언박싱에 대한 개념을 먼저 살펴보자

박싱 : 기본 타입 데이터에 대응하는 Wrapper 클래스로 만드는 동작

언박싱 : Wrapper 클래스에서 기본 타입으로 변환

// 박싱
int i = 10;
Integer num = new Integer(i);

// 언박싱
Integer num = new Integer(10);
int i = num.intValue();

 

 

오토 박싱 & 오토 언박싱

JDK 1.5부터는 자바 컴파일러가 박싱과 언박싱이 필요한 상황에 자동으로 처리를 해준다.

// 오토 박싱
int i = 10;
Integer num = i;

// 오토 언박싱
Integer num = new Integer(10);
int i = num;

 

성능

편의성을 위해 오토 박싱과 언박싱이 제공되고 있지만, 내부적으로 추가 연산 작업이 거치게 된다.

따라서, 오토 박싱&언박싱이 일어나지 않도록 동일한 타입 연산이 이루어지도록 구현하자.

오토 박싱 연산

public static void main(String[] args) {
    long t = System.currentTimeMillis();
    Long sum = 0L;
    for (long i = 0; i < 1000000; i++) {
        sum += i;
    }
    System.out.println("실행 시간: " + (System.currentTimeMillis() - t) + " ms");
}

// 실행 시간 : 19 ms

동일 타입 연산

public static void main(String[] args) {
    long t = System.currentTimeMillis();
    long sum = 0L;
    for (long i = 0; i < 1000000; i++) {
        sum += i;
    }
    System.out.println("실행 시간: " + (System.currentTimeMillis() - t) + " ms") ;
}

// 실행 시간 : 4 ms

 

100만건 기준으로 약 5배의 성능 차이가 난다. 따라서 서비스를 개발하면서 불필요한 오토 캐스팅이 일어나는 지 확인하는 습관을 가지자.



[참고 사항]

'Language > JAVA' 카테고리의 다른 글

Intrinsic Lock  (0) 2022.05.05
Java에서의 Thread  (0) 2022.05.05
Casting(업캐스팅 & 다운캐스팅)  (0) 2022.05.05
자바 가상 머신(Java Virtual Machine)  (0) 2022.05.05
String StringBuilder StringBuffer 차이  (0) 2022.05.05

Casting(업캐스팅 & 다운캐스팅)

캐스팅이란?

변수가 원하는 정보를 다 갖고 있는 것

int a = 0.1; // (1) 에러 발생 X
int b = (int) true; // (2) 에러 발생 O, boolean은 int로 캐스트 불가

(1)은 0.1이 double형이지만, int로 될 정보 또한 가지고 있음

(2)는 true는 int형이 될 정보를 가지고 있지 않음


캐스팅이 필요한 이유는?

  1. 다형성 : 오버라이딩된 함수를 분리해서 활용할 수 있다.
  2. 상속 : 캐스팅을 통해 범용적인 프로그래밍이 가능하다.


형변환의 종류

  1. 묵시적 형변환 : 캐스팅이 자동으로 발생 (업캐스팅)Parent를 상속받은 Child는 Parent의 속성을 포함하고 있기 때문
  2. Parent p = new Child(); // (Parent) new Child()할 필요가 없음
  3. 명시적 형변환 : 캐스팅할 내용을 적어줘야 하는 경우 (다운캐스팅)다운캐스팅은 업캐스팅이 발생한 이후에 작용한다.
  4. Parent p = new Child();
    Child c = (Child) p;

예시 문제

class Parent {
	int age;

	Parent() {}

	Parent(int age) {
		this.age = age;
	}

	void printInfo() {
		System.out.println("Parent Call!!!!");
	}
}

class Child extends Parent {
	String name;

	Child() {}

	Child(int age, String name) {
		super(age);
		this.name = name;
	}

	@Override 
	void printInfo() {
		System.out.println("Child Call!!!!");
	}

}

public class test {
    public static void main(String[] args) {
        Parent p = new Child();
        
        p.printInfo(); // 문제1 : 출력 결과는?
        Child c = (Child) new Parent(); //문제2 : 에러 종류는?
    }
}

문제1 : Child Call!!!!

자바에서는 오버라이딩된 함수를 동적 바인딩하기 때문에, Parent에 담겼어도 Child의 printInfo() 함수를 불러오게 된다.

문제2 : Runtime Error

컴파일 과정에서는 데이터형의 일치만 따진다. 프로그래머가 따로 (Child)로 형변환을 해줬기 때문에 컴파일러는 문법이 맞다고 생각해서 넘어간다. 하지만 런타임 과정에서 Child 클래스에 Parent 클래스를 넣을 수 없다는 것을 알게 되고, 런타임 에러가 나오게 되는것!

'Language > JAVA' 카테고리의 다른 글

Java에서의 Thread  (0) 2022.05.05
Auto Boxing & Unboxing  (0) 2022.05.05
자바 가상 머신(Java Virtual Machine)  (0) 2022.05.05
String StringBuilder StringBuffer 차이  (0) 2022.05.05
Call by value와 Call by reference  (0) 2022.05.05

자바 가상 머신(Java Virtual Machine)

시스템 메모리를 관리하면서, 자바 기반 애플리케이션을 위해 이식 가능한 실행 환경을 제공함

 

 

JVM은, 다른 프로그램을 실행시키는 것이 목적이다.

갖춘 기능으로는 크게 2가지로 말할 수 있다.

 

  1. 자바 프로그램이 어느 기기나 운영체제 상에서도 실행될 수 있도록 하는 것
  2. 프로그램 메모리를 관리하고 최적화하는 것

 

JVM은 코드를 실행하고, 해당 코드에 대해 런타임 환경을 제공하는 프로그램에 대한 사양임

 

개발자들이 말하는 JVM은 보통 어떤 기기상에서 실행되고 있는 프로세스, 특히 자바 앱에 대한 리소스를 대표하고 통제하는 서버를 지칭한다.

자바 애플리케이션을 클래스 로더를 통해 읽어들이고, 자바 API와 함께 실행하는 역할. JAVA와 OS 사이에서 중개자 역할을 수행하여 OS에 구애받지 않고 재사용을 가능하게 해준다.

 

JVM에서의 메모리 관리


JVM 실행에 있어서 가장 일반적인 상호작용은, 힙과 스택의 메모리 사용을 확인하는 것


실행 과정

  1. 프로그램이 실행되면, JVM은 OS로부터 이 프로그램이 필요로하는 메모리를 할당받음. JVM은 이 메모리를 용도에 따라 여러 영역으로 나누어 관리함
  2. 자바 컴파일러(JAVAC)가 자바 소스코드를 읽고, 자바 바이트코드(.class)로 변환시킴
  3. 변경된 class 파일들을 클래스 로더를 통해 JVM 메모리 영역으로 로딩함
  4. 로딩된 class파일들은 Execution engine을 통해 해석됨
  5. 해석된 바이트 코드는 메모리 영역에 배치되어 실질적인 수행이 이루어짐. 이러한 실행 과정 속 JVM은 필요에 따라 스레드 동기화나 가비지 컬렉션 같은 메모리 관리 작업을 수행함

 


자바 컴파일러

자바 소스코드(.java)를 바이트 코드(.class)로 변환시켜줌


클래스 로더

JVM은 런타임시에 처음으로 클래스를 참조할 때 해당 클래스를 로드하고 메모리 영역에 배치시킴. 이 동적 로드를 담당하는 부분이 바로 클래스 로더


Runtime Data Areas

JVM이 운영체제 위에서 실행되면서 할당받는 메모리 영역임

총 5가지 영역으로 나누어짐 : PC 레지스터, JVM 스택, 네이티브 메서드 스택, 힙, 메서드 영역

(이 중에 힙과 메서드 영역은 모든 스레드가 공유해서 사용함)

PC 레지스터 : 스레드가 어떤 명령어로 실행되어야 할지 기록하는 부분(JVM 명령의 주소를 가짐)

스택 Area : 지역변수, 매개변수, 메서드 정보, 임시 데이터 등을 저장

네이티브 메서드 스택 : 실제 실행할 수 있는 기계어로 작성된 프로그램을 실행시키는 영역

 : 런타임에 동적으로 할당되는 데이터가 저장되는 영역. 객체나 배열 생성이 여기에 해당함

(또한 힙에 할당된 데이터들은 가비지컬렉터의 대상이 됨. JVM 성능 이슈에서 가장 많이 언급되는 공간임)

메서드 영역 : JVM이 시작될 때 생성되고, JVM이 읽은 각각의 클래스와 인터페이스에 대한 런타임 상수 풀, 필드 및 메서드 코드, 정적 변수, 메서드의 바이트 코드 등을 보관함



가비지 컬렉션(Garbage Collection)

자바 이전에는 프로그래머가 모든 프로그램 메모리를 관리했음 하지만, 자바에서는 JVM이 프로그램 메모리를 관리함!

JVM은 가비지 컬렉션이라는 프로세스를 통해 메모리를 관리함. 가비지 컬렉션은 자바 프로그램에서 사용되지 않는 메모리를 지속적으로 찾아내서 제거하는 역할을 함.

실행순서 : 참조되지 않은 객체들을 탐색 후 삭제 → 삭제된 객체의 메모리 반환 → 힙 메모리 재사용

'Language > JAVA' 카테고리의 다른 글

Auto Boxing & Unboxing  (0) 2022.05.05
Casting(업캐스팅 & 다운캐스팅)  (0) 2022.05.05
String StringBuilder StringBuffer 차이  (0) 2022.05.05
Call by value와 Call by reference  (0) 2022.05.05
자바 컴파일과정  (0) 2022.05.05
 

String, StringBuffer, StringBuilder


분류 String StringBuffer StringBuilder
변경 Immutable Mutable Mutable
동기화   Synchronized 가능 (Thread-safe) Synchronized 불가능.

 


1. String 특징

  • new 연산을 통해 생성된 인스턴스의 메모리 공간은 변하지 않음 (Immutable)
  • Garbage Collector로 제거되어야 함.
  • 문자열 연산시 새로 객체를 만드는 Overhead 발생
  • 객체가 불변하므로, Multithread에서 동기화를 신경 쓸 필요가 없음. (조회 연산에 매우 큰 장점)

String 클래스 : 문자열 연산이 적고, 조회가 많은 멀티쓰레드 환경에서 좋음

 

2. StringBuffer, StringBuilder 특징

  • 공통점
    • new 연산으로 클래스를 한 번만 만듬 (Mutable)
    • 문자열 연산시 새로 객체를 만들지 않고, 크기를 변경시킴
    • StringBuffer와 StringBuilder 클래스의 메서드가 동일함.
  • 차이점
    • StringBuffer는 Thread-Safe함 / StringBuilder는 Thread-safe하지 않음 (불가능)

 

StringBuffer 클래스 : 문자열 연산이 많은 Multi-Thread 환경

StringBuilder 클래스 : 문자열 연산이 많은 Single-Thread 또는 Thread 신경 안쓰는 환경

'Language > JAVA' 카테고리의 다른 글

Auto Boxing & Unboxing  (0) 2022.05.05
Casting(업캐스팅 & 다운캐스팅)  (0) 2022.05.05
자바 가상 머신(Java Virtual Machine)  (0) 2022.05.05
Call by value와 Call by reference  (0) 2022.05.05
자바 컴파일과정  (0) 2022.05.05

상당히 기본적인 질문이지만, 헷갈리기 쉬운 주제다.

call by value

값에 의한 호출

함수가 호출될 때, 메모리 공간 안에서는 함수를 위한 별도의 임시공간이 생성됨 (종료 시 해당 공간 사라짐)

call by value 호출 방식은 함수 호출 시 전달되는 변수 값을 복사해서 함수 인자로 전달함

이때 복사된 인자는 함수 안에서 지역적으로 사용되기 때문에 local value 속성을 가짐

따라서, 함수 안에서 인자 값이 변경되더라도, 외부 변수 값은 변경안됨


예시

void func(int n) {
    n = 20;
}

void main() {
    int n = 10;
    func(n);
    printf("%d", n);
}

printf로 출력되는 값은 그대로 10이 출력된다.

 

call by reference

참조에 의한 호출

call by reference 호출 방식은 함수 호출 시 인자로 전달되는 변수의 레퍼런스를 전달함

따라서 함수 안에서 인자 값이 변경되면, 아규먼트로 전달된 객체의 값도 변경됨

void func(int *n) {
    *n = 20;
}

void main() {
    int n = 10;
    func(&n);
    printf("%d", n);
}

printf로 출력되는 값은 20이 된다.



Java 함수 호출 방식

자바의 경우, 함수에 전달되는 인자의 데이터 타입에 따라 함수 호출 방식이 달라짐

  • primitive type(원시 자료형) : call by value
  • int, short, long, float, double, char, boolean
  • reference type(참조 자료형) : call by reference
  • array, Class instance

자바의 경우, 항상 call by value로 값을 넘긴다.

C/C++와 같이 변수의 주소값 자체를 가져올 방법이 없으며, 이를 넘길 수 있는 방법 또한 있지 않다.

reference type(참조 자료형)을 넘길 시에는 해당 객체의 주소값을 복사하여 이를 가지고 사용한다.

따라서 원본 객체의 프로퍼티까지는 접근이 가능하나, 원본 객체 자체를 변경할 수는 없다.

아래의 예제 코드를 봐보자.

User a = new User("gyoogle");   // 1

foo(a);

public void foo(User b){        // 2
    b = new User("jongnan");    // 3
}

/*
==========================================

// 1 : a에 User 객체 생성 및 할당(새로 생성된 객체의 주소값을 가지고 있음)
 
 a   -----> User Object [name = "gyoogle"]
 
==========================================

// 2 : b라는 파라미터에 a가 가진 주소값을 복사하여 가짐

 a   -----> User Object [name = "gyoogle"]
               ↑     
 b   -----------
 
==========================================

// 3 : 새로운 객체를 생성하고 새로 생성된 주소값을 b가 가지며 a는 그대로 원본 객체를 가리킴
 
 a   -----> User Object [name = "gyoogle"]
                   
 b   -----> User Object [name = "jongnan"]
 
*/

파라미터에 객체/값의 주소값을 복사하여 넘겨주는 방식을 사용하고 있는 Java는 주소값을 넘겨 주소값에 저장되어 있는 값을 사용하는 call by reference라고 오해할 수 있다.

이는 C/C++와 Java에서 변수를 할당하는 방식을 보면 알 수 있다.

// c/c++ 
 
 int a = 10;
 int b = a;
 
 cout << &a << ", " << &b << endl; // out: 0x7ffeefbff49c, 0x7ffeefbff498
 
 a = 11;
 
 cout << &a << endl; // out: 0x7ffeefbff49c

//java
 
 int a = 10;
 int b = a;
 
 System.out.println(System.identityHashCode(a));    // out: 1627674070
 System.out.println(System.identityHashCode(b));    // out: 1627674070
 
 a = 11;

 System.out.println(System.identityHashCode(a));    // out: 1360875712

C/C++에서는 생성한 변수마다 새로운 메모리 공간을 할당하고 이에 값을 덮어씌우는 형식으로 값을 할당한다. (* 포인터를 사용한다면, 같은 주소값을 가리킬 수 있도록 할 수 있다.)

Java에서 또한 생성한 변수마다 새로운 메모리 공간을 갖는 것은 마찬가지지만, 그 메모리 공간에 값 자체를 저장하는 것이 아니라 값을 다른 메모리 공간에 할당하고 이 주소값을 저장하는 것이다.

이를 다음과 같이 나타낼 수 있다.

  C/C++        |        Java
               |
a -> [ 10 ]    |   a -> [ XXXX ]     [ 10 ] -> XXXX(위치)
b -> [ 10 ]    |   b -> [ XXXX ]
               |
             값 변경
a -> [ 11 ]    |   a -> [ YYYY ]     [ 10 ] -> XXXX(위치)
b -> [ 10 ]    |   b -> [ XXXX ]     [ 11 ] -> YYYY(위치)

b = a;일 때 a의 값을 b의 값으로 덮어 씌우는 것은 같지만, 실제 값을 저장하는 것과 값의 주소값을 저장하는 것의 차이가 존재한다.

즉, Java에서의 변수는 [할당된 값의 위치]를 [값]으로 가지고 있는 것이다.

C/C++에서는 주소값 자체를 인자로 넘겼을 때 값을 변경하면 새로운 값으로 덮어 쓰여 기존 값이 변경되고, Java에서는 주소값이 덮어 쓰여지므로 원본 값은 전혀 영향이 가지 않는 것이다. (객체의 속성값에 접근하여 변경하는 것은 직접 접근하여 변경하는 것이므로 이를 가리키는 변수들에서 변경이 일어난다.)

객체 접근하여 속성값 변경

a : [ XXXX ]  [ Object [prop : ~ ] ] -> XXXX(위치)
b : [ XXXX ]

prop : ~ (이 또한 변수이므로 어딘가에 ~가 저장되어있고 prop는 이의 주소값을 가지고 있는 셈)
prop : [ YYYY ]    [ ~ ] -> YYYY(위치)

a.prop = * (a를 통해 prop를 변경) 

prop : [ ZZZZ ]    [ ~ ] -> YYYY(위치)
                   [ * ] -> ZZZZ

b -> Object에 접근 -> prop 접근 -> ZZZZ

위와 같은 이유로 Java에서 인자로 넘길 때는 주소값이란 값을 복사하여 넘기는 것이므로 call by value라고 할 수 있다.

출처 : Is Java “pass-by-reference” or “pass-by-value”? - Stack Overflow

 

정리

Call by value의 경우, 데이터 값을 복사해서 함수로 전달하기 때문에 원본의 데이터가 변경될 가능성이 없다. 하지만 인자를 넘겨줄 때마다 메모리 공간을 할당해야해서 메모리 공간을 더 잡아먹는다.

Call by reference의 경우 메모리 공간 할당 문제는 해결했지만, 원본 값이 변경될 수 있다는 위험이 존재한다.

'Language > JAVA' 카테고리의 다른 글

Auto Boxing & Unboxing  (0) 2022.05.05
Casting(업캐스팅 & 다운캐스팅)  (0) 2022.05.05
자바 가상 머신(Java Virtual Machine)  (0) 2022.05.05
String StringBuilder StringBuffer 차이  (0) 2022.05.05
자바 컴파일과정  (0) 2022.05.05

들어가기전

자바는 OS에 독립적인 특징을 가지고 있습니다. 그게 가능한 이유는 JVM(Java Vitual Machine) 덕분인데요. 그렇다면 JVM(Java Vitual Machine)의 어떠한 기능 때문에, OS에 독립적으로 실행시킬 수 있는지 자바 컴파일 과정을 통해 알아보도록 하겠습니다.

자바 컴파일 순서

  1. 개발자가 자바 소스코드(.java)를 작성합니다.
  2. 자바 컴파일러(Java Compiler)가 자바 소스파일을 컴파일합니다. 이때 나오는 파일은 자바 바이트 코드(.class)파일로 아직 컴퓨터가 읽을 수 없는 자바 가상 머신이 이해할 수 있는 코드입니다. 바이트 코드의 각 명령어는 1바이트 크기의 Opcode와 추가 피연산자로 이루어져 있습니다.
  3. 컴파일된 바이크 코드를 JVM의 클래스로더(Class Loader)에게 전달합니다.
  4. 클래스 로더는 동적로딩(Dynamic Loading)을 통해 필요한 클래스들을 로딩 및 링크하여 런타임 데이터 영역(Runtime Data area), 즉 JVM의 메모리에 올립니다.
    • 클래스 로더 세부 동작
      1. 로드 : 클래스 파일을 가져와서 JVM의 메모리에 로드합니다.
      2. 검증 : 자바 언어 명세(Java Language Specification) 및 JVM 명세에 명시된 대로 구성되어 있는지 검사합니다.
      3. 준비 : 클래스가 필요로 하는 메모리를 할당합니다. (필드, 메서드, 인터페이스 등등)
      4. 분석 : 클래스의 상수 풀 내 모든 심볼릭 레퍼런스를 다이렉트 레퍼런스로 변경합니다.
      5. 초기화 : 클래스 변수들을 적절한 값으로 초기화합니다. (static 필드)
  5. 실행엔진(Execution Engine)은 JVM 메모리에 올라온 바이크 코드들을 명령어 단위로 하나씩 가져와서 실행합니다. 이때, 실행 엔진은 두가지 방식으로 변경합니다.
    1. 인터프리터 : 바이트 코드 명령어를 하나씩 읽어서 해석하고 실행합니다. 하나하나의 실행은 빠르나, 전체적인 실행 속도가 느리다는 단점을 가집니다.
    2. JIT 컴파일러(Just-In-Time Compiler) : 인터프리터의 단점을 보완하기 위해 도입된 방식으로 바이트 코드 전체를 컴파일하여 바이너리 코드로 변경하고 이후에는 해당 메서드를 더이상 인터프리팅 하지 않고, 바이너리 코드로 직접 실행하는 방식입니다. 하나씩 인터프리팅하여 실행하는 것이 아니라 바이트 코드 전체가 컴파일된 바이너리 코드를 실행하는 것이기 때문에 전체적인 실행속도는 인터프리팅 방식보다 빠릅니다.

Reference (추가로 읽어보면 좋은 자료)

[1] https://steady-snail.tistory.com/67

[2] https://aljjabaegi.tistory.com/387

'Language > JAVA' 카테고리의 다른 글

Auto Boxing & Unboxing  (0) 2022.05.05
Casting(업캐스팅 & 다운캐스팅)  (0) 2022.05.05
자바 가상 머신(Java Virtual Machine)  (0) 2022.05.05
String StringBuilder StringBuffer 차이  (0) 2022.05.05
Call by value와 Call by reference  (0) 2022.05.05

+ Recent posts