<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Andy&amp;rsquo;s Dev Log</title>
    <link>https://jvnlee.tistory.com/</link>
    <description>웹 백엔드 개발자</description>
    <language>ko</language>
    <pubDate>Sun, 5 Jul 2026 08:01:04 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>jvnlee</managingEditor>
    <image>
      <title>Andy&amp;rsquo;s Dev Log</title>
      <url>https://tistory1.daumcdn.net/tistory/5282497/attach/3b02858fd8634e7baa2d3f049ca1d10b</url>
      <link>https://jvnlee.tistory.com</link>
    </image>
    <item>
      <title>volatile 키워드</title>
      <link>https://jvnlee.tistory.com/9</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;캐시와 메인 메모리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;volatile에 대한 설명에 앞서, CPU의 메모리 참조에 대해서 간단하게 짚고 넘어가자. CPU는 코어당 한번에 하나의 쓰레드를 수행시키는데, 이 때 쓰레드의 작업에서 필요로 하는 데이터는 근본적으로는 메인 메모리인 RAM으로부터 온다. 그러나 CPU의 연산 속도에 비해 RAM은 한참 느리기 때문에 반복 사용되는 데이터를 CPU가 보다 빠르게 얻기 위해 캐시 메모리를 활용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;402&quot; data-origin-height=&quot;404&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AhdSO/btrxsDrr7h2/he8GEFSAT9uWIOt2qoUjjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AhdSO/btrxsDrr7h2/he8GEFSAT9uWIOt2qoUjjk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AhdSO/btrxsDrr7h2/he8GEFSAT9uWIOt2qoUjjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAhdSO%2FbtrxsDrr7h2%2Fhe8GEFSAT9uWIOt2qoUjjk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;533&quot; height=&quot;536&quot; data-origin-width=&quot;402&quot; data-origin-height=&quot;404&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도식을 보면 CPU와 RAM 사이에 캐시 메모리가 자리하고 있는 것을 확인할 수 있다. CPU는 우선적으로 캐시에 사용하고자 하는 데이터가 있는지 확인하는데, L1, L2, L3 캐시 순서대로 확인해보고 캐시에서 데이터를 얻지 못하면 RAM에서 얻어온다. 이 글은 캐시에 대해 깊게 다루는 글은 아니므로 자세한 설명은 배제했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐시를 사용하면 분명히 속도의 이점을 얻을 수 있다. 그런데 멀티 쓰레드 환경에서 모든 쓰레드가 공통적으로 참조하는 데이터를 각 코어와 연결된 캐시에 저장해두고 사용한다면, 개별 쓰레드에서 데이터를 변경시키는 과정에서 각 캐시에 저장된 데이터가 서로 달라지면서 동기화가 깨지게 된다. 이와 같이 캐시 메모리를 사용할 때 문제가 생길 소지가 있다면 volatile 키워드를 사용해볼 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;캐시 메모리 간의 동기화를 다른 말로 &lt;b&gt;캐시 일관성&lt;/b&gt;(Cache Coherence)이라고 표현한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;volatile 키워드의 사용&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;volatile은 변수 선언 시 앞에 붙여 다음과 같이 사용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1648298826880&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;volatile boolean isOn = false;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용 시 효과는 2가지 정도가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 해당 데이터에 접근하는 쓰레드가 캐시가 아닌 메인 메모리로부터 데이터를 참조하도록 강제한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티 코어, 멀티 쓰레드 환경에서는 RAM에 있던 데이터를 읽어간 후에 각 코어 마다의 캐시에 저장해두고 캐시를 참조하게 된다. 그래서 위에서 언급했던 대로 데이터 접근 속도는 빨라지더라도 동기화에 문제가 생길 수 있다. volatile을 사용하면 무조건 이 데이터는 메인 메모리에 저장해놓고 메인 메모리로부터 읽거나 쓰겠다고 선언하는 것과 같기 때문에 언급한 문제 상황을 방지할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 해당 데이터에 대한 읽기/쓰기 작업의 원자성을 보장한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JVM의 Runtime Data Area 중에서 개별 쓰레드마다 할당되는 영역인 Stack Area는 각 Stack Frame에 Operand Stack 이라는 피연산자 스택을 가지고 있다. 이 피연산자 스택은 4 byte 단위로 적재되기 때문에 byte, short, int 같은 4 byte 이하의 타입은 원자성을 보장 받아 데이터를 읽거나 쓸 때 처리 과정이 끊기지 않는다. 그러나 long이나 double과 같은 8 byte 크기의 타입은 4 byte를 초과하기 때문에 작업 도중에 다른 쓰레드에 의해 작업이 끊어질 수 있다. 이 경우 변수 선언 시 volatile을 사용하면, long이나 double 처럼 크기가 큰 데이터도 원자성을 보장 받아 작업이 도중에 끊어지지 않도록 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;synchronized와 volatile&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;volatile과 항상 같이 언급되는 것이 동기화 구현을 위한 키워드인 synchronized 이다. 이 둘에 대한 이야기 이전에 락(lock)과 동기화에 관한 2가지 성질에 대해 짚고갈 필요가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 상호 배제 (Mutual Exclusion, Mut-Ex)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오직 하나의 쓰레드만이 락을 얻어 임계 영역 내의 작업을 수행할 수 있다는 의미&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 가시성 (Visibility)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공유 데이터에 대해 하나의 쓰레드에서 발생시킨 변경이 나머지 쓰레드에게 모두 보일 수 있게 공개된다는 의미&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java의 synchronized 키워드는 이 2가지 성질을 모두 보장한다. 따라서 여러 쓰레드가 동시에 어떤 임계 영역 내에 진입하려고 해도 하나만이 들어갈 수 있고, 변경이 이루어졌다면 그것이 메인 메모리에 저장된 원본에 반영되어 나머지 쓰레드가 캐시 업데이트 등을 통해 새로운 값을 참조할 수 있게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 동기화가 항상 좋은 것만은 아니다. 상호 배제는 성능에 대한 분명한 비용 지불이 수반되기 때문이다. 따라서 다수의 쓰레드가 동시에 특정 데이터에 접근해도 상관없지만, 변경에 대한 가시성만 필요한 경우에는 volatile을 사용하면 된다. 하나의 쓰레드만 쓰기 작업을 하고 나머지는 읽기 작업만 하는 환경이라면 volatile을 써도 동기화 효과를 얻을 수 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 여러 쓰레드가 동시에 쓰기 작업을 시도할 수 있는 환경에서는 volatile 만으로는 동기화 효과를 얻을 수 없다. volatile을 붙였다고 해서 상호 배제가 이루어지는 것은 아니니, 만약 여러 쓰레드의 작업 시도에 대해 상호 배제성이 필요하다면 synchronized 키워드를 활용해야 한다. 그래서 보통은 volatile과 synchronized를 함께 사용하기도 한다. 이렇게 하면 지정한 데이터를 메인 메모리로부터만 읽거나 쓰게할 수 있고(volatile), 읽기/쓰기 작업은 한번에 한 쓰레드만 하도록 할 수 있다.(synchronized)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Happens-Before Ordering&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;volatile로 선언된 변수의 재밌는 점은, 자신의 가시성 뿐만 아니라 자신 주변의 일반 변수들의 가시성에도 영향을 미친다는 것이다. volatile 변수의 값에 쓰기 작업을 하면, 변경 사항은 메인 메모리에 즉각 반영이 되는데, 이 때 자신의 변경 사항을 반영하는 김에 자신에 대한 쓰기 작업 이전에 발생한 모든 쓰기(volatile이 붙지 않은 변수일지라도)들도 모두 함께 메인 메모리에 반영시킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 volatile 변수의 값에 읽기 작업을 하면, 마지막으로 읽었던 시점 이후에 바뀐 모든 데이터를 함께 메인 메모리로부터 읽어온다. 따라서 volatile 변수 읽기 작업을 하면 해당 작업 이후의 읽기 작업들에는 메인 메모리에서 전달된 최신 변경사항이 반영되어 변경이 이루어진 값들을 읽을 수 있게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 volatile 변수가 주는 visibility 혜택을 &lt;b&gt;Volatile Visibility Gurantee&lt;/b&gt;라고 부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단, Volatile Visibility Gurantee는 코드의 실행 순서에 강하게 의존하기 때문에, 코드의 실행 순서가 바뀌어 버리면 효과를 누릴 수 없게 된다. 문제는 JIT 컴파일러가 컴파일 과정에서 코드 실행의 최적화를 위해 &lt;b&gt;Instruction Re-ordering&lt;/b&gt;을 진행해 우리가 실제로 작성한 소스코드와 실제 코드의 실행 순서가 달라질 수 있다는 것이다. Java는 Instruction Re-ordering으로 Volatile Visibility Gurantee를 해치는 것을 막기 위해 &lt;b&gt;Happens-Before Ordering&lt;/b&gt;이라는 대책을 내놓았다. 이에 따르면 일반적인 쓰기 작업들은 volatile 변수의 쓰기 작업 뒤쪽으로 re-order 될 수 없고, 읽기 작업들은 volatile 변수의 읽기 작업 앞쪽으로 re-order 될 수 없다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Writing on a volatile variable cannot &quot;happen before&quot; writing on any other variables, and reading on a volatile variable must &quot;happen before&quot; reading on any other variables.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Happens-Before Ordering이 적용됨에 따라 Volatile Visibility Gurantee를 걱정 없이 누릴 수 있게 되었다. 따라서 여러 개의 변수가 있을 때, volatile 키워드를 전부 다 붙이지 않더라도 volatile 변수를 쓰거나 읽는 코드 위치에 따라 마치 volatile을 모든 변수에 다 붙인 것 같은 효과를 볼 수 있다. 이렇게 일반 변수 입장에서 volatile 변수의 덕택으로 동일한 visibility gurantee 효과를 얻는 것을 Piggybacking(의역하면 무임승차 정도..?)이라고 부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 106px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px; text-align: center;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px; text-align: center;&quot;&gt;&lt;b&gt;volatile&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px; text-align: center;&quot;&gt;&lt;b&gt;synchronized&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 19px; text-align: center;&quot;&gt;대상&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 19px; text-align: center;&quot;&gt;필드(변수)&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 19px; text-align: center;&quot;&gt;메서드, 사용자 지정 블럭&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 19px; text-align: center;&quot;&gt;상호 배제성&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 19px; text-align: center;&quot;&gt;없음 (non-blocking)&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 19px; text-align: center;&quot;&gt;있음 (blocking)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px; text-align: center;&quot;&gt;가시성&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px; text-align: center;&quot;&gt;있음&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px; text-align: center;&quot;&gt;있음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px; text-align: center;&quot;&gt;데이터 출처&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px; text-align: center;&quot;&gt;메인 메모리&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px; text-align: center;&quot;&gt;캐시 메모리, 메인 메모리&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고 자료&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;volatile Keyword in Java&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.geeksforgeeks.org/volatile-keyword-in-java/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.geeksforgeeks.org/volatile-keyword-in-java/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Guide to the Volatile Keyword in Java&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.baeldung.com/java-volatile&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.baeldung.com/java-volatile&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java Happens Before Guarantee - Java Memory Model - Part 2&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=oY14UyP61F8&quot;&gt;https://www.youtube.com/watch?v=oY14UyP61F8&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;이 영상은 글의 마지막에 등장한 Instruction Re-ordering과 Happens-Before Ordering에 관한 것인데, 설명이 정말 잘 되어있다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Java</category>
      <category>java</category>
      <category>volatile</category>
      <author>jvnlee</author>
      <guid isPermaLink="true">https://jvnlee.tistory.com/9</guid>
      <comments>https://jvnlee.tistory.com/9#entry9comment</comments>
      <pubDate>Sun, 27 Mar 2022 04:26:02 +0900</pubDate>
    </item>
    <item>
      <title>Garbage Collector(GC)</title>
      <link>https://jvnlee.tistory.com/5</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Garbage Collector가 뭔가요?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Garbage는 말그대로 쓰레기, 즉 더 이상 사용하지 않거나 필요하지 않아 버리는 것들을 지칭한다. Java에서도 마찬가지의 의미로 쓰인다. 우리는 필요에 따라 수많은 객체들을 생성해서 사용하는데, 이들 중에 더 이상 참조되지 않는 객체들은 굳이 메모리에 계속 보관할 필요가 없기 때문에 garbage라고 부른다. Java의 JVM은 Garbage Collector(이하 GC)라는 청소부가 있어 이런 garbage들을 인식하고 자동적으로 메모리에서 그런 불필요한 데이터들을 비워준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;GC가 제거해줄 Garbage는 어디에 있나요?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JVM의 Runtime Data Area 안에는 Heap Area라는 메모리 영역이 존재한다. &lt;a href=&quot;https://jvnlee.tistory.com/4&quot;&gt;JVM 파헤치기&lt;/a&gt;에서 보았듯, Heap Area에는 new 연산자로 생성되는 객체들, 그리고 배열과 같이 동적으로 생성된 데이터들이 저장된다. GC는 이 Heap Area를 주시하고 있다가 더 이상 참조되지 않는 데이터를 제거해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Garbage인지 아닌지는 어떻게 구별하나요? &lt;span style=&quot;background-color: #ffffff; color: #202122;&quot;&gt;― &lt;/span&gt;Reachability&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 보았던 내용에서 GC가 &quot;더 이상 참조되지 않는&quot; 데이터를 메모리에서 제거해준다고 언급했다. 여기서 &lt;b&gt;Reachability&lt;/b&gt; 라는 개념이 등장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Reachability&lt;/b&gt;란, GC가 어떤 객체가 garbage인지 아닌지 판별하기 위한 개념으로, 이 객체에 대해 유효한 참조가 존재하면 &lt;b&gt;Reachable Object&lt;/b&gt;, 존재하지 않는다면 &lt;b&gt;Unreachable Object&lt;/b&gt;라고 분류한다. 그리고 GC는 이 중에서 Unreachable Object를 대상으로 garbage collecting을 수행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 대부분 애플리케이션의 코드 안에서는 여러 객체들이 수없이 얽힌 복잡한 참조 관계(참조 사슬)를 가지고 있다. 이 때 GC 입장에서 Reachability를 정확히 판단하려면 참조 사슬 안에서 유효한 최초 참조(누가 제일 먼저 불렀니?)를 찾아야 한다. 이 유효한 최초 참조를 Root Set이라고 부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GC는 Heap Area를 바라보고 있다고 했으므로, 이 안의 객체들이 어떻게 참조되는지를 먼저 짚어보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Heap Area 내의 다른 객체에 의한 참조&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 개별 쓰레드에게 할당된 Stack Area에서 Java 메서드 실행을 위해 사용하는 지역 변수와 파라미터에 의한 참조&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 개별 쓰레드에게 할당된 Native Method Stack에서 네이티브 메서드 실행을 위해 생성한 객체에 의한 참조&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. Method Area의 static 변수에 의한 참조&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Heap Area의 객체에 대해 이렇게 4가지 종류의 참조가 가능한데, 이 중에서 1번을 제외한 나머지가 Root Set으로 간주되어 Reachability 판단의 기준이 된다. Root Set에서 참조하는 것이 Reachable Object이고, 그렇지 않은 것이 Unreachable Object로 GC 대상이다.&amp;nbsp;이 이상의 자세한 내용은 아래 링크를 참고하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://d2.naver.com/helloworld/329631&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://d2.naver.com/helloworld/329631&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;GC가 동작할 때는 무슨 일이 벌어지나요? &lt;span style=&quot;background-color: #ffffff; color: #202122;&quot;&gt;― Stop-The-World&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JVM은 GC를 동작시킬 때 잠시 동안 전체 애플리케이션 실행을 멈추는데, 이를 &lt;b&gt;stop-the-world&lt;/b&gt;라고 한다. 이 때에는 GC를 실행 중인 쓰레드를 제외한 나머지 쓰레드는 작업을 잠시 중단했다가 GC가 끝난 후에 재개한다. 어떤 GC 알고리즘(GC를 수행하는 방식)이든 stop-the-world는 발생한다. 그래서 GC (알고리즘)튜닝은 일반적으로 stop-the-world의 시간을 줄이는 것이라고 보면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;GC는 어떤 방식으로 동작하나요?&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GC의 알고리즘은 Weak Generational Hypothesis라는 가설(이면서 전제 조건)을 가지고 출발한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 대부분의 객체는 얼마 지나지 않아 접근 불가능(Unreachable) 상태가 된다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;메서드의 실행 과정에서 생성되어 사용된 객체는 그 객체 자체를 반환하는게 아닌 이상, 메서드가 종료되면 더 이상 참조되지 않는다. 대다수의 객체의 수명이 이렇게 짧다고 보는 것이다. 그래서 이후에 살펴볼 Heap Area의 메모리 구조 상에서도 새로 생성된 객체를 저장하는 Young Generation 영역은 전체 대비 적은 비중을 차지한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 오래된 객체에서 젊은 객체로의 참조는 아주 적게 존재한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;오래된 객체는 말 그대로 생성된 지 오래된 객체, 젊은 객체는 갓 생성되었거나 생성된지 얼마 안된 객체를 뜻한다. GC에 의해 제거되지 않고 버틴 오래된 객체가 금방 GC에 의해 소멸될 가능성이 높은 젊은 객체를 참조할 가능성이 낮다 정도로 받아들이면 될 것 같다. &quot;오래된&quot;, &quot;젊은&quot;이라는 표현을 제대로 이해하려면 Heap Area에서 어떻게 데이터를 나누어 관리하는지를 알아야한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GC의 구체적인 알고리즘에 대해서는 &lt;a href=&quot;http://www.naver.com&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;별도의 글(2부)&lt;/a&gt;로 다루려고 한다. 여기서는 GC도 나름의 알고리즘을 갖추고 있어 그 알고리즘에 따라 garbage collecting을 한다는 것 정도만 알아두자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Heap Area에 저장된 데이터의 비밀&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GC의 타겟이 되는 Heap Area의 데이터들은 수명에 따라 다음과 같이 서로 다른 공간에 위치한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;868&quot; data-origin-height=&quot;564&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mUai7/btrvPf7pPC7/qVx6pzU9hjP1h1LKsbXgj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mUai7/btrvPf7pPC7/qVx6pzU9hjP1h1LKsbXgj0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mUai7/btrvPf7pPC7/qVx6pzU9hjP1h1LKsbXgj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmUai7%2FbtrvPf7pPC7%2FqVx6pzU9hjP1h1LKsbXgj0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;868&quot; height=&quot;564&quot; data-origin-width=&quot;868&quot; data-origin-height=&quot;564&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Young Generation&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Eden과 Survivor들로 이루어진 &quot;젊은 세대&quot;의 데이터들이 존재하는 영역이다. 여기서 발생하는 GC를 &lt;b&gt;Minor GC&lt;/b&gt;라고 부른다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;- Eden&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성경의 에덴 동산을 떠올리면 쉽다. 에덴이 태초의 생명들이 있던 곳이듯이, new 연산자로 생성되는 객체들이 여기에 위치한다. 대부분의 객체는 생성 이후 여기에 존재하다가 Minor GC에 의해 메모리에서 제거된다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;- Survivor&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Eden이 가득 차면 Minor GC가 이루어지고, 이 때 살아남은 객체들이 여기로 온다. Survivor는 공간이 2개로 분리되어 있는데, 살아남은 객체들은 우선 모두 Survivor1로 들어간다. 다음번 Minor GC 때 Eden이 비워지고, Eden에서 새로 넘어오는 생존자들이 또 있을텐데 이들과 원래 Survivor1에 있었던 생존자들은 모두 Survivor2로 들어간다. (따라서 Survivor1은 비워진 상태) Minor GC가 일어날 때마다 이런 방식으로 Survivor1 또는 2가 비워지고 안에 있던 데이터는 반대편 Survivor 영역으로 이동한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계속 살아남는 데이터는 Minor GC에 의해 번갈아 비워지는 Survivor의 두 공간 사이를 왔다갔다 하면서 age 값이 증가한다. 그러다가 age 값이 특정 기준치 이상이 되면 Old Generation으로 옮겨진다. 이렇게 Young에서 Old로 이동하는 것을 Promotion이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Old Generation&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;늙은 세대&quot;의 데이터들이 존재하는 영역이다. 여기서 발생하는 GC를 &lt;b&gt;Major GC&lt;/b&gt;라고 부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체들은 Survivor 영역에서 Promotion을 거쳐 이 곳으로 오는데, Old Generation 영역이 가득 차면 Major GC가 발생한다. 이 사실로부터 Major GC는 Minor GC에 비해 상대적으로 훨씬 적게 발생한다는 것을 유추할 수 있다. (무수한 Minor GC로부터 살아남은 극한의 생존자들이 이동해서 Old Generation을 가득 채워야 그제서야 Major GC 1번이다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Weak Generational Hypothesis의 2가지 가설 중 두번째에서 &quot;오래된 객체에서 젊은 객체로의 참조는 아주 적게 존재한다.&quot;라고 했었다. 그런데 만약 오래된 객체에서 젊은 객체로 참조하는 상황이 있다면 GC는 어떻게 대응할까?&lt;br /&gt;&lt;br /&gt;이런 경우를 대비해 Old Generation 영역에는 512 바이트짜리 card table이 존재한다. 이 table에는 Old Gen 객체가 참조하는 Young Gen 객체의 정보를 등록한다. 그리고 Minor GC가 일어날 때는 Old Gen 객체가 참조 중인 Young Gen 객체는 살려줘야하므로, GC가 이 테이블에 적힌 정보를 참고하여 그 객체는 남겨둔다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Permanent Generation&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Young부터 Old로 이어지는 Generation의 흐름 상 같이 언급되는 영역이기 때문에 논리적으로는 Heap의 일부인 것처럼 묘사를 하곤 한다. 그러나 엄밀히는 Heap 영역의 일부가 아니므로 주의해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java 8 부터는 Metaspace 라는 이름의 공간으로 대체되면서 Heap과의 논리적 관계 조차도 분리되었다고 보면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java 8 이전, Heap에 포함된 Permanent Generation 시절에는 클래스와 클래스와 관련된 배열 객체, 메서드의 메타 데이터와 static object, 상수화된 String object 등을 보관하는 역할이었는데, Java 8에서는 static object와 상수화된 String object만 Heap에 넘기고 나머지는 모두 Metaspace에 넘기는 것으로 변경되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Heap Area를 왜 이렇게 구분해서 관리하나요?&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 눈치챘을 수 있지만, Heap Area 내의 영역을 구분해서 데이터를 관리하면 다음과 같은 장점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Young Generation 위주로 GC를 하면 되므로, garbage collecting 시간이 단축된다. (stop-the-world로 애플리케이션이 중지되는 시간도 최소화)&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Minor GC가 발생하는 Young Generation 영역은 크기가 Old보다 작기 때문에 금방 가득 차게 된다. 따라서 자주 GC가 발생하여 시시때때로 비워진다고 보면 된다. Minor GC가 Major GC보다 훨씬 빠르기 때문에 이렇게 Minor GC 선에서 정리하는 것이 성능 측면에서 바람직하다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Young Generation 영역에는 GC가 자주 일어나므로 이 영역에 해당하는 만큼 여유 공간이 계속 확보된다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Memory Fragmentation(메모리 파편화)로 인해 Compaction(압축, 메모리 공간 정리 작업)이 발생하는 빈도를 최소화할 수 있다.&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Compaction과 Memory Fragmentation을 이해하려면, 아래의 상황을 상상해보면 된다.&lt;br /&gt;&lt;br /&gt;Heap Area에 데이터들이 가득 차면 어느 영역이냐 따라, Minor GC 또는 Major GC가 발생하게 된다. 그래서 불필요한 데이터를 모두 제거하고 나면, 제거된 데이터의 자리가 공백이 되어 메모리가 듬성듬성해질 것이다. 다음 번에 새 데이터를 저장하려고 할 때 그 데이터가 공백의 크기와 맞아떨어진다는 보장은 없기 때문에 여유 공간이 있음에도 효율적으로 사용하는 것이 불가능한 상황이 발생한다. 이렇게 메모리가 순차적으로 차있지 않고 듬성듬성 남아있는 상태를 &lt;b&gt;Memory Fragmentation&lt;/b&gt;이라고 한다. 그리고 중간에 남아있던 공백들을 없애기 위해 데이터를 한 곳으로 모으는 것을 &lt;b&gt;Compaction&lt;/b&gt;이라고 한다. ArrayList의 중간 어딘가의 데이터를 삭제하면 그 뒤에 위치하던 데이터들은 모두 index가 당겨지는 것과 비슷한 이치다.&lt;br /&gt;&lt;br /&gt;이러한 Compaction이 필요할 때 &lt;b&gt;Full GC&lt;/b&gt;가 나서게 된다. Full GC는 Heap Area 전체에 걸쳐 Compaction을 진행해주기 때문에 Minor GC나 Major GC보다도 더 느리고 큰 규모의 작업이다. 그리고 당연히 Full GC가 수행되는 동안 stop-the-world의 시간도 길어질 것이다. 이런 비효율적인 상황을 막으려면 애초에 GC가 자주 일어날 수 있는 Young Generation 영역에 Memory Fragmentation이 일어나지 않게 자주 메모리가 비워지는 것이 중요하다. 그리고 애플리케이션 스펙에 맞게 적절히 Young Generation과 Old Generation 영역의 크기 비율도 튜닝이 필요하다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고 자료&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://d2.naver.com/helloworld/1329&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://d2.naver.com/helloworld/1329&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;a href=&quot;https://blog.gceasy.io/2020/05/31/what-is-java-heap-fragmentation/&quot;&gt;https://blog.gceasy.io/2020/05/31/what-is-java-heap-fragmentation/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Java</category>
      <category>Garbage Collector</category>
      <category>GC</category>
      <category>java</category>
      <category>자바</category>
      <author>jvnlee</author>
      <guid isPermaLink="true">https://jvnlee.tistory.com/5</guid>
      <comments>https://jvnlee.tistory.com/5#entry5comment</comments>
      <pubDate>Sun, 13 Mar 2022 20:55:03 +0900</pubDate>
    </item>
    <item>
      <title>Java Virtual Machine(JVM)</title>
      <link>https://jvnlee.tistory.com/4</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;JVM이란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JVM은 Java Virtual Machine의 약자로, 쉽게 이야기하면 Java 코드의 실행(런타임) 환경이다. Java로 작성한 코드는 모두 JVM이라는 가상 환경 위에서 돌아간다. 흔히 Java를 설치한다고 하면 원하는 버전의 JDK(Java Development Kit)를 설치하게 되는데, 이 안에는 JVM과 여러 Java API들이 포함되어있다. 즉, Java와 JVM은 뗄 수 없는 존재라는 뜻이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;JVM이 주는 이점&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 한 번 작성하고, 어디서든 실행해라 (Write Once, Run Everywhere)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴파일된 Java 코드는 실행 시 OS로 바로 전달되지 않고 JVM을 거쳐야 하기 때문에 Java는 OS에 종속적이지 않다는 특징을 갖는다. 반면, JVM은 OS마다 상이하게 명령을 전달할 준비가 되어있어야 하므로, OS에 종속적이다. Java 언어를 만든 사람들은 이처럼 Java가 OS에 종속적이지 않게 하기 위해 JVM이라는 가상환경을 사용하는 방식을 고안했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 자동 메모리 관리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222222;&quot;&gt;1995년, Java가 처음 공개될 당시에만 해도 모든 컴퓨터 소프트웨어는 프로그래머가 직접 메모리를 관리하는 방식으로 작성되었다. 그러나 Java 진영의 JVM은 프로그래머를 대신해서 OS로부터 직접 메모리를 할당 받아 스스로 관리하는 능력이 있다. JVM 덕분에 프로그래머는 직접 메모리 관리를 하지 않고도 소프트웨어를 작성할 수 있게 되었다. (물론 JVM이 가진 메모리 한도 내에서 메모리 누수 등이 발생하지 않도록 효율적인 코드를 작성하는 것은 여전히 필요하다). 이 글에서 다루고자 하는 부분이 바로 JVM이 어떻게 메모리를 관리하는지와 관련이 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;JVM의 구조와 역할&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그래머가 작성한 Java 소스 코드(.java)는 javac라는 컴파일러의 손을 거쳐 바이트 코드인 클래스 파일(.class)로 컴파일된다. 그리고 그 바이트 코드를 받아와 실행하기까지의 과정을 JVM이 맡고 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;jvm-01.jpg&quot; data-origin-width=&quot;2223&quot; data-origin-height=&quot;1786&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bniJNO/btrvK4jz9wE/1fokPmNOSUritfUmpJqitK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bniJNO/btrvK4jz9wE/1fokPmNOSUritfUmpJqitK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bniJNO/btrvK4jz9wE/1fokPmNOSUritfUmpJqitK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbniJNO%2FbtrvK4jz9wE%2F1fokPmNOSUritfUmpJqitK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2223&quot; height=&quot;1786&quot; data-filename=&quot;jvm-01.jpg&quot; data-origin-width=&quot;2223&quot; data-origin-height=&quot;1786&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Class Loader&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바이트 코드를 받아와 Runtime Data Area에 배치하는 역할을 한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Execution Engine&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Class Loader가 불러온 바이트 코드를 실제로 실행하는 역할을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. Interpreter&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;런타임에 바이트 코드를 한줄씩 읽어들여 기계어로 변환한 뒤 CPU에 명령어로 내보낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. JIT Compilier (Just-In-Time Compiler)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기 버전의 JVM은 인터프리터 방식만 사용했기 때문에 속도가 느리다는 단점이 있었다. 이를 보완하기 위해 JIT Compiler로 자주 실행되는 코드를 파악하고 별도의 저장소에 기계어 상태로 저장해두는 방식이 도입되었다. &lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;이미 기계어로 변환된 이력이 있는 코드는 인터프리터가 굳이 또 읽어들여 기계어로 내보내는 과정을 거칠 필요 없이 저장해둔 것을 내보내게끔 하면 되므로 훨씬 효율적이다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;따라서 프로그램을 오래 실행할수록 프로그램의 실행 속도를 높여주는 장점이 있다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단, JIT Compiler 방식도 변환과 보관에 드는 비용이 있기 때문에 처음부터 모든 코드를 JIT Compiler를 통해 실행시키지는 않고, Interpreter를 사용하다가 일정 기준을 넘어서면 JIT Compiler 방식으로 실행한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;여기서 일정 기준이란 메서드가 호출된 횟수인데, JVM은 메서드 호출 시, 호출 횟수를 내부적으로 카운트한다. 일정 횟수 이상 호출되기 전까지는 Interpreter를 사용해서 실행하다가, 기준 횟수를 넘어서면 JIT Compiler를 통해 기계어로 컴파일한 후 보관하여 다음 번 호출 때 더 빠르게 실행시킬 수 있도록 돕는다.&lt;br /&gt;&lt;br /&gt;컴파일해서 보관된 메서드는 다시 호출 횟수가 0으로 초기화 되는데, 만약 이후에도 계속 호출되어 기준 횟수를 넘어서면 JIT Compiler가 다시 한번 컴파일을 하게 된다. 이 때에는 그 전보다 한 단계 더 최적화를 적용한 컴파일 과정을 거친다. 이 과정이 반복되다 보면 맥시멈 최적화 단계까지 도달하게 되고, 주로 가장 자주 쓰이는 메서드들이 이 단계까지 도달하게 된다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. Garbage Collector&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 이상 쓰이지 않는 참조 데이터를 메모리(Heap Area)에서 지워주는 청소부 역할을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://jvnlee.tistory.com/5&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;GC의 동작 원리&lt;/a&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Runtime Data Area&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JVM의 메모리 영역은 Runtime Data Area라고 불리고, 5가지 주요 영역으로 구분되어 있다. (Java 8 이상 기준)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 중에서 아래의 두 영역은 모든 쓰레드가 공유하는 메모리 영역이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JVM이 시작될 때 생성되므로 JVM과 생명 주기를 함께한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. Method Area&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;span style=&quot;color: #666666;&quot;&gt;Java 8 이전에는 Non-Heap Area 중 Permanent Generation의 일부였고, &lt;/span&gt;각 클래스(또는 인터페이스) 마다의 Class-Level 정보들을 가지고 있었다 (Runtime Constant Pool, static 필드와 메서드, 생성자 등)&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Runtime Constant Pool은 클래스/인터페이스 상수, 메서드, 필드에 대한 모든 레퍼런스(메모리 주소)를 저장하고 있다. JVM은 이곳에 필요한 메서드나 필드의 실제 메모리 주소를 저장해서 다른 클래스에서 이러한 정보들을 필요로 할 때 링크로서 사용한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Java 8 이후로는 Permanent Generation이 삭제되면서 &lt;b&gt;Metaspace&lt;/b&gt;라는 새로운 개념이 등장했다. 사실상 Method Area의 역할 대부분을 Metaspace가 한다고 봐도 무방하다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Metaspace는 JVM 메모리가 아닌 OS가 제공하는 네이티브 메모리에 존재하며, 이렇게 만든 이유는 기존의 Permanent Generation이 JVM 내의 고정 메모리로 할당되어 툭하면 OutOfMemoryError를 뱉어댔기 때문이다. 네이티브 메모리로 넘겨버리면 필요에 따라 유동적으로 메모리를 늘려나갈 수 있다. 공간이 부족해지면 단순히 커지기만 하는게 아니라, GC의 관리도 받는다.&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Metaspace는 다음과 같은 데이터를 저장한다.&lt;br /&gt;&lt;br /&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 314px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25.7109%; text-align: center;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;항목&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 74.2891%; text-align: center;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;설명&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 131px;&quot;&gt;
&lt;td style=&quot;width: 25.7109%; height: 131px; text-align: center;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Type Information&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 74.2891%; height: 131px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- Type(class or interface)의 전체 이름&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- Type의 직계 하위 클래스 전체 이름&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- Type의 class/interface 여부&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- Type의 modifier (public / abstract / final)&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 연관된 interface 이름 리스트&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 105px;&quot;&gt;
&lt;td style=&quot;width: 25.7109%; height: 105px; text-align: center;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Field Information&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 74.2891%; height: 105px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- Field Type&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- Field Modifier&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;(public / private / protected / static / final / volatile / transient)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 26px;&quot;&gt;
&lt;td style=&quot;width: 25.7109%; height: 26px; text-align: center;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Method Information&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 74.2891%; height: 26px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- Constructor를 포함한 모든 Method의 메타 데이터&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 26px;&quot;&gt;
&lt;td style=&quot;width: 25.7109%; height: 26px; text-align: center;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Runtime Constant Pool&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 74.2891%; height: 26px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- Type, Field, Method로의 모든 레퍼런스를 저장&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- JVM은 이 영역을 통해 실제 메모리 상의 주소를 찾아 참조&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 26px;&quot;&gt;
&lt;td style=&quot;width: 25.7109%; height: 26px; text-align: center;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Class Variable&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 74.2891%; height: 26px;&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- static 키워드로 선언된 변수를 저장&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 기본형이 아닌 static 변수는 레퍼런스 변수만 저장되고&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;실제 인스턴스는 Heap에 저장됨&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 클래스를 사용하기 이전에 이 변수들은 미리 메모리를 할당받음&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. Heap Area&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- new 연산자로 생성된 객체(+ 인스턴스 변수), 그리고 배열처럼 동적으로 생성된 데이터가 저장된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- String Constant Pool이 리터럴로 생성된 문자열 상수들을 관리한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;같은 내용의 문자열이더라도 new String(&quot;...&quot;)으로 생성하면 Heap에 각각 독립적인 객체로서 저장되지만, 문자열 리터럴 &quot;...&quot;로 생성하면 String Constant Pool에 하나만 저장된다. String Constant Pool에서는 리터럴로 생성한 문자열에 한해서는 내용이 같으면 하나의 메모리를 공유한다고 보면 된다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 효율적 메모리 관리를 위해 더 이상 참조되지 않는 데이터는 GC가 제거해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;​&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 아래의 세 영역은 개별 쓰레드에게 각각 할당되는 메모리 영역이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쓰레드가 생성될 때 함께 생성되므로, 각 쓰레드와 생명 주기를 함께한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. Stack Area&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 메서드 호출 스택&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 메서드 단위로 Stack Frame이 구성되는데, 크게 3가지 구성 요소로 이루어져있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;1. 지역 변수 배열&lt;br /&gt;메서드의 파라미터와 메서드 블럭 내부에 선언된 지역 변수들이 배열의 형태로 저장되어있다. 이 때, Primitive type 변수는 여기에 직접 값이 저장되고, Reference type 변수는 Heap Area에 저장되어 있기 때문에 주소값만 가져와 저장한다.&lt;br /&gt;&lt;br /&gt;2. 피연산자 스택(Operand Stack)&lt;br /&gt;프레임이 갓 생성되어있을 때는 비어있다. JVM은 작업을 instruction이라는 명령어 단위로 처리하는데, 이 중에서 피연산자를 피연산자 스택에 넣으라는 instruction이 실행되면 필요한 값들을 실제로 불러와 피연산자 스택에 push 한다. 또 다른 instruction은 스택에 들어있는 피연산자들을 하나씩 pop해서 실제 연산 작업 등을 수행하고 결과값을 다시 피연산자 스택에 push 한다. 이외에도 메서드 호출 시에 넘길 파라미터들을 담는 용도로도 쓰인다. 결국 메서드 실행에 필요한 재료와 중간 결과들을 담는 통이라고 보면 된다.&lt;br /&gt;&lt;br /&gt;3. 소속 클래스에 대한 참조&lt;br /&gt;Runtime Constant Pool(Metaspace의 일부)에 저장된 클래스 데이터에 대한 참조를 말한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Stack이므로 LIFO(Last-In-First-Out)로 동작하며, JVM은 스택 프레임을 push/pop 하는 연산을 수행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- printStackTrace()로 호출 스택에 쌓인 스택 프레임들을 출력할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. PC Register&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 현재 실행 중인 JVM 명령어의 주소를 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5. Native Method Stack&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- JNI(Java Native Interface)를 통해 호출되는 C나 C++로 된 메서드 실행에 사용되는 공간&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고 자료&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.ibm.com/docs/en/sdk-java-technology/7?topic=jc-jit-compiler-overview-2&quot;&gt;https://www.ibm.com/docs/en/sdk-java-technology/7?topic=jc-jit-compiler-overview-2&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Java</category>
      <category>jvm</category>
      <author>jvnlee</author>
      <guid isPermaLink="true">https://jvnlee.tistory.com/4</guid>
      <comments>https://jvnlee.tistory.com/4#entry4comment</comments>
      <pubDate>Fri, 11 Mar 2022 16:34:02 +0900</pubDate>
    </item>
  </channel>
</rss>