기술나눔

인기 과학 기사 : jvm의 실제 전투를 하나의 기사로 이해 (4) 탈출 분석에 대한 심층적 이해 탈출 분석

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

개요

Java의 객체는 힙 메모리에 할당됩니까?

좋습니다. 좀 더 구체적으로 설명하자면 다음 개체의 메모리가 어디에 할당되는지 살펴보겠습니다.

  1. public void test() {
  2. Object object = new Object();
  3. }
  4. 这个方法中的object对象,是在堆中分配内存么?

결과를 말해보세요. 객체는 스택이나 힙에 메모리를 할당할 수 있습니다.

핵심은 다음과 같습니다. JVM 구현 시 JVM의 성능을 향상하고 메모리 공간을 절약하기 위해 JVM은 "탈출 분석"이라는 기능을 제공합니다. 이는 현재 비교적 최첨단 최적화 기술입니다. Java 가상 머신이며 JIT이기도 합니다. 매우 중요한 최적화 기술입니다. jdk6은 이 기술을 도입하기 시작했고, jdk7은 기본적으로 이스케이프 분석을 활성화하기 시작했고, jdk8은 이스케이프 분석을 개선하기 시작했으며, JDK 9까지는 기본적으로 이스케이프 분석이 사용되며 특별한 컴파일 매개변수는 없습니다. 필수입니다.

이제 "객체는 스택에 메모리를 할당하거나 힙에 메모리를 할당할 수 있습니다"라는 문장을 이해하세요. jdk7 및 8에서는 여기의 객체가 힙에 메모리를 할당해야 하지만, jdk7에서는 메모리를 할당하는 것이 가능합니다. 이스케이프 분석만 지원되기 시작했습니다. jdk9는 기본적으로 이스케이프 분석만 지원하고 활성화하기 때문에 스택에 할당될 가능성이 높습니다(여기서 개체는 매우 작습니다).

JIT 컴파일러(Just-In-Time 컴파일러)의 개발과 이스케이프 분석 기술의 점진적인 성숙으로 인해 스택 할당 및 스칼라 대체 최적화 기술은 Java In에서 "모든 객체가 힙에 할당됩니다"가 덜 절대적이게 만듭니다. 가상 머신에서는 개체에 힙에 메모리가 할당되지만 특별한 경우가 있습니다. 즉, 이스케이프 분석 후 개체가 메서드에서 이스케이프되지 않는 것으로 확인되면 스택에 할당되도록 최적화될 수 있습니다. 메서드가 실행되면 스택 프레임이 팝되고 개체가 해제됩니다. 이렇게 하면 힙에 메모리를 할당하고 가비지 수집을 수행할 필요가 없습니다.핫스팟은 현재 이 작업을 수행하지 않습니다.) 이는 가장 일반적인 오프힙 스토리지 기술이기도 합니다.

JDK 6u23(기억 가능한 주요 버전 JDK7) 이후에는 Hotspot에서 기본적으로 이스케이프 분석이 활성화됩니다. 이전 버전을 사용하는 경우 "-XX:+DoEscapeAnalytic" 옵션을 통해 이스케이프 분석을 표시할 수 있습니다. 이탈 분석에 대한 필터 결과를 봅니다.

Hotspot은 이스케이프 분석을 통해 스칼라 대체를 구현합니다(이스케이프되지 않은 개체는 스칼라 및 집계로 대체되어 코드 효율성을 향상시킬 수 있음). 그러나 이스케이프되지 않은 개체는 여전히 힙에 메모리를 할당하므로 여전히 모든 개체가 할당되었다고 말할 수 있습니다. 힙에 있는 메모리.

또한 Open JDK를 기반으로 심층적인 사용자 정의가 가능합니다.타오바오 VM그 중 혁신적인 GCIH(GC Invisible Heap) 기술은 오프 힙을 구현하여 수명 주기가 긴 객체를 힙에서 힙 외부로 이동시키고, GC는 GCIH 내부의 Java 객체를 관리하지 않으므로 GC 재활용 빈도를 줄이고 목적을 향상시킵니다. GC 회수 효율.

스택: 각 메소드가 실행되면 스택 프레임이 동시에 생성되어 지역 변수 테이블, 작업 스택, 동적 연결, 메소드 종료 등과 같은 정보를 저장합니다. 각 메서드가 호출되어 실행이 완료될 때까지의 과정은 가상머신 스택에서 스택 프레임이 스택에 푸시(Push)되어 스택 밖으로 튀어나오기까지의 과정에 해당한다.

더미:개체가 인스턴스화되면 개체가 힙에 할당되고 힙에 대한 참조가 스택에 푸시됩니다.

탈출하다:객체에 대한 포인터가 여러 메소드나 스레드에 의해 참조되는 경우 일반적으로 포인터가 이스케이프된다고 말합니다. 일반적으로 반환 객체와 전역 변수는 이스케이프됩니다.

탈출 분석:이러한 탈출 현상을 분석하는 데 사용되는 방법을 탈출 분석이라고 합니다.

탈출 분석 최적화 - 스택 할당:스택에서의 할당은 메서드의 지역 변수에 의해 생성된 인스턴스(이스케이프 발생 없음)가 스택에 할당되며 힙에 할당할 필요가 없음을 의미합니다. 할당이 완료된 후 호출 스택에서 실행이 계속됩니다. 마지막으로 스레드가 종료되고 스택 공간이 재활용되며 로컬 Variable 객체도 재활용됩니다.

Java 객체 메모리 할당 프로세스

  1. 스택 할당(이스케이프 분석)이 활성화된 경우 JVM은 먼저 스택에 할당합니다.
  2. 스택에 대한 할당이 활성화되지 않거나 조건이 충족되지 않으면 TLAB 할당이 시작됩니다.
  3. TLAB 할당이 실패하거나 일관성이 없는 경우 Old Generation 할당에 들어갈 수 있는지 여부를 결정합니다.
  4. Old Generation에 들어갈 수 없으면 eden 할당에 들어갑니다.
  5. 모든 개체가 힙에 할당되는 것은 아닙니다. 힙 외에 스택 및 TLAB에도 개체를 할당할 수 있습니다. (대부분의 객체는 힙에 할당됨)

Java의 객체는 반드시 힙에 할당됩니까?

답변: 반드시 그런 것은 아닙니다.

이스케이프 분석 조건이 만족되면 스택에 객체를 할당할 수 있습니다.힙 메모리 할당 및 GC 부담을 줄입니다.스택 메모리는 제한되어 있으므로 객체가 스칼라 치환 조건을 만족하면,주제를 여러 부분으로 나누기 위해 추가 작업이 수행됩니다.스칼라 대체의 구체적인 방법은 다음과 같습니다. JVM은 객체를 추가로 분할하고 객체를 이 방법에서 사용되는 여러 멤버 변수로 분해합니다.따라서 스택 메모리와 레지스터를 더 잘 활용한다는 목표가 달성되었습니다.

힙의 개체를 스택에 할당하는 방법에는 이스케이프 분석이 필요합니다.

이는 Java 프로그램의 동기화 부하 및 메모리 힙 할당 부담을 효과적으로 줄일 수 있는 교차 기능 전역 데이터 흐름 분석 알고리즘입니다. Java Hotspot 컴파일러는 이스케이프 분석을 통해 새 개체 참조의 사용 범위를 분석하고 이 개체를 힙에 할당할지 여부를 결정할 수 있습니다.

탈출 분석의 기본 동작은 객체의 동적 범위를 분석하는 것입니다.

  1. 메소드 내에 객체가 정의되어 있고 해당 객체가 메소드 내에서만 사용되는 경우에는 Escape가 발생하지 않은 것으로 간주됩니다.
  2. 개체가 메서드에 정의되고 외부 메서드에서 참조되면 이스케이프가 발생합니다. 예를 들어 다른 장소에 호출 매개변수로 전달됩니다.

컴퓨터 언어 컴파일러 최적화 원리에서 이스케이프 분석은 포인터의 동적 범위를 분석하는 방법을 의미하며 컴파일러 최적화 원리의 포인터 분석 및 모양 분석과 관련이 있습니다. 변수(또는 개체)가 메서드에 할당되면 해당 포인터가 전역적으로 반환되거나 참조될 수 있으며, 이는 다른 메서드나 스레드에서 참조됩니다. 이러한 현상을 포인터(또는 참조) 이스케이프라고 합니다. 평신도의 용어로, 객체의 포인터가 여러 메소드나 스레드에 의해 참조되는 경우 객체의 포인터(또는 객체) Escape를 호출합니다(이때 객체가 메소드나 스레드의 로컬 범위에서 탈출하기 때문입니다).

탈출 분석이란 무엇입니까?

간략한 설명: "이스케이프 분석: 포인터의 동적 범위를 결정하는 정적 분석입니다. 프로그램에서 포인터에 액세스할 수 있는 위치를 분석할 수 있습니다." JVM의 적시 컴파일 컨텍스트에서 이스케이프 분석은 새로 생성된 개체가 이스케이프됩니다.

객체 이스케이프 여부를 결정하기 위한 JIT(Just-In-Time) 컴파일의 기초: 하나는 객체가 힙(힙에 있는 객체의 정적 필드 또는 인스턴스 필드)에 저장되어 있는지 여부이고, 다른 하나는 객체가 힙에 전달되는지 여부입니다. 알 수 없는 코드.

Escape Analysis는 현재 Java Virtual Machine에서 비교적 최첨단 최적화 기술입니다. 유형 상속 관계 분석과 마찬가지로 코드를 직접 최적화하는 수단이 아니라 다른 최적화 수단의 기반을 제공하는 분석 기술입니다.

Escape Analysis : 객체가 메소드 외부에서 접근할 것인지, 즉 메소드의 범위를 벗어나는지 판단하는데 사용되는 매우 중요한 JIT 최적화 기술이다. 이스케이프 분석은 JIT 컴파일러의 단계입니다. JIT를 통해 메서드 내부에서 사용하도록 제한할 수 있고 외부로 이스케이프하지 않는 개체를 결정할 수 있습니다. 그런 다음 힙 대신 스택에 할당하는 등 최적화할 수 있습니다. 또는 스칼라 대체를 수행하여 객체를 여러 기본 유형으로 분할하여 저장합니다. Java 프로그램의 동기화 부하와 메모리 힙 할당, 가비지 수집 부담을 효과적으로 줄일 수 있는 교차 기능 전역 데이터 흐름 분석 알고리즘입니다. Java Hotspot 컴파일러는 이스케이프 분석을 통해 새 개체 참조의 사용 범위를 분석하고 이 개체를 힙에 할당할지 여부를 결정할 수 있습니다.

이스케이프 분석은 주로 지역 변수에 초점을 맞춰 힙에 할당된 개체가 메서드 범위를 벗어났는지 여부를 확인합니다. 이는 컴파일러 최적화 원리의 포인터 분석 및 형태 분석과 관련이 있습니다. 변수(또는 개체)가 메서드에 할당되면 해당 포인터가 전역적으로 반환되거나 참조될 수 있으며, 이는 다른 메서드나 스레드에서 참조됩니다. 이러한 현상을 포인터(또는 참조) 이스케이프라고 합니다. 일반인의 관점에서 보면, 객체의 포인터가 여러 메서드나 스레드에 의해 참조되는 경우 객체의 포인터가 이스케이프되었다고 말합니다. 코드 구조와 데이터 사용을 올바르게 설계하면 탈출 분석을 더 잘 활용하여 프로그램 성능을 최적화할 수 있습니다. 또한 힙에 객체를 할당하는 오버헤드를 줄이고 이스케이프 분석을 통해 메모리 활용도를 향상시킬 수 있습니다.

이스케이프 분석은 개체가 수명 동안 메서드 범위 외부에서 이스케이프되었는지 여부를 확인하는 데 사용되는 기술입니다. Java 개발에서 이스케이프 분석은 해당 최적화를 수행하고 프로그램 성능 및 메모리 활용 효율성을 향상시키기 위해 객체의 수명주기와 범위를 결정하는 데 사용됩니다.

객체가 생성되면 메소드 내부에서 사용할 수도 있고, 다른 메소드나 스레드에 전달되어 메소드 외부에 계속 존재할 수도 있습니다. 객체가 메소드 범위를 벗어나지 않으면 JVM은 이를 힙 대신 스택에 할당할 수 있으므로 힙 메모리 할당 및 가비지 수집의 오버헤드를 피할 수 있습니다.

  1. 关于逃逸分析的论文在1999年就已经发表,但直到Sun JDK 1.6才实现了逃逸分析,而且直到现在这项优化尚未足够成熟,仍有很大的改进余地。不成熟的原因主要是不能保证逃逸分析的性能收益必定高于它的消耗。如果要完全准确的判断一个对象是否会逃逸,需要进行数据流敏感的一系列复杂分析,从而确定程序各个分支执行时对此对象的影响。这是一个相对高耗时的过程,如果分析完后发现没有几个不逃逸的对象,那这些运行期耗用的时间就白白浪费了,所以目前虚拟机只能采用不那么准确,但时间压力相对较小的算法来完成逃逸分析。还有一点是,基于逃逸分析的一些优化手段,如上面提到的“栈上分配”,由于HotSpot虚拟机目前的实现方式导致栈上分配实现起来比较复杂,因此在HotSpot中暂时还没有做这项优化。
  2. 在测试结果中,实施逃逸分析后的程序在MicroBenchmarks中往往能运行出不错的成绩,但是在实际的应用程序,尤其是大型程序中反而发现实施逃逸分析可能出现效果不稳定的情况,或因分析过程耗时但却无法有效判别出非逃逸对象而导致性能(即使编译的收益)有所下降,所以在很长的一段时间里,即使是Server Compiler,也默认不开启逃逸分析(在JDK 1.6 Update 23的Server Compiler中才开始默认开启了逃逸分析),甚至在某些版本(如JDK 1.6 Update 18)中还曾经短暂的完全禁止了这项优化。
  3. 如果有需要,并且确认对程序运行有益,用户可以使用参数-XX:+DoEscapeAnalysis来手动开启逃逸分析,开启之后可以通过参数-XX:+PrintEscapeAnalysis来查看分析结果。有了逃逸分析支持之后,用户可以使用参数-XX:EliminateAllocations来开启标量替换,使用+XX:+EliminateLocks来开启同步消除,使用参数-XX:PrintEliminateAllocations查看标量的替换情况。
  4. 尽管目前逃逸分析的技术仍不是十分成熟,但是他却是即时编译器优化技术的一个重要的方向,在今后的虚拟机中,逃逸分析技术肯定会支撑起一系列使用有效的优化技术。

탈출분석의 기본원리

JVM 탈출 분석의 기본 원리는 정적 분석과 동적 분석 방법을 통해 객체의 탈출 상황을 파악하는 것입니다.

Java 컴파일 시스템에서 Java 소스 코드 파일을 컴퓨터에서 실행 가능한 기계 명령으로 변환하는 프로세스에는 두 가지 컴파일 단계가 필요합니다.

컴파일의 첫 번째 섹션은 프런트엔드 컴파일러를 나타냅니다..java 파일로 변환됨.class 파일 (바이트코드 파일). 프런트 엔드 컴파일러 제품은 JDK의 Javac 또는 Eclipse JDT의 증분 컴파일러일 수 있습니다.

두 번째 컴파일 단계에서 JVM은 바이트코드를 해석하여 해당 기계어 명령어로 번역하고, 바이트코드를 하나씩 읽어서 하나씩 기계어 코드로 해석하고 번역합니다.

분명히 중간 해석 프로세스로 인해 실행 속도는 실행 가능한 바이너리 바이트코드 프로그램의 속도보다 필연적으로 훨씬 느릴 것입니다. 이는 전통적인 JVM 인터프리터(Interpreter)의 기능이다.

중개인을 없애고 효율성을 높이는 방법은 무엇입니까?

이러한 효율성 문제를 해결하기 위해 JIT(Just In Time Compiler) 기술이 도입되었습니다.

JIT 기술이 도입된 후에도 Java 프로그램은 여전히 ​​인터프리터를 통해 해석되고 실행됩니다. 즉, 본문은 여전히 ​​해석되고 실행되지만 중간 링크는 부분적으로 제거됩니다.

JIT 컴파일러(Just-in-timeCompiler) 적시 컴파일. 최초의 Java 구현 솔루션은 각 Java 명령을 동등한 마이크로프로세서 명령으로 변환하고 변환된 명령의 순서에 따라 순차적으로 실행하는 변환기(인터프리터) 세트로 구성되었습니다. 동등한 마이크로프로세서 명령을 사용하는 경우 이 모드는 매우 느리게 실행됩니다.

중간 링크를 부분적으로 제거하는 방법은 무엇입니까?

JVM이 특정 메서드나 코드 블록이 특히 자주 실행되고 있음을 발견하면 이를 "핫스팟 코드"로 간주합니다. 그런 다음 JIT는 "핫 코드"의 일부를 로컬 시스템과 관련된 기계 코드로 변환하고 최적화한 다음 다음 사용을 위해 번역된 기계 코드를 캐시합니다.

번역된 기계어 코드를 어디에 캐시하나요? 이 캐시를 코드 캐시라고 합니다. JVM과 WEB 애플리케이션 간의 높은 동시성을 달성하는 방법은 유사하며 여전히 캐시 아키텍처를 사용하고 있음을 알 수 있습니다.

JVM이 다음에 동일한 핫 코드를 만나면 해석의 중간 링크를 건너뛰고 코드 캐시에서 직접 기계어 코드를 로드한 후 다시 컴파일하지 않고 직접 실행합니다.

따라서 JIT의 전반적인 목표는 핫 코드를 발견하는 것이며 핫 코드는 성능 향상의 핵심이 되었습니다. 핫스팟 JVM이라는 이름은 핫 코드를 식별하고 이름에 쓰는 것이 평생 추구한 방식입니다.

따라서 JVM의 전반적인 전략은 다음과 같습니다.

  • 대부분의 흔하지 않은 코드의 경우 이를 기계어 코드로 컴파일하는 데 시간을 소비할 필요가 없으며 해석 및 실행을 통해 실행합니다.

  • 반면에 작은 부분만 차지하는 핫 코드의 경우 이를 기계어 코드로 컴파일하여 이상적인 실행 속도를 얻을 수 있습니다.

JIT(Just In Time Compilation)의 등장과 통역사의 차이

(1) 인터프리터는 바이트코드를 기계어 코드로 해석합니다. 다음에 동일한 바이트코드를 만나더라도 반복 해석을 수행합니다.

(2) JIT는 일부 바이트 코드를 기계어 코드로 컴파일하여 코드 캐시에 저장합니다. 다음에 동일한 코드가 나타나면 다시 컴파일하지 않고 직접 실행합니다.

(3) 인터프리터는 바이트코드를 모든 플랫폼에 공통적인 기계어 코드로 해석합니다.

(4) JIT는 플랫폼 유형을 기반으로 플랫폼별 기계어 코드를 생성합니다.

JVM에는 주로 C1, C2, Graal(실험용) 등 여러 JIT(Just-In-Time) 컴파일러가 포함되어 있습니다.

여러 JIT(Just-In-Time) 컴파일러가 바이트코드를 최적화하고 기계어 코드를 생성합니다.

  • C1은 메서드 인라인화, 가상화 해제, 중복성 제거 등을 포함하여 간단하고 안정적인 바이트코드 최적화를 수행합니다. -client를 통해 C1 컴파일을 강제로 지정할 수 있습니다.
  • C2는 분기 빈도 예측, 동기 삭제 등을 포함하여 바이트코드에 대한 근본적인 최적화를 수행합니다. C2 컴파일은 -server를 통해 강제로 지정될 수 있습니다.

JVM은 실행 상태를 5가지 수준으로 나눕니다.

  • 레벨 0, 통역사

  • 레벨 1, C1 JIT(Just-In-Time) 컴파일러를 사용하여 컴파일 및 실행(프로파일링 없음)

  • 레이어 2, C1 JIT(Just-In-Time) 컴파일러를 사용하여 컴파일 및 실행(기본 프로파일링 포함)

  • 레이어 3, C1 JIT(Just-In-Time) 컴파일러를 사용하여 컴파일 및 실행(완전한 프로파일링 포함)

  • 레벨 4, C2 JIT(Just-In-Time) 컴파일러를 사용하여 컴파일 및 실행

JVM은 C2를 직접 활성화하지 않고 먼저 C1 컴파일을 통해 프로그램의 실행 상태를 수집한 다음 분석 결과에 따라 C2를 활성화할지 여부를 결정합니다.

계층화된 컴파일 모드에서 가상 머신 실행 상태는 단순한 것에서 복잡한 것, 빠른 것에서 느린 것까지 5개의 계층으로 나뉩니다.

컴파일하는 동안 JIT는 프로세스 속도를 높이기 위해 핫 코드를 캐싱하는 것 외에도 코드에 대한 많은 최적화를 수행합니다.

일부 최적화의 목적은 다음과 같습니다.메모리 힙 할당 압력 감소 JIT 최적화의 중요한 기술 중 하나는 이탈 분석(Escape Analysis)입니다. 탈출 분석에 따르면 JIT(Just-In-Time) 컴파일러는 컴파일 프로세스 중에 다음과 같이 코드를 최적화합니다.

  • 잠금 제거: 하나의 스레드에 의해서만 잠금 개체가 잠기면 JIT(Just-In-Time) 컴파일러가 잠금을 제거합니다.
  • 스택에서의 할당: 객체가 이스케이프되지 않으면 객체가 스택에 직접 할당됩니다. 스레드가 재활용되므로 많은 양의 JVM 코드가 힙 할당이므로 현재 JVM은 스택에서의 할당을 지원하지 않습니다. 하지만 스칼라 대체를 사용합니다.
  • 스칼라 대체: 개체가 이스케이프되지 않으면 현재 개체가 여러 지역 변수로 분할되어 가상 머신 스택의 지역 변수 테이블에 할당됩니다.

1. 정적 분석은 컴파일 타임에 수행되는 분석입니다.

개체가 이스케이프될 수 있는지 여부를 결정하기 위해 코드의 정적 구조를 확인합니다. 예를 들어 개체가 클래스의 멤버 변수에 할당되거나 외부 메서드에 반환되면 개체가 이스케이프되는 것으로 판단할 수 있습니다.

2. 동적 분석은 런타임에 수행되는 분석입니다.

메서드 호출 및 개체 참조의 동작을 관찰하여 개체가 이스케이프되는지 여부를 결정합니다. 예를 들어, 여러 스레드에서 객체를 참조하는 경우 해당 객체가 이스케이프된 것으로 판단할 수 있습니다.

이스케이프 분석은 코드에 대한 심층 분석을 수행하여 메서드 수명 동안 개체가 메서드 범위 외부에서 이스케이프되었는지 여부를 확인합니다. 객체가 이스케이프되지 않으면 JVM은 이를 힙 대신 스택에 할당할 수 있습니다.

탈출 상태: 전역 탈출, 매개변수 탈출, 탈출 없음

객체에는 전역 이스케이프, 매개변수 이스케이프, 이스케이프 없음이라는 세 가지 이스케이프 상태가 있습니다.

글로벌 탈출(GlobalEscape): 즉, 개체의 범위가 현재 메서드나 현재 스레드에서 이스케이프됩니다.

일반적으로 다음과 같은 시나리오가 있습니다.
① 객체가 정적 변수이다
② 객체는 탈출한 객체이다
③ 객체는 현재 메소드의 반환값으로 사용됩니다.

매개변수 이스케이프(ArgEscape): 즉, 개체가 메서드 매개 변수로 전달되거나 매개 변수에 의해 참조되지만 호출 프로세스 중에 전역 이스케이프가 발생하지 않습니다. 이 상태는 호출된 메서드의 바이트 코드에 의해 결정됩니다.

탈출구가없는: 즉, 메서드의 개체가 이스케이프되지 않습니다.

이스케이프 상태 샘플 코드는 다음과 같습니다.

  1. public class EscapeAnalysisTest {
  2. public static Object globalVariableObject;
  3. public Object instanceObject;
  4. public void globalVariableEscape(){
  5. globalVariableObject = new Object(); // 静态变量,外部线程可见,发生逃逸
  6. }
  7. public void instanceObjectEscape(){
  8. instanceObject = new Object(); // 赋值给堆中实例字段,外部线程可见,发生逃逸
  9. }
  10. public Object returnObjectEscape(){
  11. return new Object(); // 返回实例,外部线程可见,发生逃逸
  12. }
  13. public void noEscape(){
  14. Object noEscape = new Object(); // 仅创建线程可见,对象无逃逸
  15. }
  16. }

이스케이프 방법: 메서드 이스케이프 및 스레드 이스케이프


1. 메소드 이스케이프: 메소드 본문에서 호출 매개변수로 메소드에 전달되거나 객체로 직접 반환되는 등 외부 메소드에서 참조할 수 있는 지역 변수를 정의합니다. 혹은 객체가 메소드 밖으로 튀어나오는 것으로 이해될 수 있다.

메소드 이스케이프에는 다음이 포함됩니다.

  • 매개변수를 호출하여 객체 주소를 다른 메소드에 전달합니다.
  • 객체는 return 문을 통해 다른 메소드에 대한 객체 포인터를 반환합니다.
  • 등.
  1. 我们可以用下面的代码来表示这个现象。
  2. //StringBuffer对象发生了方法逃逸
  3. public static StringBuffer createStringBuffer(String s1, String s2) {
  4. StringBuffer sb = new StringBuffer();
  5. sb.append(s1);
  6. sb.append(s2);
  7. return sb;
  8. }
  9. 上面的例子中,StringBuffer 对象通过return语句返回。
  10. StringBuffer sb是一个方法内部变量,上述代码中直接将sb返回,这样这个StringBuffer有可能被其他方法所改变,这样它的作用域就不只是在方法内部,虽然它是一个局部变量,称其逃逸到了方法外部。
  11. 甚至还有可能被外部线程访问到,譬如赋值给类变量或可以在其他线程中访问的实例变量,称为线程逃逸。
  12. 不直接返回 StringBuffer,那么StringBuffer将不会逃逸出方法。
  13. 具体的代码如下:
  14. // 非方法逃逸
  15. public static String createString(String s1, String s2) {
  16. StringBuffer sb = new StringBuffer();
  17. sb.append(s1);
  18. sb.append(s2);
  19. return sb.toString();
  20. }
  21. 可以看出,想要逃逸方法的话,需要让对象本身被外部调用,或者说, 对象的指针,传递到了 方法之外。

이스케이프 분석이 발생했는지 빠르게 확인하는 방법은 무엇입니까? 새로운 개체 엔터티가 메서드 외부에서 호출되는지 살펴보겠습니다.

  1. public class EscapeAnalysis {
  2.  
  3.     public EscapeAnalysis obj;
  4.  
  5.     /**
  6.      * 方法返回EscapeAnalysis对象,发生逃逸
  7.      * @return
  8.      */
  9.     public EscapeAnalysis getInstance() {
  10.         return obj == null ? new EscapeAnalysis():obj;
  11.     }
  12.  
  13.     /**
  14.      * 为成员属性赋值,发生逃逸
  15.      */
  16.     public void setObj() {
  17.         this.obj = new EscapeAnalysis();
  18.     }
  19.  
  20.     /**
  21.      * 对象的作用于仅在当前方法中有效,没有发生逃逸
  22.      */
  23.     public void useEscapeAnalysis() {
  24.         EscapeAnalysis e = new EscapeAnalysis();
  25.     }
  26.  
  27.     /**
  28.      * 引用成员变量的值,发生逃逸
  29.      */
  30.     public void useEscapeAnalysis2() {
  31.         EscapeAnalysis e = getInstance();
  32.     }
  33. }

2. 스레드 이스케이프: 이 객체는 인스턴스 변수에 할당되거나 다른 스레드에 의해 액세스되는 등 다른 스레드에 의해 액세스됩니다. 개체가 현재 스레드를 이스케이프했습니다.

탈출 분석을 위한 최적화 전략

이스케이프 분석은 Java 프로그램에 스택 할당, 동기화 제거, 스칼라 대체 및 메소드 인라인과 같은 최적화 전략을 가져올 수 있습니다.

탈출 분석 관련 매개변수:

  1. -XX:+DoEscapeAnalysis 开启逃逸分析
  2. -XX:+PrintEscapeAnalysis 开启逃逸分析后,可通过此参数查看分析结果。
  3. -XX:+EliminateAllocations 开启标量替换
  4. -XX:+EliminateLocks 开启同步消除
  5. -XX:+PrintEliminateAllocations 开启标量替换后,查看标量替换情况。

1. 스택 할당

이스케이프 분석은 메서드 범위를 벗어나지 않는 개체를 결정하고 이러한 개체를 힙 대신 스택에 할당할 수 있습니다. 스택에 할당된 객체는 가비지 컬렉션 없이 메소드 호출 수명주기 내에서 생성 및 소멸되므로 프로그램 실행 효율성이 향상됩니다.

일반적인 상황에서 탈출할 수 없는 객체는 상대적으로 큰 공간을 차지합니다. 스택의 공간을 사용할 수 있으면 메서드가 종료될 때 많은 수의 객체가 파괴되어 GC 부담이 줄어듭니다.

        스택에 대한 할당 아이디어스택에서의 할당은 JVM에서 제공하는 최적화 기술입니다.
아이디어는 다음과 같습니다.

  1. 스레드 전용 개체(다른 스레드에서 액세스할 수 없는 개체)의 경우 힙 메모리 대신 스택 메모리에 할당할 수 있습니다. 이는 집계 변수를 스칼라로 대체하는 솔루션입니다.
  2. 스택에 할당하면 메서드가 종료된 후 GC 개입 없이 자동으로 삭제될 수 있어 시스템 성능이 향상된다는 장점이 있습니다.
  3. 분산된 개체 수가 많은 경우 스택에 할당하는 것은 좋은 개체 할당 전략을 제공합니다. 스택에 속도 블록을 할당하면 GC 재활용의 부정적인 영향을 효과적으로 피할 수 있습니다.

문제: 스택 메모리가 상대적으로 작기 때문에 큰 개체는 스택에 할당할 수 없으며 적합하지 않습니다.
        스택 내 할당 활성화
스택에서의 할당은 이스케이프 분석 및 스칼라 대체를 기반으로 하므로 이스케이프 분석 및 스칼라 대체가 활성화되어야 합니다. 물론 JDK1.8은 기본적으로 활성화됩니다.

  1. 开启逃逸分析:-XX:+DoEscapeAnalysis
  2. 关闭逃逸分析:-XX:-DoEscapeAnalysis
  3. 显示分析结果:-XX:+PrintEscapeAnalysis
  4. 开启标量替换:-XX:+EliminateAllocations
  5. 关闭标量替换:-XX:-EliminateAllocations
  6. 显示标量替换详情:-XX:+PrintEliminateAllocations

스택 할당의 예:

  1. 示例1
  2. import java.lang.management.ManagementFactory;
  3. import java.util.List;
  4. /**
  5. * 逃逸分析优化-栈上分配
  6. * 栈上分配,意思是方法内局部变量(未发生逃逸)生成的实例在栈上分配,不用在堆中分配,分配完成后,继续在调用栈内执行,最后线程结束,栈空间被回收,局部变量对象也被回收。
  7. * 一般生成的实例都是放在堆中的,然后把实例的指针或引用压入栈中。
  8. *虚拟机参数设置如下,表示做了逃逸分析 消耗时间在10毫秒以下
  9. * -server -Xmx10M -Xms10M
  10. -XX:+DoEscapeAnalysis -XX:+PrintGC
  11. *
  12. *虚拟机参数设置如下,表示没有做逃逸分析 消耗时间在1000毫秒以上
  13. * -server -Xmx10m -Xms10m
  14. -XX: -DoEscapeAnalysis -XX:+PrintGC
  15. * @author 734621
  16. *
  17. */
  18. public class OnStack{
  19. public static void alloc(){
  20. byte[] b=new byte[2];
  21. b[0]=1;
  22. }
  23. public static void main(String [] args){
  24. long b=System.currentTimeMillis();
  25. for(int i=0;i<100000000;i++){
  26. alloc();
  27. }
  28. long e=System.currentTimeMillis();
  29. System.out.println("消耗时间为:" + (e - b));
  30. List<String> paramters = ManagementFactory.getRuntimeMXBean().getInputArguments();
  31. for(String p : paramters){
  32. System.out.println(p);
  33. }
  34. }
  35. }
  36. 加逃逸分析的结果
  37. [GC (Allocation Failure) 2816K->484K(9984K), 0.0013117 secs]
  38. 消耗时间为:7
  39. -Xmx10m
  40. -Xms10m
  41. -XX:+DoEscapeAnalysis
  42. -XX:+PrintGC
  43. 没有加逃逸分析的结果如下:
  44. [GC (Allocation Failure) 3320K->504K(9984K), 0.0003174 secs]
  45. [GC (Allocation Failure) 3320K->504K(9984K), 0.0002524 secs]
  46. 消耗时间为:1150
  47. -Xmx10m
  48. -Xms10m
  49. -XX:-DoEscapeAnalysis
  50. -XX:+PrintGC
  51. 以上测试可以看出,栈上分配可以明显提高效率: 效率是不开启的1150/7= 160
  52. 示例2
  53. 我们通过举例来说明 开启逃逸分析 和 未开启逃逸分析时候的情况
  54. class User {
  55. private String name;
  56. private String age;
  57. private String gender;
  58. private String phone;
  59. }
  60. public class StackAllocation {
  61. public static void main(String[] args) throws InterruptedException {
  62. long start = System.currentTimeMillis();
  63. for (int i = 0; i < 100000000; i++) {
  64. alloc();
  65. }
  66. long end = System.currentTimeMillis();
  67. System.out.println("花费的时间为:" + (end - start) + " ms");
  68. // 为了方便查看堆内存中对象个数,线程sleep
  69. Thread.sleep(10000000);
  70. }
  71. private static void alloc() {
  72. // 未发生逃逸
  73. User user = new User();
  74. }
  75. }
  76. 设置JVM参数,表示未开启逃逸分析
  77. -Xmx1G -Xms1G -XX:-DoEscapeAnalysis -XX:+PrintGCDetails
  78. 花费的时间为:664 ms
  79. 然后查看内存的情况,发现有大量的User存储在堆中
  80. 开启逃逸分析
  81. -Xmx1G -Xms1G -XX:+DoEscapeAnalysis -XX:+PrintGCDetails
  82. 然后查看运行时间,我们能够发现花费的时间快速减少,同时不会发生GC操作
  83. 花费的时间为:5 ms
  84. 在看内存情况,我们发现只有很少的User对象,说明User未发生逃逸,因为它存储在栈中,随着栈的销毁而消失。

비교해 보면 알 수 있다

  • 스택 할당을 활성화하고 이스케이프되지 않은 개체를 스택 메모리에 할당하면 분명히 더 효율적으로 실행됩니다.
  • 스택에서 할당을 닫은 후 GC는 자주 가비지 수집을 수행합니다.

2. 잠금 제거

이스케이프 분석은 특정 개체가 단일 스레드에서만 액세스되고 다른 스레드로 이스케이프되지 않음을 감지할 수 있습니다. 따라서 불필요한 동기화 작업을 제거할 수 있으며 멀티스레드 프로그램의 실행 오버헤드가 줄어듭니다.

동기화 잠금은 성능을 많이 소모하므로 컴파일러는 개체가 이스케이프되지 않았다고 판단하면 개체에서 동기화 잠금을 제거합니다. JDK1.8은 기본적으로 동기화 잠금을 활성화하지만 이스케이프 분석 활성화를 기반으로 합니다.

  1. -XX:+EliminateLocks #开启同步锁消除(JVM默认状态)
  2. -XX:-EliminateLocks #关闭同步锁消除
  1. 通过示例: 明显可以看到“逃逸分析和锁消除” 对性能的提升
  2. public void testLock(){
  3. long t1 = System.currentTimeMillis();
  4. for (int i = 0; i < 100_000_000; i++) {
  5. locketMethod();
  6. }
  7. long t2 = System.currentTimeMillis();
  8. System.out.println("耗时:"+(t2-t1));
  9. }
  10. public static void locketMethod(){
  11. EscapeAnalysis escapeAnalysis = new EscapeAnalysis();
  12. synchronized(escapeAnalysis) {
  13. escapeAnalysis.obj2="abcdefg";
  14. }
  15. }
  16. 设置JVM参数,开启逃逸分析, 耗时:
  17. java -Xmx64m -Xms64m -XX:+DoEscapeAnalysis
  18. 设置JVM参数,关闭逃逸分析, 耗时:
  19. java -Xmx64m -Xms64m -XX:-DoEscapeAnalysis
  20. 设置JVM参数,关闭锁消除,再次运行
  21. java -Xmx64m -Xms15m -XX:+DoEscapeAnalysis -XX:-EliminateLocks
  22. 设置JVM参数,开启锁消除,再次运行
  23. java -Xmx64m -Xms15m -XX:+DoEscapeAnalysis -XX:+EliminateLocks

스레드 동기화 비용은 상당히 높으며 동기화로 인해 동시성 및 성능이 저하됩니다.

동기화된 블록을 동적으로 컴파일할 때 JIT 컴파일러는 이스케이프 분석을 사용하여 동기화된 블록에서 사용하는 잠금 개체가 한 스레드에서만 액세스할 수 있고 다른 스레드에서는 해제되지 않았는지 여부를 확인할 수 있습니다. 그렇지 않은 경우 JIT 컴파일러는 이 동기화된 블록을 컴파일할 때 코드의 이 부분을 비동기화합니다. 이는 동시성과 성능을 크게 향상시킬 수 있습니다. 이러한 동기화 취소 프로세스를 동기화 생략, 잠금 제거라고도 합니다.

  1. 例如下面的代码
  2. public void f() {
  3.     Object hellis = new Object();
  4.     synchronized(hellis) {
  5.         System.out.println(hellis);
  6.     }
  7. }
  8. 代码中对hellis这个对象加锁,但是hellis对象的生命周期只在f()方法中,并不会被其他线程所访问到,所以在JIT编译阶段就会被优化掉,优化成:
  9. public void f() {
  10.     Object hellis = new Object();
  11.     System.out.println(hellis);
  12. }
  13. 我们将其转换成字节码,此处发现,还是有同步锁的身影,是因为优化是在编译阶段的,在加载进内存后发生。

3. 스칼라 대체

이스케이프 분석은 객체를 기본 유형이나 기타 객체와 같은 여러 스칼라로 분할하고 이를 다른 위치에 할당할 수 있습니다. 이를 통해 메모리 조각화 및 개체 액세스 오버헤드를 줄이고 메모리 활용 효율성을 향상시킬 수 있습니다.

우선, 우리는 스칼라와 집합체를 이해해야 합니다. 기본 유형과 객체에 대한 참조는 스칼라로 이해될 수 있으며 더 이상 분해될 수 없습니다. 더 분해될 수 있는 수량은 다음과 같은 집계 수량입니다.

객체는 스칼라로 더 분해될 수 있고 해당 멤버 변수는 이산 변수로 분해될 수 있는 집계 수량입니다.

이런 방식으로 객체가 이스케이프되지 않으면 객체를 생성할 필요가 전혀 없습니다. 객체가 사용하는 멤버 스칼라만 스택이나 레지스터에 생성되므로 메모리 공간이 절약되고 애플리케이션 성능이 향상됩니다.

JDK1.8에서는 스칼라 대체도 기본적으로 활성화되어 있지만 이스케이프 분석이 활성화되어 있어야 합니다.

스칼라는 더 작은 데이터로 나눌 수 없는 데이터입니다. Java의 기본 데이터 유형은 스칼라입니다.

대조적으로, 분해될 수 있는 데이터를 집계라고 합니다. Java의 객체는 다른 집계 및 스칼라로 분해될 수 있기 때문에 집계입니다.

  1. public static void main(String args[]) {
  2.     alloc();
  3. }
  4. class Point {
  5.     private int x;
  6.     private int y;
  7. }
  8. private static void alloc() {
  9.     Point point = new Point(1,2);
  10.     System.out.println("point.x" + point.x + ";point.y" + point.y);
  11. }
  12. 以上代码,经过标量替换后,就会变成
  13. private static void alloc() {
  14.     int x = 1;
  15.     int y = 2;
  16.     System.out.println("point.x = " + x + "; point.y=" + y);
  17. }

JIT 단계에서 이스케이프 분석을 통해 객체가 외부 세계에서 접근할 수 없다는 것이 발견되면 JIT 최적화 후 객체는 그 안에 포함된 여러 멤버 변수로 분해되어 교체됩니다. 이 과정은 스칼라 대체입니다.
이스케이프 분석 결과 집계량 Point가 이스케이프되지 않은 것으로 확인되어 두 개의 스칼라로 대체되었음을 알 수 있습니다. 그렇다면 스칼라 대체의 이점은 무엇입니까? 즉, 힙 메모리 사용량을 크게 줄일 수 있습니다. 일단 객체를 생성할 필요가 없기 때문에 힙 메모리를 할당할 필요가 없습니다. 스칼라 대체는 스택 할당을 위한 좋은 기반을 제공합니다.

탈출 분석 테스트

  1. 逃逸分析测试
  2. 代码如下,大致思路就是 for 循环 1 亿次,循环体内调用外部的 allot() 方法,而 allot() 方法的作用就是简单创建一个对象,但是这个对象是内部的,所以是未逃逸的,所以理论上 JVM 是会进行优化的,我们拭目以待。并且我们会对比开启和关闭逃逸分析之后各自程序的运行时间:
  3. /**
  4. * @ClassName: EscapeAnalysisTest
  5. * @Description: http://www.jetchen.cn 逃逸分析 demo
  6. * @Author: Jet.Chen
  7. * @Date: 2020/11/23 14:26
  8. * @Version: 1.0
  9. **/
  10. public class EscapeAnalysisTest {
  11. public static void main(String[] args) {
  12. long t1 = System.currentTimeMillis();
  13. for (int i = 0; i < 100000000; i++) {
  14. allot();
  15. }
  16. long t2 = System.currentTimeMillis();
  17. System.out.println(t2-t1);
  18. }
  19. private static void allot() {
  20. Jet jet = new Jet();
  21. }
  22. static class Jet {
  23. public String name;
  24. }
  25. }
  26. 上面就是我们进行逃逸分析测试的代码, mian() 方法末尾有一个线程暂停,目的是为了观察此时 JVM 中的内存情况。
  27. Step 1:测试开启逃逸
  28. 由于环境是 jdk1.8,默认开启了逃逸分析,所以直接运行,得到结果如下,程序耗时 3 毫秒:
  29. 此时线程是处于睡眠状态的,我们观察下内存情况,发现堆内存中一共新建了 11 万个 Jet 对象。
  30. Step 2:测试关闭逃逸
  31. 我们关闭逃逸分析再来运行一次(使用 java -XX:-DoEscapeAnalysis EscapeAnalysisTest 来运行代码即可),得到结果如下,程序耗时 400 毫秒:
  32. 此时我们观察下内存情况,发现堆内存中一共新建了 3 千多万个 Jet 对象。
  33. 所以,无论是从代码的执行时间(3 毫秒 VS 400 毫秒),还是从堆内存中对象的数量(11 万个 VS 3 千万个)来分析,在上述场景下,开启逃逸分析是有正向益的。
  34. Step 3:测试标量替换
  35. 我们测试下开启和关闭 标量替换,如下图:
  36. 由上图我们可以看出,在上述极端场景下,开启和关闭标量替换对于性能的影响也是满巨大的,另外,同时也验证了标量替换功能生效的前提是逃逸分析已经开启,否则没有意义。
  37. Step 4:测试锁消除
  38. 测试锁消除,我们需要简单调整下代码,即给 allot() 方法中的内容加锁处理,如下:
  39. private static void allot() {
  40. Jet jet = new Jet();
  41. synchronized (jet) {
  42. jet.name = "jet Chen";
  43. }
  44. }
  45. 然后我们运行测试代码,测试结果也很明显,在上述场景下,开启和关闭锁消除对程序性能的影响也是巨大的。
  46. /**
  47. * 进行两种测试
  48. * 关闭逃逸分析,同时调大堆空间,避免堆内GC的发生,如果有GC信息将会被打印出来
  49. * VM运行参数:-Xmx4G -Xms4G -XX:-DoEscapeAnalysis -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError
  50. *
  51. * 开启逃逸分析 jdk8默认开启
  52. * VM运行参数:-Xmx4G -Xms4G -XX:+DoEscapeAnalysis -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError
  53. *
  54. * 执行main方法后
  55. * jps 查看进程
  56. * jmap -histo 进程ID
  57. *
  58. */
  59. @Slf4j
  60. public class EscapeTest {
  61. public static void main(String[] args) {
  62. long start = System.currentTimeMillis();
  63. for (int i = 0; i < 500000; i++) {
  64. alloc();
  65. }
  66. long end = System.currentTimeMillis();
  67. log.info("执行时间:" + (end - start) + " ms");
  68. try {
  69. Thread.sleep(Integer.MAX_VALUE);
  70. } catch (InterruptedException e1) {
  71. e1.printStackTrace();
  72. }
  73. }
  74. /**
  75. * JIT编译时会对代码进行逃逸分析
  76. * 并不是所有对象存放在堆区,有的一部分存在线程栈空间
  77. * Ponit没有逃逸
  78. */
  79. private static String alloc() {
  80. Point point = new Point();
  81. return point.toString();
  82. }
  83. /**
  84. *同步省略(锁消除) JIT编译阶段优化,JIT经过逃逸分析之后发现无线程安全问题,就会做锁消除
  85. */
  86. public void append(String str1, String str2) {
  87. StringBuffer stringBuffer = new StringBuffer();
  88. stringBuffer.append(str1).append(str2);
  89. }
  90. /**
  91. * 标量替换
  92. *
  93. */
  94. private static void test2() {
  95. Point point = new Point(1,2);
  96. System.out.println("point.x="+point.getX()+"; point.y="+point.getY());
  97. // int x=1;
  98. // int y=2;
  99. // System.out.println("point.x="+x+"; point.y="+y);
  100. }
  101. }
  102. @Data
  103. @AllArgsConstructor
  104. @NoArgsConstructor
  105. class Point{
  106. private int x;
  107. private int y;
  108. }

4. 메소드 인라이닝

이스케이프 분석을 통해 특정 메서드 호출이 현재 메서드의 범위를 벗어나지 않는지 확인할 수 있습니다. 따라서 이러한 메소드를 인라인으로 최적화하여 메소드 호출 비용을 줄이고 프로그램의 실행 효율성을 향상시킬 수 있습니다.

이러한 최적화 전략을 통해 이스케이프 분석은 JVM이 코드를 더 잘 최적화하고, 가비지 수집 오버헤드를 줄이고, 프로그램 실행 효율성과 응답성을 개선하고, 메모리 사용량을 줄이는 데 도움이 될 수 있습니다.
 

실제 적용 시나리오

이스케이프 분석에는 실제 Java 애플리케이션에서 광범위한 애플리케이션 시나리오가 있습니다. 다음은 몇 가지 일반적인 애플리케이션 시나리오입니다.

  1. 개체가 메서드 매개 변수로 전달되면 이스케이프 분석을 통해 개체가 이스케이프되는지 여부를 판단하여 해당 개체가 힙에 할당되었는지 스택에 할당되었는지 확인할 수 있습니다.
    1. 개체가 메서드 반환 값으로 사용되는 경우 이스케이프 분석을 통해 개체가 이스케이프되는지 여부를 판단하여 해당 개체가 힙에 할당되었는지 스택에 할당되었는지 확인할 수 있습니다.
    1. 객체가 스레드에 의해 공유되는 경우 이스케이프 분석은 객체가 이스케이프되는지 여부를 확인하여 동기화 작업이 필요한지 여부를 결정할 수 있습니다.
    1. 루프의 임시 개체가 생성되면 이스케이프 분석을 통해 개체가 이스케이프되는지 여부를 확인할 수 있으므로 개체를 자주 생성하고 소멸해야 하는지 여부를 결정할 수 있습니다.

탈출 분석의 단점

탈출 분석에 관한 논문은 1999년에 발표되었지만 JDK1.6까지는 구현되지 않았으며 이 기술은 아직 성숙하지 않았습니다.

근본적인 이유는 탈출 분석의 성능 소모가 소모 분석의 성능 소모보다 높을 것이라는 보장이 없기 때문입니다. 이스케이프 분석은 스칼라 대체, 스택 할당 및 잠금 제거를 수행할 수 있습니다. 그러나 탈출 분석 자체에도 일련의 복잡한 분석이 필요하며 이는 실제로 상대적으로 시간이 많이 걸리는 프로세스입니다.

극단적인 예는 탈출 분석 결과 탈출하지 않는 객체가 없다는 것을 발견한 것이다. 그러면 탈출 분석 과정이 낭비됩니다.

이 기술은 아직 성숙되지는 않았지만 JIT(Just-In-Time) 컴파일러 최적화 기술에 있어 매우 중요한 수단이기도 합니다. 이스케이프 분석을 통해 JVM이 이스케이프되지 않는 객체를 스택에 할당한다는 의견이 있다는 것을 알았습니다. 이는 이론적으로는 가능하지만 JvM 디자이너의 선택에 달려 있습니다. 제가 아는 한 Oracle Hotspot JVM은 이를 수행하지 않습니다. 이는 escape 분석 관련 문서에서 설명되었으므로 모든 객체 인스턴스가 힙에 생성되는 것이 분명합니다.

현재 많은 책들이 여전히 JDK7 이전 버전을 기반으로 하고 있으며, JDK는 큰 변화를 겪었습니다. 인턴 문자열과 정적 변수의 캐시는 한때 영구 생성에 할당되었으며 영구 생성은 메타데이터 영역으로 대체되었습니다. 그러나 인턴 문자열 캐시와 정적 변수는 메타데이터 영역으로 전송되지 않고 힙에 직접 할당되므로 이는 이전 요점의 결론과도 일치합니다. 개체 인스턴스는 힙에 할당됩니다. 위의 예는 스칼라 대체 때문에 속도가 빨라졌습니다.

탈출 분석의 이점


객체가 메서드 본문이나 스레드 내에서 이스케이프되지 않는 경우(또는 이스케이프 분석 후 이스케이프에 실패한 것으로 판단되는 경우) 다음과 같은 최적화가 이루어질 수 있습니다.

스택 할당:


일반적인 상황에서는 탈출할 수 없는 객체가 상대적으로 큰 공간을 차지합니다. 스택의 공간을 사용할 수 있으면 메서드가 종료될 때 많은 수의 객체가 파괴되어 GC 부담이 줄어듭니다.


동기 제거:


정의한 클래스의 메서드에 동기화 잠금이 있지만 런타임에 하나의 스레드만 액세스하는 경우 이스케이프 분석 후 기계어 코드는 동기화 잠금 없이 실행됩니다.


스칼라 대체:


JVM(Java Virtual Machine)의 기본 데이터 유형(int, long, 참조 유형 등의 숫자 유형)은 더 이상 분해될 수 없으며 이를 스칼라라고 부를 수 있습니다. 반대로, 데이터 조각이 계속해서 분해될 수 있는 경우 이를 집계라고 합니다. Java에서 가장 일반적인 집계는 개체입니다. 이스케이프 분석을 통해 개체가 외부에서 액세스되지 않고 개체가 분해 가능하다는 것이 입증되면 프로그램이 실제로 실행될 때 개체가 생성되지 않고 대신 이 메서드에서 대체하는 데 사용되는 여러 멤버 변수를 직접 생성할 수 있습니다. 디스어셈블된 변수는 속성을 평면화한 후 개별적으로 분석하고 최적화할 수 있으므로 참조 포인터를 통해 관계를 설정할 필요가 없으며 연속적이고 컴팩트하게 저장할 수 있으므로 다양한 저장소에 더 친숙하고 처리하는 동안 많은 데이터가 절약됩니다. 실행하면 성능 손실이 발생합니다. 동시에 스택 프레임이나 레지스터에 각각 공간을 할당할 수도 있으므로 원래 개체가 전체적으로 공간을 할당할 필요가 없습니다.

요약


Young Generation은 객체가 태어나고 성장하고 죽는 영역이다. 여기서 객체가 생성되고 사용되며 최종적으로 Garbage Collector에 의해 수집되어 수명을 종료한다.

이전 세대에 배치된 긴 수명 주기를 가진 객체는 일반적으로 생존 영역에서 복사된 Java 객체입니다. 물론, 특별한 경우도 있습니다. 일반 객체가 TLAB에 할당된다는 것을 알고 있습니다. 객체가 크면 JVM은 객체가 너무 크면 이를 Eden의 다른 위치에 직접 할당하려고 시도합니다. 새로운 세대에서 충분히 길고 연속적인 여유 공간을 찾을 수 있으면 JVM은 이를 이전 세대에 직접 할당합니다. Young Generation에서만 GC가 발생하는 경우 Young Generation 객체를 재활용하는 행위를 MinorGc라고 합니다.

Old Generation에서 GC가 발생하는 경우 이를 MajorGc 또는 FullGC라고 합니다. 일반적으로 MinorGc의 발생 빈도는 MajorGC의 발생 빈도보다 훨씬 높습니다. 즉, Old 세대의 가비지 수집 빈도는 Young 세대의 발생 빈도보다 훨씬 낮습니다.

JVM 이스케이프 분석은 정적 및 동적이라는 두 가지 분석 방법을 사용하여 객체가 메서드 범위를 벗어날 수 있는지 여부를 결정합니다. 이는 JVM이 코드를 최적화하고 Java 프로그램의 성능 및 메모리 활용 효율성을 향상시키는 데 도움이 될 수 있습니다.

이스케이프 분석을 위한 최적화 전략에는 스택 내 할당, 동기화 제거, 스칼라 대체 및 메서드 인라인이 포함됩니다. 이러한 최적화 전략은 가비지 수집의 오버헤드를 줄이고, 프로그램 실행 효율성과 응답성을 향상시키며, 메모리 사용량을 줄일 수 있습니다.

인용하다:

https://zhuanlan.zhihu.com/p/693382698

JVM-힙-이스케이프 분석-08-CSDN 블로그

JIT 메모리 이스케이프 분석_java가 스칼라 교체를 끕니다.-CSDN 블로그

JVM의 구성된 모든 매개변수 값 보기

java -XX:+PrintFlagsFinal  #输出打印所有参数jvm参数

여기에 이미지 설명을 삽입하세요.
여기에 이미지 설명을 삽입하세요.
여기에 이미지 설명을 삽입하세요.
여기에 이미지 설명을 삽입하세요.