기본 콘텐츠로 건너뛰기

[Java] JVM에 대해 알아보자

이번 시간엔는 JVM에 대해 전반적으로 알아보려 한다.
자바 코드를 작성하는 일에 JVM이 뭐 구지 중요한가 의심이 가지만,
성능 튜닝이나 더 아름다운 코드를 작성하기 위해 어느 정도 이해가 필요한것 같다.
JVM을 알아보면서 추가적으로 필요한 개념을 더 설명하려한다.

자 그럼 시작 해보자.

<JVM 그림>

<Runtime Data Access>

JVM
- 자바의 특징 중 Write once, run everywhere가 있다. JVM이 OS와 프로그램의 매개체 역할을 하고,
애플리케이션은 JVM 위에서 동작하도록 설계 되었다.
- 작성된 자바프로그램의 바이트 코드를 실행할 수 있는 주체이다.
- 스택기반의 가상머신이다.
- 가비지 컬렉션을 실행한다. (가비지컬렉터 이용)
- Java 애플리케이션은 플랫폼에 독립적이다.
하지만 JVM은 플랫폼에 종속적이다. 각 플랫폼(OS)마다 다르게 구현되어야 한다.

JVM 구조
  • JVM의 내부 구조를 큰 형태로 보면 클래스 로더 서브시스템, 런타임 데이터 영역(Runtime Data Area) 그리고 실행엔진(Execution Engine)으로 나눌 수 있다.
  • Class Loader : JVM 내로 class를 로드하고 Link를 통해 적절히 배치하는 일련의 작업을 수행하는 모듈. Runtime시 동적으로 수행된다. 클래스를 분석하는 과정에서 리플렉션 기법을 사용해 컴파일 된 클래스의 내부를 분석한다.
  • Execution Engine : class loader를 통해 JVM 내의 런타임 데이터 영역에 배치된 바이트 코드는 실행 엔진에 의해 실행된다. 실행 엔진은 자바 바이트 코드를 명령어 단위로 읽어 실행한다.
  • Garbage Collector : 가비지 컬렉터를 통해 자동화된 메모리 관리 기능을 제공한다. Garbage Collector는 Garbage Collection을 수행하는 모듈 쓰레드를 말한다.
  • Runtime Data Areas : JVM이 운영체제 위에서 실행되면서 할당 받는 메모리 영역이다.  

JVM 수행 과정
1. Class Loader System을 통해 Class 파일을 JVM으로 로딩한다.
2. 로딩된 class 파일들을 Execution Engine을 통해 해석한다.
3. 해석된 프로그램은 Runtime Data Access에 배치되어 실질적 수행이 이뤄진다. 이런 실행과정
속에서 JVM은 필요에 따라 Thread Synchronization과 GC같은 관리 작업을 수행한다.  

JIT
javac 명령어를 수행하는 건 텍스트 파일로 만든 java 파일을 어떤 os에서도 수행 될 수 있도록 바이트 코드를 만드는 것이다.(동적 변환, java, c#이 사용).  컴퓨터가 알아 먹을 수 있도록 하려면 변환 작업이 필요한데, 이를 JIT 컴파일러가 진행한다.
자바 소스코드 → 자바 컴파일러 → 컴파일된 바이트코드 → JVM → 기계코드 → 하드웨어 및 OS  (JIT이 JVM → 기계코드로 바뀌는 부분을 처리한다.) .

프로그램이 메모리 사용하는 방식 : 코드실행영역 + 데이터 저장 영역

Runtime Data Access
  • 스태틱 영역 : 클래스들의 놀이터. JVM이 읽어들인 클래스와 인터페이스에 대한 런타임 상수 풀, 메서드필드, static변수, 메서드 바이트 코드 등을 보관한다. (클래스 메타 정보). JVM에서 클래스를 실행하면 메서드 영역에서 클래스 정보를 복사하고 힙 영역에서 메모리를 할당해 실행한다.
  • 스택 영역 : 메서드들의 놀이터. 메소드 호출 시 생성된 스레드 수행정보를 기록하는 Frame을 저장. 메서드 정보, 지역변수, 매개변수, 연산 중 발생하는 임시 데이터 저장.
  • 힙 영역 : 객체들의 놀이터. 런타임 시 동적으로 할당하며 사용하는 영역. new로 생성된 객체/인스턴스와 배열을 저장한다. JVM 성능 향상을 위해 튜닝이 가능한 영역이다.
  • Runtime Constant Pool : Method Area 영역에 포함된다. JVM은 런타임 상수 풀을 통해 해당 메서드나 필드의 실제 메모리 상 주소를 찾아 참조한다.
  • PC 레지스터 : 현재 수행중인 JVM 명령 주소를 갖는다. 개발자가 개입할 필요가 없는 영역. 쓰레드 별로 있다.
  • Native Method Stack Area : OS의 시스템 정보, 리소스를 사용하거나 접근하기 위한 코드로 자바 외 언어로 작성된 네이티브 코드를 위한 Stack. 즉 JNI(Java Native Interface)를 통해 호출하는 C/C++ 등의코드를 수행하기 위한 스택. 자바 프로그램에서 시스템 함수를 사용하려면 JNI를 통해 JVM을 한번 거쳐야 한다. 자바 프로그램과 OS 사이에 JVM이 존재하기 때문.

String의 메모리
String은 두가지 생성 방식이 있다.
  1. new를 이용 : Heap 영역에 존재하게 됨
  2. 리터럴 방식 : String constant pool 영역에 존재하게 됨.
string constant pool은 Perm 영역이라는 곳에 존재하는데, java7을 기점으로 위치가 변경됨.
heap영역에 생성한 객체와 string constant pool에 저장된 객체의 주소값은 다르다.
String을 리터럴로 선언한 경우 내부적으로 String의 intern() 메서드가 호출. intern() 메서드는 주어진 문자열이 string constant pool에 존재하는 지 검사하고 있으면 그 주소값을 반환 하고 없으면 새로운 주소값을 넣고 반환한다.
String constant pool 위치 변경
  • java6까지 string constant pool의 위치는 Perm영역이었다.
  • java7 부터는 Heap영역으로 변경되었다. OOM때문
  • Perm 영역은 고정된 사이즈고 Runtime시에 사이즈가 확장되지 않는다. 이로인해 Out of Memory 발생 할 수 있었다.
  • Heap영역으로 변경의 이점은 string constant pool 의 모든 문자열도 GC의 대상이 될 수 있다는 점이다.

main() 메서드의 흐름
JRE는 먼저 프로그램 안에 main()을 찾는다. 찾으면 JVM에 전원을 넣고, JVM은 목적파일을 받아 실행한다. JVM은 전처리를 먼저 거치고, java.lang 패키지를 스태틱 영역에 올린다. 다음 개발자가 작성한 모든 클래스와 임포트 패키지를 스태틱 영역에 놓는다(클래스를 사용할 때 올림). 다음 메서드 중 main()이 스택 영역에 올라가고, main 메서드 인자들을 저장할 변수 공간을 스택 프레임 밑에 확보한다. 그 다음 메인 메서드 첫명령문이 실행된다. 메서드를 여는 중괄호를 만나면 새로운 스택 프레임이 만들어지고 닫는 중괄호를 만나면 스택 프레임이 소멸된다. main() 이 끝나면 JRE는 JVM을 종료하고 JRE 자체도 운영체제 메모리에서 사라진다.

멀티스레드와 메모리
멀티 스레드는 T 메모리 중 스택 영역에서 스레드 개수만큼 분할해서 사용한다. 멀티 프로세스는 다수의 T 메모리를 확보하는 구조이다. 각자의 공간이라 서로 참조할 수 없다.
멀티 스레드는 하나의 T메모리 구조를 사용하므로 스택 영역을 제외한 공간을 공유한다.

상속과 메모리
A ←- B라는 상속구조가 있다고 할 때, B객체를 생성하면 A도 함께 힙 메모리에 올라가게 된다. 즉 하위 인스턴스가 생성될 때 상위 클래스의 인스턴스도 함께 생성된다.

다형성과 메모리
상위 클래스 타입의 객체 참조 변수를 사용하더라도, 하위 클래스에서 오버로딩한 메서드가 호출된다.

클래스 로더
1. 클래스 로더의 정의 및 특징
  • 컴파일 시점이 아닌 실행시점에 클래스를 로딩할 수 있게 해주는 기술이다.
  • 클래스 로더는 클래스의 바이트코드를 파일 시스템 상의 클래스나, JAR와 같은 아카이브 형태로 동적으로 로딩할 수 있게 지원해 준다.
특징
  • 계층적 : 클래스 로더는 계층적으로 생성 가능하다.
  • 위임형 로드 요청 : 상위 클래스 로더가 로딩한 클래스가 우선권을 가진다.
  • 가시적인 규약 : 상위 클래스 로더를 먼저 참조하고, 하위 클래스 로더는 상위 클래스 로더의 클래스를 위임형 로드 요청을 이용하여 찾을 수 있다.
  • 클래스는 언로드 할 수 없음 : 클래스 로더에 의해 로딩된 클래스는 언로드될 수 없다.

2. 클래스 로더의 구조


  • 부트스트랩 class Loader : 자바 가상머신이 실행될 때 가장 먼저 실행되는 클래스로더. 자바 실행에 필요한 기본적인 클래스들을 로딩한다.java API.
  • 확장 class Loader : java 확장 클래스들이 로딩된다. classpath에 설정되어 있지 않아도 로딩 된다. API 확장 클래스.
  • 시스템 class Loader : -classpath에 정의되거나 jvm 옵션에서 -cp에 지정된 클래스들이 로딩된다.
  • 사용자 정의 class Loader(=Application class Loader) : 애플리케이션 사용자가 직접 코드 상에서 생성해 사용하는 클래스 로더.  

Class Path

클래스를 찾기 위한 경로이다. JVM이 프로그램을 실행할 때, 클래스파일을 찾는데 기준이 되는 파일 경로를 말한다. classpath는 .class 파일이 포함된 디렉토리와 파일을 콜론으로 구분한 목록이다. java runtime은 classpath에 지정된 경로를 모두 검색해 특정 클래스에 대한 코드가 포함된 .class 파일을 찾는다.

댓글

이 블로그의 인기 게시물

[자바 웹 프로그래밍]2장 문자열 계산기 구현을 통한 테스트와 리펙토링

이번엔 2장에 나와 있는 내용 정리와 느낀점을 정리 해 보겠다. 1. main() 메소드를 활용한 테스트의 문제점.   - 소스코드 구현 후 정상적으로 동작하는지 확인 위해 일반적인 방법은 main()메소드를 활용하는 것이다.   - 실제 서비스를 담당하는 프로덕션 코드와 이 프로덕션 코드가 정상 동작 하는지 확인을 위한 main() 으로 나뉜다.   - 이 방법의 첫번째 문제점은 프로덕션코드와 main() 메서드가 함께 있다는 것이다.   - 프로덕션 코드와 테스트코드(main)을 분리할 수 있다.   - 두 번째 문제는 내가 구현하고 있는 메서드만 집중 할 수 없고, 클래스가 가지고 있는 모든 메서드를 테스트 할 수 밖에 없다.   - 다른 문제는 항상 콘솔로 확인을 할 수 밖에 없다는 것이다.   - 이를 위해 등장한 라이브러리가 JUnit 이다. 내 관심을 가지는 메서드에 대해 테스트 가능하다. 2. JUnit을 활용해 main() 메서드 문제 극복 2.1 한 번에 메서드 하나에만 집중.   - JUnit관련 라이브러리 추가 후  테스트 메서드에 @Test를 붙이면 된다.   - test 관련 코드 작성 후 Run > Run as> JunitTest를 실행해 보자.   - 각각 테스트 메서드를 독립적으로 실행할 수 있기 때문에 현재 내가 구현하고 있는 프로덕션 코드의 메서드에 집중할 수 있다. import org.junit.Test; public class CalculatorTest { @Test public void add() { Calculator cal = new Calculator(); System.out.println(cal.add(1,2)); } } 2.2 결과 값을 눈이 아닌 프로그램을 통해 자동화 import org.junit.Test; import static org.junit.Assert.assertEquals;

[고량주] 라오왕 연태고량주 플러스

나에게 처음 고량주란 이런것이다 라는걸 알려준 녀석이다. 부모님이 중국집을 하다 보니 가끔 초록색병 고량주를 먹었을때  역한 공업용 알콜 맛에 고량주는 나랑 안맞는다 생각했다가 우연히 양고기에 이녀석을 접한 뒤로 고량주의 맛을 알아버렸다... 제품명 : 라오왕 연태고량주플러스 제품유형 : 일반증류주 도수 : 34.2% 가격 : 9000원(홈플러스 익스프레스 기준) 재구매 의사 : 있다 시음평 : 역시 고량주 특유의 향인데, 열대과일 향도나고, 배향, 살짝 달달한 향이 난다.            목넘김은 34.2%에도 불구하고 그리 힘들지 않았다(주당이 된걸수도..)             중국요리나 양꼬치집에서 맛있는 술이 땡긴다면 강력추천한다.