소개
지난 이틀 동안 한 동료가 머리를 긁적이며 Java를 값으로 전달해야 할지 아니면 참조로 전달해야 할지 고민하고 있었습니다. 그는 자신의 여동생에 대해 혼란스러워서 모든 사람에게 문제를 제기하여 토론했습니다. 따라서 어떤 사람들은 자신의 이해가 옳다고 느끼더라도 가치에 의한 전달이라고 말하고 어떤 사람들은 참조에 의한 전달이라고 말합니다. 제 생각에는 이 질문에 대답하려면 이 질문을 제쳐두고 먼저 이 질문의 상위 단계인 Java 메모리 할당으로 넘어가는 것이 좋습니다. 메모리 할당에 관해서라면 많은 사람들의 마음에 다음 문장이 떠오를 것 같습니다. 참조는 스택에 배치되고, 객체는 힙에 배치되며, 스택은 힙을 가리킵니다. 흠, 이 문장은 맞는 것 같지만 계속 질문해 보겠습니다. 이 스택은 무엇입니까? 용문여관인가요? 아니요! 실제로는 Java 가상 머신 스택입니다. 자, 이 시점에서 학구적인 아이들은 다음과 같은 질문을 하지 않을 수 없습니다: Java 가상 머신 스택이 무엇입니까? 걱정하지 마시고 함께 살펴보시죠.
우리는 모든 Java 프로그램이 Java 가상 머신에서 실행된다는 사실을 알고 있습니다. 즉, 런타임 Java 가상 머신이 Java 프로그램 실행을 담당합니다. Java 프로그램이 시작되면 가상 머신 인스턴스가 생성되고 프로그램이 실행되면 가상 머신 인스턴스가 종료됩니다. 예를 들어, 5개의 Java 프로그램이 컴퓨터에서 동시에 실행되는 경우 시스템은 5개의 Java 가상 머신 인스턴스를 제공합니다. 각 Java 프로그램은 해당 Java 가상 머신 인스턴스에서 독립적으로 실행됩니다.
Java Virtual Machine에는 데몬 스레드와 비데몬 스레드라는 두 가지 유형의 스레드가 있습니다. 데몬 스레드는 일반적으로 가비지 수집을 수행하는 스레드와 같이 가상 머신 자체에서 사용됩니다. 데몬이 아닌 스레드는 일반적으로 자체 스레드를 참조합니다. 프로그램의 데몬이 아닌 스레드가 모두 종료되면 가상 머신 인스턴스가 자동으로 종료됩니다.
Java Virtual Machine은 Java 프로그램 실행을 담당하므로 먼저 Java Virtual Machine 아키텍처를 살펴보겠습니다. 아래 그림을 참조하세요.
할 수 있습니다. 여기를 참조하세요. To: 클래스 파일이 JVM에 로드되고 클래스 로더에 의해 실행됩니다. 여기서는 런타임 중 JVM에 의한 메모리 공간 분할 및 할당을 나타내는 파란색 와이어프레임에 있는 JVM의 런타임 데이터 영역에 중점을 둡니다. 이 데이터 영역은 메소드 영역(메서드 영역), 힙(힙), Java 스택(Java 스택), 프로그램 카운터 레지스터(프로그램 카운터), 네이티브 메소드 스택(로컬 메소드 스택) 등 주요 영역으로 구분됩니다. 각 지역의 기능과 특징은 아래와 같습니다.
메서드 영역(Method Area)은 각 스레드가 공유하는 메모리 영역으로, 로드된 클래스 정보, 상수, 정적 변수, 컴파일러 컴파일 코드를 저장하는 데 사용됩니다. 가상 머신에서 데이터를 기다리세요. Java Virtual Machine 사양에 따르면 메서드 영역이 메모리 할당 요구 사항을 충족할 수 없는 경우 OutOfMemoryError(OOM) 예외가 발생합니다. 방법 영역을 더 자세히 이해하기 위해 이 영역에 포함된 특정 구성 요소를 살펴보겠습니다.
클래스 버전, 필드, 메서드, 인터페이스 및 기타 설명과 클래스와 밀접하게 관련된 기타 정보 외에도 생성된 컴파일 타임을 저장하는 데 사용되는 상수 풀도 있습니다. 다양한 리터럴 및 기호 참조, 상수 풀은 클래스가 로드된 후 메서드 영역의 런타임 상수 풀에 저장됩니다. 즉, 이 클래스에서 사용하는 순서화된 상수 컬렉션은 런타임 상수 풀에 저장되며, 이는 Java 프로그램의 동적 연결에서 매우 중요한 역할을 합니다. 이 컬렉션에는 직접 상수(문자열, 정수 및 부동 소수점 등)와 기타 유형, 필드 및 메소드에 대한 기호 참조가 포함됩니다. 외부 세계에서는 인덱스를 통해 런타임 상수 풀의 데이터 항목에 액세스할 수 있으며 이는 배열에 액세스하는 것과 매우 유사합니다. 물론 런타임 상수 풀은 메소드 영역의 일부이며 메소드 영역의 메모리에 의해 제한됩니다. 런타임 상수 풀이 더 이상 메모리를 적용할 수 없으면 OutOfMemoryError(OOM) 예외가 발생합니다.
에는 다음 섹션이 포함됩니다.
유형의 정규화된 이름
유형의 직접 슈퍼클래스의 정규화된 이름
유형이 클래스인지 여부 유형 또는 인터페이스 유형
유형 액세스 수정자(public, abstract, final 등)
직접 슈퍼 인터페이스의 정규화된 이름의 정렬된 목록
필드 정보는 설명에 사용됩니다(로컬 변수 제외). 이 클래스에 선언된 모든 필드에는 다음과 같은 특정 정보가 포함됩니다. the fields
(4) 메소드 정보
메서드 정보는 클래스에 선언된 모든 메소드를 설명하는 데 사용됩니다. 여기에는 다음과 같은 특정 정보가 포함됩니다.
메서드 이름
메서드 반환 유형
메서드 수정자
연산자 스택
프레임 스택의 로컬 변수 영역의 크기
정적으로 수정된 변수를 클래스에 저장하는 부분입니다.
클래스는 클래스 로더에 의해 로드되고 JVM은 메서드 영역에서 클래스 로더에 대한 참조를 유지합니다.
로더가 클래스를 로드하는 동안 가상 머신은 클래스를 나타내는 클래스 객체를 생성하는 동시에 JVM은 클래스에 대한 참조를 유지합니다. 방법 영역에서.
프로그램 카운터 레지스터(Program Counter)는 런타임 데이터 영역(Runtime Data Area)에서 매우 작은 메모리 공간만을 차지하며 다음에 실행될 바이트코드를 저장하는 데 사용됩니다. 명령의 주소입니다.
자바 스택(Java 스택)은 가상 머신 스택(VM Stack)이라고도 하는데, 우리가 흔히 스택이라고 부르는 것입니다. Java 메소드 실행의 메모리 모델을 설명하는 데 사용됩니다. 각 메소드가 실행되면 스택 프레임(Stack Frame)이 동시에 생성되어 지역 변수 테이블, 작업 스택, 동적 링크, 메소드 종료, 등. 각 메서드가 호출되어 실행이 완료될 때까지의 과정은 가상 머신 스택의 스택 프레임이 스택에서 푸시되어 스택 밖으로 튀어나오는 과정에 해당합니다. Java 스택의 수명 주기는 스레드의 수명 주기와 동일합니다. 스레드가 실행을 완료하면 스택도 지워집니다.
네이티브 메소드 스택(Native Method Stack)은 자바 스택(Java Stack)과 매우 유사합니다. 네이티브 메소드(C/C++) 호출에 관련된 로컬 변수를 저장하는 데 사용됩니다. 테이블, 작업 스택 및 기타 정보.
Heap(Heap)은 가상 머신이 시작될 때 생성되며 거의 모든 객체 인스턴스가 여기에 메모리를 할당하는 데 사용됩니다. 따라서 힙은 자바 가상 머신이 관리하는 메모리 중 가장 큰 부분이자 가비지 컬렉터가 관리하는 핵심 영역이기도 하다.
JVM 런타임 데이터 영역을 요약하면 다음과 같습니다.
메서드 영역(메서드 영역)과 힙(힙)은 모든 스레드가 공유하는 메모리 영역입니다.
Java 스택(Java 스택), 프로그램 카운터 레지스터(프로그램 카운터) 및 네이티브 메서드 스택(네이티브 메서드 스택)은 각 스레드에 대한 전용 메모리 영역입니다.
객체를 생성하면 객체의 참조가 Java 스택(Java 스택)에 저장되고 실제 객체 인스턴스가 힙(힙)에 저장됩니다. 이것은 모든 사람들이 흔히 말하는 것입니다: 스택은 힙을 가리킵니다.
방금 언급한 JVM 런타임 데이터 영역에 관련된 메모리 외에도 직접 메모리(Direct Memory)에도 주목할 필요가 있습니다. 참고: 직접 메모리(Direct Memory)는 가상 머신 런타임 데이터 영역의 일부도 아니고 JVM(Java Virtual Machine) 사양에 정의된 메모리 영역도 아니지만 이 메모리 부분도 자주 사용되며 OutOfMemoryError(OOM)가 발생할 수도 있습니다. ) 예외가 발생합니다. 예를 들어 NIO를 사용하는 경우 네이티브 함수 라이브러리를 사용하여 오프 힙 메모리를 직접 할당한 다음 이 메모리에 대한 참조로 Java 힙에 저장된 DirectByteBuffer 개체를 통해 작동할 수 있습니다. 유사한 작업을 수행하면 Java 힙과 네이티브 힙 간에 데이터가 앞뒤로 복사되는 것을 방지하여 성능을 향상시킬 수 있습니다.
Java 메서드를 호출하여 매개변수를 전달할 때 값으로 전달되나요, 아니면 참조로 전달되나요? 많은 논쟁 속에서도 코드를 살펴보겠습니다. 결국 코드는 거짓말을 하지 않습니다. 먼저 매우 간단한 예를 살펴보겠습니다. 두 개의 int 유형 데이터를 교환하는 코드는 다음과 같습니다.
package cn.com;/** */public class TestMemory { public static void main(String[] args) { TestMemory testMemory=new TestMemory(); int number1=9527; int number2=1314; System.out.println("main方法中,数据交换前:number1="+number1+" , number2="+number2); testMemory.swapData(number1, number2); System.out.println("main方法中,数据交换后:number1="+number1+" , number2="+number2); } private void swapData(int a,int b) { System.out.println("swapData方法中,数据交换前:a="+a+" , b="+b); int temp=a; a=b; b=temp; System.out.println("swapData方法中,数据交换后:a="+a+" , b="+b); } }
메인 메소드에서 선언한 두 개의 변수 number1=9527, number2=1314를 매개변수로 사용합니다. swapData(int a, int b) 메소드와 데이터가 메소드 내에서 교환됩니다. 코드 자체는 길게 설명할 필요가 없지만, 출력 결과를 생각해보세요. 생각하신 후 다음 인쇄된 정보를 참조하십시오.
주 메서드에서 데이터 교환 전: number1=9527, number2=1314
swapData 메소드에서 데이터 교환 전: a=9527, b=1314
swapData 메소드에서 데이터 교환 후: a=1314, b=9527
기본 메소드에서 데이터 교환 후: number1=9527, number2=1314
嗯哼,这和你想的一样么?为什么会是这样呢?还记得刚才讨论Java Stacks(Java 栈)时说的么:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。结合示例的代码:main( )方法在一个栈帧中,swapData( )在另外一个栈帧中;两者彼此独立互不干扰。在main( )中调用swapData( )传入参数时它的本质是:将实际参数值的副本(复制品)传入其它方法内而参数本身不会受到任何影响。也就是说,这number1和number2这两个变量仍然存在于main( )方法所对应的栈帧中,但number1和number2这两个变量的副本(即int a和int b)存在于swapData( )方法所对应的栈帧中。故,在swapData( )中交换数据,对于main( )是没有任何影响的。这就是Java中调用方法时的传值机制——值传递。
嗯哼,刚才这个例子是关于基本类型的参数传递。Java对于引用类型的参数传递一样采用了值传递的方式。我们在刚才的示例中稍加改造。首先,我们创建一个类,该类有两个变量number1和number2,请看代码:
package cn.com;/** */public class DataObject { private int number1; private int number2; public int getNumber1() { return number1; } public void setNumber1(int number1) { this.number1 = number1; } public int getNumber2() { return number2; } public void setNumber2(int number2) { this.number2 = number2; } }
好了,现在我们来测试交换DataObject类对象中的两个数据:
package cn.com;/** */public class TestMemory { public static void main(String[] args) { TestMemory testMemory=new TestMemory(); DataObject dataObject=new DataObject(); dataObject.setNumber1(9527); dataObject.setNumber2(1314); System.out.println("main方法中,数据交换前:number1="+dataObject.getNumber1()+" , number2="+dataObject.getNumber2()); testMemory.swapData(dataObject); System.out.println("main方法中,数据交换后:number1="+dataObject.getNumber1()+" , number2="+dataObject.getNumber2()); } private void swapData(DataObject dataObject) { System.out.println("swapData方法中,数据交换前:number1="+dataObject.getNumber1()+" , number2="+dataObject.getNumber2()); int temp=dataObject.getNumber1(); dataObject.setNumber1(dataObject.getNumber2()); dataObject.setNumber2(temp); System.out.println("swapData方法中,数据交换后:number1="+dataObject.getNumber1()+" , number2="+dataObject.getNumber2()); } }
简单地描述一下代码:在main( )中定义一个DataObject类的对象并为其number1和number2赋值;然后调用swapData(DataObject dataObject)方法,在该方法中交换数据。请思考输出的结果是什么?在您考虑之后,请参见如下打印信息:
main方法中,数据交换前:number1=9527 , number2=1314
swapData方法中,数据交换前:number1=9527 , number2=1314
swapData方法中,数据交换后:number1=1314 , number2=9527
main方法中,数据交换后:number1=1314 , number2=9527
嗯哼,为什么是这样呢?我们通过DataObject dataObject=new DataObject();创建一个对象;该对象的引用dataObject存放于栈中,而该对象的真正的实例存放于堆中。在main( )中调用swapData( )方法传入dataObject作为参数时仍然传递的是值,只不过稍微特殊点的是:该值指向了堆中的实例对象。好了,再结合栈帧来梳理一遍:main( )方法存在于与之对应的栈帧中,在该栈帧中有一个变量dataObject它指向了堆内存中的真正的实例对象。swapData( )收到main( )传递过来的变量dataObject时将其存放于其本身对应的栈帧中,但是该变量依然指向堆内存中的真正的实例对象。也就是说:main( )方法中的dataObject和swapData( )方法中的dataObject指向了堆中的同一个实例对象!所以,在swapData( )中交换了数据之后,在main( )会体现交换后的变化。在此,我们可以进一步的验证:在该swapData( )方法的最后一行添加一句代码dataObject=null ;我们发现打印信息并没有任何变化。因为这句代码仅仅使得swapData( )所对应的栈帧中的dataObject不再指向堆内存中的实例对象但不会影响main( )所对应的栈帧中的dataObject依然指向堆内存中的实例对象。
通过这两个示例,我们进一步验证了:Java中调用方法时的传递机制——值传递。当然,有的人说:基础类型传值,对象类型传引用。其实,这也没有什么错,只不过是表述方式不同罢了;只要明白其中的道理就行。如果,有些童鞋非纠缠着个别字眼不放,那我只好说:PHP是世界上最好的语言。
위 내용은 Java 메모리 할당 탐색의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!