19장. 이쯤에서 자바의 역사와 JVM에 대해서 알아보자

공부하는 감자·2023년 12월 10일
0

자바의 신 3판

목록 보기
19/30

들어가기 전

『자바의 신 3판』 을 읽고 내용 정리 및 공부한 내용을 정리한 글입니다.
서적: 자바의 신 3판 구입처

여기서부터는 2권의 내용입니다. 2권은 1장부터 다시 시작하는데, 편의상 1권에 이어 19장으로 카운팅했습니다.

내용 정리

자바의 역사

1991년

  • 제임스 고슬링, Mike Sheridan, Patrick Naughtom이 TV와 시청자가 서로 상호 작용할 수 있는(interactive 한) 것을 만들기 위해 “Green” 프로젝트가 시작되었다.
  • 이때 “Oak”라는 언어가 탄생했다.

1995년

  • “Oak”라는 이름이 이미 사용되어서 “Java”라는 이름으로 바꾸었다.
  • Java는 커피 이름이기도 해서, 커피가 로고가 되었다.
  • “Write Once, Run Anywhere” (WORA) 라는 모토가 만들어졌다.
  • 여러 플랫폼에서 수행할 수 있는 개발 언어를 목표로 개발되었다.

1996년

  • JDK 1.0이 출시되었다.
  • JDK는 Java Development Kit의 약자다.

1997년

  • JDK 1.1 출시

1998년

  • J2SE라는 이름으로 자바의 기본 명칭이 바뀌면서, JDK 1.2가 출시되었다.
  • J2SE는 Java 2 Standard Edition의 약자이며 기존의 JDK를 의미하는 것인데, 기업 등에서 만드는 시스템을 개발하기 위한 J2EE라는 Enterprise Edition과 블랙베리와 같은 전화기에서 아직도 사용하고 있는 J2ME라는 Micro Edition과의 혼동을 막기 위해 명칭을 변경했다.

2000년~2006년

  • J2SE 1.3, J2SE 1.4 출시
  • J2SE 1.5부터는 앞의 “1.”을 빼고 J2SE 5로 불리게 되었다.
  • 약간의 개선이 이루어진 Java SE 6

2011년~2014년

  • Oracle에서 Sun Microsystems를 인수했다.
  • 오랜 기간 업그레이드 준비를 마친 Java SE 7와 Java SE 8이 정식 릴리즈 되었다.

💡 자세한 것은 참고 사이트의 오라클 공식 타임라인을 보면 된다.

JDK 플랫폼에 따른 차이

자바는 Oracle에서만 만드는 것이 아니다. 각 Java 버전에서 제공되어야 하는 표준 문서가 만들어지면, 그 기준에 해당하는 각 벤더에 맞는 JDK가 별도로 만들어진다.

우리나라에서 많이 사용하는 서버는 다음과 같다.

  • IBM 및 HP에서 만든 서버
    • 별도의 운영체제를 갖고 있으며, 그 OS에 최적화되어 있는 JDK를 개발하여 사용하고 있다.
  • 예전 Sun에서 만든 Solaris

리눅스의 경우에는 Oracle에서 만드는 기본 JDK를 주로 사용한다.

어떤 OS에서 개발하든지 JDK의 버전만 맞으면, 적용할 OS에서 컴파일만 하면 애플리케이션 실행에 문제가 없다.

즉, Oracle JDK로 개발하고 HP의 JDK에서 컴파일하더라도 전혀 문제는 발생하지 않는다는 것이다(하지만 각 회사별 JDK의 내부 동작 구조는 상이하다).

이 외의 라이선스 문제가 되는 부분을 제거한 완전 오픈소스 버전의 OpenJDK와, JRockit이라는 JDK가 있다.

💡 Sun JDK와 Oracle JRockit

Sun JDK는 인터프리터를 사용하고, JRockit은 JIT 컴파일러만 사용한다. 장기 실행 프로세스에서 JRockit이 조금 더 나은 성능을 보인다고 한다.

자바에서 사용되는 용어들

JDK, J2SE, Java SE

먼저, 각 용어는 다음의 약자다.

  • JDK: Java Development Kit
  • J2SE: Java 2 Standard Edition
  • Java SE: Java Standard Edition

여기서 Java 2에서 2가 빠진 것은 Java SE 6가 출시되면서부터이며, 마케팅을 위해서 보다 쉽게 자바를 부를 수 있도록 Java로 통칭했다고 한다.

JDK와 JRE

  • JDK: Java Development Kit
  • JRE: Java Runtime Environment

이름에서 알 수 있듯이, JRE는 실행만을 위한 환경이다. JDK는 JRE(실행환경)도 포함하고 있다.

각각 어떤 걸 제공하는 지는 아래 그림을 참고할 것.

Java Platform Standard Edition 8 Documentation

💡 출처: Java Platform Standard Edition 8 Documentation

자바 언어의 특징

💡 아래 특징들은 자바 개발 초기에 정해진 것으로 지금의 상황과는 약간 다른 부분이 있다. 하지만, 이 내용의 근간은 달라지지 않고 계속 유지되고 있으므로 기억해두면 좋을 것이다.
자세한 설명: Oracle 홈페이지

책에서는 이를 간추려서 다음 다섯 가지를 소개하고 있다.

  • 자바는 단순하고, 객체지향이며, 친숙해야 한다.
  • 자바는 견고하며, 보안상 안전하다.
  • 자바는 아키텍처에 중립적이어야 하며 포터블해야 한다.
  • 자바는 높은 성능을 제공해야 한다.
  • 자바는 인터프리트 언어이며, 쓰레드를 제공하고, 동적인 언어이다.

자바는 단순하고, 객체지향이며, 친숙해야 한다.

오라클 홈페이지

  • Your programming language is object oriented, yet it's still dead simple.

설명

  • 자바는 기본 컨셉을 배우는 것이 어렵지 않다. (C나 C++을 배웠다면 쉽게 익힐 수 있다)
  • 자바는 처음부터 객체지향으로 설계되었다. 그리고 다형성, 캡슐화 등 객체지향 언어의 특징들을 지원할 수 있는 구조로 되어 있다.
    (물론, 개발자가 객체지향 프로그램을 작성하지 않으면 이러한 이점은 모두 사라진다.)
  • 자바는 개발하면서 필요한 여러 기능들을 API를 통해 제공하고 있다. 즉, 보다 쉽게 개발할 수 있는 환경을 제공한다.
  • C++과 비슷하지만 불필요하게 복잡한 부분을 제거했다. 따라서 C++을 사용하던 개발자라면 자바를 더 빠르고 쉽게 익혀 사용할 수 있다.

자바는 견고하며, 보안상 안전하다.

오라클 홈페이지

  • Your applications are robust because the Java runtime environment manages memory for you.
  • Your end users can trust that your applications are secure, even though they're downloading code from all over the Internet; the Java runtime environment has built-in protection against viruses and tampering.

설명

  • 자바는 신뢰성이 높은 소프트웨어를 만들기 위해 설계되었다.
  • 자바는 컴파일할 때와 실행할 때 문법적 오류에 대한 체크를 한다.
  • 메모리 관리 모델이 매우 단순하고, 포인터의 개념도 없다.
  • 따라서 시스템이 많은 오류를 빠르게 발견하고, 프로그램이 실행된 이후에 주요 문제(major problems)가 잠재되지 않았다는 확신을 가지고 코딩할 수 있다.
  • 자바는 기본적으로 분산 환경에서 사용하기 위해서 디자인되었다. 따라서 외부에서 침입이 불가능한 애플리케이션을 만들 수 있도록 도와준다.
    • 네트워크 환경에서 클라이언트로 다운로드한 승인받지 않은 프로그램은 실행할 수 없도록 되어 있다. 따라서, 바이러스를 생성하거나 파일 시스템을 공격할 수가 없다.
    • 하지만 간혹 JDK내부 오류나 라이브러리의 오류로 인해 해커들의 공격을 받을 수 있는 보안 취약점이 발견되기도 한다.

자바는 아키텍처에 중립적이어야 하며 포터블해야 한다.

오라클 홈페이지

  • Your applications are portable across multiple platforms. Write your applications once, and you never need to port them--they will run without modification on multiple operating systems and hardware architectures.

설명

  • 자바로 작성한 프로그램은 매우 다양한 하드웨어 아키텍처에서 수행할 수 있도록 되어 있다. 즉, 자바는 아키텍처에 중립적인 바이트 코드를 생성한다. (자바의 버전만 동일하다면 어떤 플랫폼에서도 실행 가능)
  • 아키텍처 중립적이라는 말은 포터블한 시스템의 일부분일 뿐이다.
  • 기본 데이터 타입의 크기를 지정하고, 숫자 연산자에 대한 행위들을 정의함으로써 프로그램은 어떤 플랫폼에서도 동일한 결과가 나오며, 하드웨어와 소프트웨어 아키텍처에 따른 데이터 타입의 호환성에 문제가 발생하지 않는다.
    • 이러한 호환성과 포터블한 환경을 제공하는 것은 JVM 덕분이다.

💡 Portable한 프로그램이란
찾아보니 “설치 없이 들고 다니면서 사용할 수 있는 프로그램”이라고 한다.
여기서는 조금 다른 의미로, 이식성이 좋다는 뜻으로 말한 것 같다. OS에 JRE만 설치하면, 개발한 프로그램을 어떤 플랫폼이든 상관없이 실행시킬 수 있으니까.
즉, Java 로 작성된 프로그램이 여러 플랫폼에서 실행될 수 있는 특성으로 이해하자.

자바는 높은 성능을 제공해야 한다.

오라클 홈페이지

  • Your interactive graphical applications have high performance because multiple concurrent threads of activity in your application are supported by the multithreading built into the Java programming language and runtime platform.

설명

  • 자바는 실행 환경에서 최대한의 성능을 낼 수 있도록 되어 있다.
  • 가비지 컬렉터는 낮은 우선 순위의 쓰레드로 동작하기 때문에 보다 높은 성능을 낼 수 있다.
  • 그리고, 보다 빠른 성능을 위해 네이트브한 언어(C, C++)로 작성한 코드를 자바에서 사용할 수 있도록 되어 있다.

자바는 인터프리트 언어이며, 쓰레드를 제공하고, 동적인 언어이다.

오라클 홈페이지

  • Your development cycle is much faster because Java technology is interpreted. The compile-link-load-test-crash-debug cycle is obsolete--now you just compile and run.
  • Your applications are adaptable to changing environments because you can dynamically download code modules from anywhere on the network.

설명

  • 자바 인터프리터는 자바 바이트 코드를 어떤 장비에서도 수행할 수 있도록 해준다.
    • 기존에 사용하던 무거운 컴파일과 링크와 테스트 사이클을 거쳐야 하는 개발 환경보다 빠른 환경을 구축할 수 있다.
    • 하지만 지금의 자바 개발 환경은 각종 라이브러리들로 인해 그렇게 단순하지만은 않다.
  • 자바는 멀티 쓰레드 환경을 제공하기 때문에, 동시에 여러 작업을 수행할 수 있다. 따라서 사용자에게 매우 빠른 사용 환경을 제공한다.
  • 자바 컴파일러는 컴파일 시 매우 엄격한 정적인 점검을 수행하는 반면, 실행 시에 동적으로 필요한 프로그램들을 링크시킨다. 게다가 새로운 코드는 다양한 소스에서 요청에 의해서 연결될 수 있다.

자바의 버전 별 차이

책에서 설명한 것 중, 유의해서 봐야하는 것만 중점적으로 적었다. 자세한 내용은 책을 참고하는 것을 추천한다.

JDK 1.0

가장 최초의 버전이다. 그 이후에 나온 마이너 버전인 JDK 1.0.2 버전은 최초 안정 버전이며, Java 1이라고 불리게 되었다.

JDK 1.0에서 추가된 주요 기능

  • AWT의 이벤트 모델의 확장 및 변경
  • JavaBeans, JDBK, RMI 등 추가

AWT는 Abstract Window Toolkit의 약자로, 자바를 이용하여 UI를 구성할 때 사용되는 기반 기술이다. 이 기술을 오랜 시간동안 발전시켜 왔지만 그리 많이 사용되지는 않는다.

JavaBeans는 자바에서 제공하는 컴포넌트 모델 중 하나이다.

JDBC는 Java Database Connectivity의 약자로, 데이터베이스에 데이터를 담기 위한 API를 말한다.

RMI는 Remote Method Invocation의 약자로, 같은 JVM에 있는 메소드를 호출하는 것이 아니라, 원격 JVM에 있는 메소드를 호출하기 위한 기술을 의미한다.

JDK 1.2에서 달라진 것들과 추가된 것들

JDK 1.2 버전부터 1.5버전까지는 J2SE로 불렀으며, 새로운 버전의 자바라는 의미에서 Java 2라는 이름이 생겼다.

다음과 같은 사항들이 추가되었다.

  • strictfp 예약어 추가
  • JIT라는 컴파일러가 Sun JVM에 처음 추가
  • CORBA라는 지금은 별로 사용하지 않는 기술과 데이터를 주고 받기 위한 IDL 추가
  • 자바에서 각종 자료 구조를 쉽게 처리하기 위한 Collections라는 프레임웍 추가

JIT는 Just-In-Time의 약자로 어떤 메소드의 일부 혹은 전체 코드를 네이티브 코드로 변환하여 JVM에서 번역하지 않도록 함으로써 보다 빠른 성능을 제공하는 기술이다.

Collections는 이후 챕터에서 자세히 알아본다.

JDK 1.3에서 추가된 것들

  • HotSpot JVM 추가
    • 정확히 말하면, JDK 1.2 JVM에 추가되었지만 공식적으로는 JDK 1.3에서 추가되었다고 보면 된다.
  • JNDI(Java Naming and Directory Interface)가 코어 라이브러리에 추가

JNDI는 쉽게 말하면, 어떤 객체를 쉽게 찾을 수 있도록 도와주는 이름을 지정한 후, 나중에 그 이름으로 객체를 찾아가는 것을 의미한다.

자바에서는 RMI, CORBA와 같은 것을 사용할 때나, LDAP, DMS 등 각종 주소를 쉽게 지정하기 위해서 사용된다.

JDK 1.4 버전에서 추가된 것들

JDK 1.4에서는 많은 라이브러리들이 추가되었으며, 자바 커뮤니티 프로세스(JSR)라는 절차에 따라서 개발된 첫 번째 릴리즈이기도 하다.

  • assert 예약어 추가
  • Perl 언어의 정규 표현식을 따르는 정규 표현식(regular expression) 추가
  • NIO(New Input/Output)라는 non-blocking 추가

정규 표현식은 어떤 문자열에서 특정 조건에 맞는 값이 있는지를 확인하는 데 사용된다.

  • 예를 들어 어떤 문자열이 이메일 주소의 조건에 맞는지, 웹 페이지의 주소를 나타내는 URL 조건에 맞는지 등을 확인.

NIO는 이전 버전까지의 IO를 처리할 때 사용한 java.io 패키지에서 제공하는 기능의 단점을 보완하기 위한 것이다.

Java 5버전에서 추가 및 향상된 점들

Java 5에서는 매우 많은 변화가 있었다.

  • 보다 안전하게 컬렉션 데이터를 처리할 수 있는 제네릭(Generic) 추가
  • 어노테이션이라고 불리는 메타데이터 기능 추가
  • 기본 자료형과 그 기본 자료형을 객체로 다루는 클래스 간의 데이터 변환이 자동으로 발생하는 autoboxing 및 unboxing 기능 추가
  • 상수 타입을 나타내는 enum 추가
  • 매개 변수의 개수를 가변적으로 선언할 수 있는 varargs 추가
  • 향상된 for 루프 추가
  • 정적 import가 가능하도록 해 주는 static import 추가

이 밖의 JDK의 라이브러리에 추가된 주요 기능은 다음과 같다.

  • 쓰레드 처리를 쉽게 할 수 있는 concurrent 패키지(java.util.concurrent) 추가
  • 스트림이나 버퍼로 들어오는 데이터의 분석(parse)을 보다 간편하게 할 수 있는 Scanner 클래스 추가

Java 6 버전에서 향상된 점들

Java 6가 나온 목적은 안정성과 확장성이라고 볼 수 있다.

  • 스크립팅 언어가 JVM 위에서 수행 가능하게 됨
  • 각종 코어 기능의 성능 개선
  • Compiler API가 추가되어 프로그램에서 자바 컴파일러 실행이 가능

요즘 들어 JVM 위에서 실행 가능한 각종 스크립트 언어들이 나오고 있다. 메모리 관리를 JVM에서 잘 처리해주기 때문이다.

💡 스크립트 언어란 (script language)

다른 응용 프로그램에 삽입되어서 동작하는 프로그래밍 언어이다. 간략히 스크립트라고도 한다.

C, C++, Java 등은 컴파일된 후 독립적으로 작동하는 완전한 응용 프로그램이지만, 자바스크립트, 제이쿼리, JSP, PHP, 파이썬 등의 스크립트 언어는 다른 응용 프로그램 안에 삽입되어 해석되는 방식으로 작동한다.
출처: 해시넷

Java 7에서 향상된 점들

이전 버전으로부터 5년만에 출시되었다. 30장과 31장에서 별도로 살펴본다.

Java 8에서 추가된 점들

가장 큰 변화는 람다 표현식 사용이 가능하다는 것이다.

다만, 무작정 람다로 개발하다가는 오히려 코드가 복잡해질 수도 있으니 잘 알고 필요할 때만 사용하는 것이 좋다.

이 부분에 대해서는 32장에서 별도로 살펴본다.

정리하며

자바에서 추가된 모든 기능들이 대부분 웹 기반 개발자들에게 필요한 것은 아니다. 예를 들어 CORBA와 같은 기능은 사용하는 곳이 별로 없다.

💡 참고
1. Java API Doc: /technotes/guides/language/enhancements.html
2. 영문 위키피디아: java version history

JIT 컴파일러란?

JIT는 Just-In-Time의 약자이며, 이것을 사용하는 언어에는 자바와 .NET 등이 있다. 좀 더 쉬운 말로 “동적 변환 Dynamic translation”이라고 할 수 있다.

실행 시에 적용되는 기술로, 프로그램 실행을 보다 빠르게 할 수 있다.

역사적으로 보면, 컴퓨터 프로그램을 실행하는 방식은 두 가지로 나눌 수 있다.

  • 인터프리트 방식 (Interpret)
    • 프로그램을 실행할 때마다 컴퓨터가 알아 들을 수 있는 언어로 변환하는 작업을 수행
    • 간편하기는 하지만, 성능이 매우 느리다
  • 정적 컴파일 방식 (Static Compile)
    • 실행하기 전에 컴퓨터가 알아들을 수 있는 언어로 변환하는 작업을 미리 수행
    • 따라서, 변환 작업은 딱 한 번만 수행한다.

JIT는 이 두 가지 방식을 혼합한 것이라고 보면 된다.

변환 작업은 인터프리터에 의해서 지속적으로 수행되지만, 필요한 코드의 정보는 캐시에 담아두었다가(메모리에 올려두었다가) 재사용하게 된다.

자바의 변환 과정

자바의 모토 중 하나가 “Compile once, Run Anywhere”다. 즉, 한 번 컴파일한 코드로 리눅스, 맥, 윈도우 등에서 모두 사용할 수 있다.

우리는 javac 명령어를 사용해 컴파일 하여 class라는 바이트 코드(Byte code) 생성한다. 다시 말해서, 텍스트로 만든 java 파일을 어떤 OS에서도 수행될 수 있도록 바이트 코드라는 파일로 만든 것 뿐이다.

바이트 코드는 컴퓨터가 알 수 있도록 변환 작업이 필요하다. 이 변환 작업을 JIT 컴파일러에서 한다.

자바 프로그램이 수행되는 절차

  1. 자바 소스 코드 작성
  2. 자바 컴파일러가 자바 소스 코드를 컴파일된 Bytecode로 변환
  3. JVM이 컴파일된 Bytecode를 기계 코드로 변환
    • 이 부분을 JIT에서 수행한다.
  4. 하드웨어 및 OS에서 기계 코드 실행

JIT 장단점

  • 반복적으로 수행되는 코드는 매우 빠른 성능을 보인다.
  • 처음에 시작할 때에는 변환 단계를 거쳐야 하므로 성능이 느리다.

하지만, 최근 들어 CPU 성능이 많이 좋아졌고 JDK의 성능 개선도 많이 이루어졌기 때문에 방금 이야기한 단점도 많이 개선되었다.

HotSpot이란?

Oracle이 개발한 자바 가상 머신(JVM)의 구현 중 하나로, 성능 향상과 최적화에 중점을 뒀다.

공식적으로 JDK 1.3부터 제공되었다. 자바에서는 “HotSpot 클라이언트 컴파일러”와 “HotSpot 서버 컴파일러”의 두 가지 컴파일러를 제공한다.

JDK 1.3 전에 나왔던 가상 머신과 구분하기 위해 HotSpot이라는 이름을 사용했다. 오라클 사이트에서는 이전 버전의 JVM을 Classic VM이라고 한다.

HotSpot 클라이언트 컴파일러

예전에는 대부분 PC의 CPU 코어 개수는 하나였다. 이렇게 CPU 코어가 하나뿐인 사용자를 위해 만들어진 것이 “HotSpot 클라이언트 컴파일러”다.

이 컴파일러의 주요 특징은 다음과 같다.

  • 애플리케이션 시작 시간을 빠르게 한다.
  • 적은 메모리를 점유하도록 한다.

HotSpot 서버 컴파일러

코어가 많은 장비에서 애플리케이션을 돌리기 위해 만들어진 것이다. 이 컴파일러는 애플리케이션 수행 속도에 초점이 맞추어져 있다.

클라이언트 컴파일러와 서버 컴파일러를 선택하는 기준

기본적으로 자바가 시작할 때 알아서 클라이언트 장비인지 서버 장비인지를 확인한다. 서버 컴파일러를 선택하는 기준은 다음과 같다.

  • 2개 이상의 물리적 프로세서
  • 2GB 이상의 물리적 메모리

참고로, OS에 따라서 어느 컴파일러를 사용할지가 정해져 있기도 하는데, 윈도우는 기본적으로 지정해주지 않으면 클라이언트 컴파일러가 사용된다.

명시적으로 컴파일러 지정

java 명령에 클라이언트면 -client , 서버면 -server 라고 지정해주면 된다.

$java -server 클래스이름
$java -client 클래스이름

이 외에도 여러 가지 옵션을 지정할 수 있다. 각 옵션은 공백으로 구분해준다.

아래는 JVM의 시작 메모리 크기를 지정하는 -Xms 라는 옵션으로, 512 메가 바이트의 시작 크기를 같이 지정한다.

$java -server -Xms512m 클래스이름

JVM과 GC

일반적으로 자바를 설명할 때 많이 사용되는 용어들이다.

JVM: Java Virtual Machine

작성한 자바 프로그램이 수행되는 프로세스를 의미한다. 다시 말해, java라는 명령어를 통해서 애플리케이션이 수행되면, 이 JVM 위에서 애플리케이션이 동작한다.

GC: Garbage Collector

자바의 메모리 관리는 JVM이 알아서 하기 때문에 개발자가 하지 않아도 된다. 이때, JVM 내에서 메모리 관리를 해주는 것을 바로 “가비지 컬렉터”라고 부른다.

사용하고 남아 있는 전혀 필요 없는 객체들은 쓰레기(가비지)에 속한다. 가비지 컬렉터가 쓰레기를 알아서 청소해준다고 하더라도 메모리를 효율적으로 사용하도록 개발하는 것은 매우 중요하다.

일반적으로 GC라고 하면 Garbage Collection을 의미한다.

자바의 GC 과정

자바에서 GC를 수행하는 방식은 매우 여러 가지가 있다. 여기서는 아주 간단하게 GC를 살펴봤다.

어떤 객체를 생성하더라도, 그 객체는 언젠가는 쓰레기가 되어 메모리에서 지워져야만 한다.

만약 지워지지 않으면, 자바 프로그램은 엄청난 메모리가 필요할 것이다.

그래서 C를 사용하여 개발할 때에는 메모리를 해제하는 작업을 꼭 해줘야 한다. 하지만, 자바는 가비지 컬렉터가 알아서 치워주기 때문에 그런 작업을 해 줄 필요가 없다.

JVM의 영역

Java 7부터 공식적으로 사용할 수 있는 G1 (Garbage First)라는 가비지 컬렉터를 제외한 나머지 JVM은 다음과 같이 영역을 나누어 힙이라는 공간에 객체들을 관리한다.

💡 여기서 사용되는 영역에 대한 용어들은 JDK 벤더에 따라서 조금씩 상이하며, 이 책은 오라클에서 제공하는 JDK를 기준으로 작성하였다.

자바의 신에 있는 그림에 색을 넣어 구분해봤다

  • Young 영역 : 젊은 객체들이 존재
    • Eden과 두 개의 Survivor 영역으로 나뉜다.
    • Eden 영역 : 객체가 생성되면 저장되는 장소
  • Old 영역 : 늙은 객체들이 존재
  • Perm 영역 : 클래스나 메소드에 대한 정보가 쌓임
    • Perm에 저장되는 데이터는 더 많지만, 이 정도만 알고 있어도 된다.

자바의 메모리 동작

일반적으로 자바에서 메모리가 살아가는 과정은 다음과 같다. 여기서의 GC는 가비지 컬렉션을 말한다.

  1. Eden 영역에서 객체가 생성된다.
  2. Eden 영역이 꽉 차면 살아있는 객체만 Survivor 영역으로 복사되고, 다시 Eden 영역을 채운다.
  3. Survivor 영역이 꽉 차게 되면, 다른 Survivor 영역으로 객체가 복사된다.
    • 이 때, Eden 영역에 있는 객체들 중 살아있는 객체들도 다른 Survivor 영역으로 간다.
    • 즉, Survivor 영역의 둘 중 하나는 반드시 비어 있어야만 한다.
    • Minor GC 혹은 Young GC 라고 부른다.
  4. 오래 살아있는 객체들은 Old 영역으로 이동한다.
    • Old 영역이 꽉 차면 발생하는 GC를 Major GC 혹은 Full GC 라고 부른다.

Young GC가 Full GC보다 속도가 더 빠르다. 왜냐하면 일반적으로 더 작은 공간이 할당되고, 객체들을 처리하는 방식도 다르기 때문이다. 그렇다고 전체의 힙 영역을 영 영역으로 만들면 장애로 이어질 확률이 매우 높아진다.

GC의 종류

오라클 JDK에서 제공하는 GC의 방식은 크게 4가지가 있으며, Java 7에서 추가된 G1 GC를 포함하여 총 5가지의 가비지 컬렉터가 존재한다.

  • Serial GC
    • 이 GC는 -client 옵션을 지정했을 때 사용된다.
    • 즉, 클라이언트용 장비에 최적화된 GC이기 때문에 WAS로 사용하는 JVM에서 사용하면 GC 속도가 매우 느려 웹 애플리케이션이 엄청 느려진다.
  • Parallel Young Generation Collector
  • Parallel Old Generation Collector
  • Concurrent Mark & Sweep Collector (줄여서 CMS)
  • Shenandoah GC
  • ZGC

각 GC 방식들은 서로 장단점이 존재하기 때문에, 어떤 GC 방식이 가장 적합하다고 이야기하기는 매우 어렵다.

정리하며

자바의 역사가 어떻게 되는지를 외우고 있을 필요는 없지만, 개발하고 운영하는 시스템의 자바 버전이 낮다면 최신 버전의 JDK에서 제공하는 기능들을 사용하기 어렵고 호환성 문제가 있을 수 있다.

따라서 본인이 사용하는 JDK에서 어떤 것이 제공되고, 제공 안 되는지 정도는 알아두어야만 한다.

질문

💡 책에 있는 내용이 아닙니다.

책을 읽으며 설명이 더 필요하거나, 추가로 궁금한 점에 대해 질문 형식으로 작성 후, 답을 구해보고 있습니다.
참고한 사이트나 영상은 [출처]로 달아두었으며, 오류 지적은 언제나 환영합니다.

Q. 자바의 보안이란 무엇인가?

💡 자바 보안을 강화하는 방법 (AhnLab | 보안 이슈에서 발췌)

원래 자바는 보안성이 뛰어난 것으로 유명하다. 자바의 핵심 아키텍처가 가상머신(JVM)과 샌드박스이기 때문이다.

JVM은 자바 코드를 실행하는 환경이다. 자바 코드는 시스템 자원에 직접 접근하지 못하고, JVM이 제공하는 API를 통해 시스템 자원에 접근해야 한다. 이는 자바 코드가 시스템을 손상시킬 위험을 줄인다.

샌드박스는 자바 코드가 실행되는 환경을 제한하는 기능이다. 샌드박스 내에서 실행되는 자바 코드는 특정 파일에만 접근할 수 있고, 다른 프로세스와 상호 작용할 수 없다. 따라서 자바 코드가 시스템에 피해를 줄 가능성은 낮다.

자바 보안은 자바 플랫폼과 애플리케이션을 보안 위협으로부터 보호하기 위한 기술과 정책의 집합이다. 자바 보안은 다음 2가지 영역으로 나눌 수 있다.

  1. 자바 플랫폼 보안 (Java Platform Security): JVM과 자바 클래스 라이브러리 (JCL)가 공격자에 의해 변조되거나 오용되지 않도록 보호한다.
  2. 자바 애플리케이션 보안 (Java Application Security): 자바로 개발된 애플리케이션의 정보와 기능이 공격자에 의해 침해되거나 손상되지 않도록 보호한다.

자바는 이러한 보안 기능을 통해 공격으로부터 시스템을 보호할 수 있다. 하지만 자바 개발자와 사용자도 보안을 위해 노력해야 한다.

  1. 최신 보안 업데이트를 적용하자.
    : 자바는 정기적으로 보안 업데이트를 제공한다. 최신 보안 업데이트를 적용하면 취약점을 악용한 공격을 방지할 수 있다.

  2. 개발자는 안전한 코딩 관행을 따라야 한다.
    : 자바는 안전한 코딩을 위한 가이드라인을 제공하는데, 이 가이드라인만 따라도 공격을 방지하는 데 도움이 된다.

  3. 안전한 사용 습관을 유지해야 한다.
    : 신뢰할 수 없는 소스에서 다운로드한 자바 애플리케이션을 실행해서는 안 되며, 자바 애플리케이션 실행 시 올바른 보안 설정을 하는 것이 중요하다.

Q. 자바의 포인터 개념 (feat. 참조 값 복사)

Java 에서도 Porinter 라는 개념이 있나요? - 인프런 | 질문 & 답변

자바는 명시적인 포인터 연산이나 주소 직접 조작을 할 수 없도록 설계되었다. 대신, 객체에 대한 참조(reference)를 사용하여 객체를 조작할 수 있다.

reference는 메모리 주소가 아닌 객체의 식별자로, reference를 사용하면서 개발자는 메모리 관리를 더 쉽게 할 수 있으며, 포인터로 인한 일반적인 버그(예: 포인터 연산 오류)를 방지할 수 있다.

Q. 자바에서 포인터를 왜 없앴는가?

C언어에서는 포인터를 이용해 메모리의 주소에 직접 접근이 가능했고, 메모리 관리를 개발자가 해야했다. 자바에서는 이러한 포인터를 JVM만 사용할 수 있도록 한 것과 같다.

  1. 메모리 안전성(Safety): 포인터 연산은 메모리의 잘못된 위치에 접근할 수 있으며, 이는 프로그램의 예측 불가능한 동작과 메모리 오류로 이어질 수 있다.

  2. 가비지 컬렉션의 간소화: 포인터가 있는 경우, 메모리 할당 및 해제가 더 복잡해진다. 자바는 가비지 컬렉션을 통해 메모리 관리를 자동으로 수행하여 개발자가 명시적으로 메모리를 해제할 필요가 없게 했다.

  3. 플랫폼 독립성(Portability): 자바에서는 한 번 작성한 코드는 어떤 플랫폼에서도 실행될 수 있어야 한다. 포인터 연산은 플랫폼에 종속적이며 이식성을 저하시킬 수 있습니다.

  4. 보안 강화: 포인터 연산은 보안 측면에서 취약점을 생성할 수 있다.
    - 예를 들어, 잘못된 포인터 연산은 메모리 덮어쓰기와 같은 보안 문제를 야기할 수 있다.
    프로그래머 실수 방지:

포인터 연산은 개발자가 실수를 저지를 가능성이 높은 작업 중 하나다. 자바는 이러한 실수를 방지하고 예측 가능한 동작을 유지하기 위해 명시적인 포인터를 제공하지 않음으로써 편리함과 안정성을 제공한다.

하지만, GC와 같이 메모리를 어느 시점에 청소해주는지는 개발자가 알 수가 없어서 오류가 발생해도 확인이 어렵다.

[Java] 자바에서 포인터가 아닌 참조(Reference) 자료형을 사용하는 이유는?

Q. “아키텍처에 중립적이고 포터블하다”는 무슨 의미인가?

아키텍처에 중립적이다는 것은 플랫폼에 중립적이다, 라는 것이다. 포터블하다는 찾아보니 “이식성이 높다”정도로 생각하면 될 것 같다.

한 번 컴파일 한 후, 플랫폼에 관계없이 실행시킬 수 있다는 것이 포터블하다는 의미가 아닐까?

Q. 어떻게 플랫폼에 독립적인 걸 지원해주고 있는가?

JVM을 통해서 독립적으로 만들어 주고 있다. 기존의 C나 C++등의 컴파일 방식은 OS 별로 기계어가 달랐기 때문에, 각 OS에 맞춰서 컴파일을 해줘야 했다.

그래서 Java에서는 처음에 JVM이 이해할 수 있는 바이트 코드로 컴파일을 시켜준다. 그리고 JVM은 각 OS별로 설치할 수 있도록 해둔다.

JVM이 바이트 코드를 알아서 읽고 기계어로 변환시켜주기 때문에, 개발자가 할 일은 컴파일하고, 각 OS에 맞는 JVM을 설치하는 것 뿐이다.

JVM을 생각하면 완전히 플랫폼에 독립적인 것은 아니지만, 개발자가 수작업으로 손대지 않아도 된다는 점에서는 독립적이라고 할 수 있다.

Q. 네이티브 언어의 필요성과 장단점

네이티브 언어는 C언어와 C++언어를 말하며, 다음과 같은 이유로 사용할 수 있게 했다고 한다.

  • 특정 플랫폼의 네이티브 기능을 사용해야 할 경우
  • C나 C++ 등의 네이티브 언어로 개발된 라이브러리를 사용할 경우
  • 일부 하드웨어 기능에 직접 접근하거나 특정 기능을 구현해야 할 경우
  • 속도 개선을 위해

하지만, 다음과 같은 문제가 있을 수 있으므로 신중히 사용해야 한다.

  • 네이티브 코드에서 발생할 수 있는 메모리 접근 오류, 포인터 연산, 버퍼 오버플로우 등의 문제는 자바의 안전성을 해칠 수 있다.
  • 네이티브 코드는 가비지 컬렉션의 범위 밖에서 동작하기 때문에 메모리 누수가 발생할 수 있다.
  • 네이티브 코드는 특정 플랫폼에 의존적이므로 이식성 문제가 발생할 수 있다.

Q. JIT 컴파일러 동작 과정

JIT 컴파일러는 런타임 시에 바이트 코드를 기계어로 컴파일함으로써 Java 프로그램의 성능을 향상시키는 데 도움이 된다.

JVM은 각 메소드에 대해 사전 정의된 컴파일 임계값을 지정하고, 해당 메소드가 호출될 때마다 값을 감소시킨다. 호출 개수가 0에 도달하면 해당 메소드에 대한 JIT(Just-In-Time) 컴파일이 실행된다.

자주 사용되는 메소드는 JVM이 시작된 직후 컴파일되며, 자주 사용되지 않는 메소드는 훨씬 나중에 컴파일되거나 컴파일되지 않는다.

즉, 실행 시점에서 기계어 코드를 생성하면서 그 코드를 캐싱함으로써, 같은 함수가 여러 번 불릴 때 매번 기계어 코드를 생성하는 것을 방지하는 것이다.

장단점

참고 사이트

장점은 프로그램을 실행하는 동안에 코드를 기계어로 변환하므로 실행 시간에 특정 플랫폼에 최적화된 코드를 생성할 수 있다. 실행할 때마다 기계어 변환 과정이 필요한 인터프리트 방식, 혹은 중간코드 방식에 비해서 속도가 빠를 수밖에 없다.

단점은 초기에 실행이 시작되는 속도가 많이 느리며, 소스 코드 혹은 바이트코드 파일과 실행파일 코드가 같이 있기 때문에 용량을 많이 잡아먹게 된다. 소스 코드나 바이트코드가 바뀌었을 경우에 컴파일을 다시 해야 하므로 그 변화 여부를 체크하고 있어야 한다.

참고 사이트

Oracle JVM 공식 문서

Java SE Specifications

AhnLab - Java의 보안성 강화에 대해

AhnLab | 보안 이슈

JIT 컴파일러 동작 과정

JIT 컴파일러

컴파일러와 인터프리터, 그리고 JIT 컴파일

The Java Language Environment

Java Programming Language Enhancements

[Java] Java에 포인터가 없는 이유 - 포인터(Pointer) vs 참조(Reference)

profile
책을 읽거나 강의를 들으며 공부한 내용을 정리합니다. 가끔 개발하는데 있었던 이슈도 올립니다.

0개의 댓글