Introduction
In the past two days, a colleague has been scratching his head and wondering whether Java is passed by value or by reference. Confused about his sister, he raised the issue to everyone for discussion. Therefore, some people say passing by value, and some people say passing by reference; no matter which party feels that their understanding is correct. I think: To answer this question, we might as well put this question aside and go upstream of this question first - Java memory allocation. When it comes to memory allocation, I think a sentence will come to many people's minds: references are placed on the stack, objects are placed on the heap, and the stack points to the heap. Hmm, this sentence sounds correct; but let's continue to ask: What is this stack? Is it Longmen Inn? No! It is actually a Java virtual machine stack. Well, at this point, the studious children can't help but ask: What is the Java virtual machine stack? Don't worry, let's take a look together.
We know that: every Java program runs on a Java virtual machine; that is to say: a runtime Java The virtual machine is responsible for running a Java program. When a Java program is started, a virtual machine instance is born; when the program is executed, the virtual machine instance dies. For example: if five Java programs are run simultaneously on a computer, the system will provide five Java virtual machine instances; each Java program runs independently in its own corresponding Java virtual machine instance.
There are two types of threads in the Java virtual machine: daemon threads and non-daemon threads. Daemon threads are typically used by the virtual machine itself, such as threads that perform garbage collection. Non-daemon threads usually refer to our own threads. When all non-daemon threads in the program terminate, the virtual machine instance will automatically exit.
Since the Java virtual machine is responsible for executing Java programs, let’s first take a look at the Java virtual machine architecture, please see Below:
You can see here: the class file is loaded into the JVM by the class loader and run. Here, we focus on the Runtime Data Areas of the JVM in the blue wireframe, which represents the division and allocation of memory space by the JVM during runtime. The data area is divided into the following main areas: Method Area (method area), Heap (heap), Java Stacks (Java stack), Program Counter Register (program counter), Native Method Stack (local method stack), now The main functions and characteristics of each region are introduced in detail below.
Method Area is a memory area shared by each thread. It is used to store class information that has been loaded by the virtual machine. , constants, static variables, compiler-compiled code and other data. According to the Java Virtual Machine Specification, when the method area cannot meet the memory allocation requirements, an OutOfMemoryError (OOM) exception will be thrown. To further understand the Method Area, let's look at the specific components included in this area.
In addition to the class version, fields, methods, interfaces and other descriptions, the Class file contains information closely related to the class. , there is also a constant pool used to store various literals and symbol references generated during compilation; this constant pool will be stored in the runtime constant pool in the method area after the class is loaded. In other words: an ordered collection of constants used by this class is stored in the runtime constant pool, which plays a very important role in the dynamic connection of Java programs. Included in this collection are direct constants (string, integer, floating point, etc.) and symbolic references to other types, fields, and methods. The outside world can access data items in the runtime constant pool through indexes, which is very similar to accessing an array. Of course, the runtime constant pool is part of the method area, and it will also be limited by the memory of the method area. When the runtime constant pool can no longer apply for memory, an OutOfMemoryError (OOM) exception will be thrown.
includes in this section:
The fully qualified name of the type
The fully qualified name of the direct superclass of the type
Is the type a class type or an interface type
Access modifiers (public, abstract, final, etc.)
Ordered list of fully qualified names of direct superinterfaces
Field information is used to describe all fields declared in the class (except local variables). It contains the following specific information:
The size of the local variable area in the frame stack
This part Used to store statically modified variables in the class.
The class is loaded by the class loader, and the JVM will retain a reference to the class loader in the method area.
When a class is loaded by the loader, the virtual machine creates a Class object representing the class. At the same time The JVM will retain a reference to the Class in the method area.
Program Counter Register only occupies a very small memory space in the Runtime Data Areas. It is used to store the address of the next bytecode instruction to be executed.
Java Stacks(Java stack) is also called the virtual machine stack (VM Stack), which is what we usually call the stack. It is used to describe the memory model of Java method execution: when each method is executed, a stack frame (Stack Frame) is created at the same time to store information such as local variable tables, operation stacks, dynamic links, method exits, etc. The process from each method being called until execution is completed corresponds to the process of a stack frame being pushed from the stack to being popped out of the stack in the virtual machine stack. The life cycle of Java Stacks is the same as that of threads; when a thread completes execution, the stack is also cleared.
Native Method Stack(local method stack) is very similar to Java Stacks(Java stack). It is used to store the call local The local variable table, operation stack and other information involved in the method (C/C++).
Heap (Heap) is created when the virtual machine starts and is used to store object instances. Almost all object instances allocate memory here. Therefore, the Heap is the largest piece of memory managed by the Java virtual machine and is also the key area managed by the garbage collector.
Here is a summary of the JVM runtime data area:
Method Area (method area) and Heap is a memory area shared by all threads.
Java Stacks (Java stack), Program Counter Register (program counter) and Native Method Stack (local method stack) are private memory areas for each thread.
Create an object, the reference of the object is stored in Java Stacks (Java stack), and the real object instance is stored in Heap (heap). This is what everyone often says: the stack points to the heap.
In addition to the memory involved in the JVM runtime data area just mentioned, we also need to pay attention to direct memory (Direct Memory). Please note: Direct memory (Direct Memory) is not part of the virtual machine runtime data area, nor is it a memory area defined in the Java virtual machine specification, but this part of memory is also frequently used and may also cause OutOfMemoryError (OOM) An exception occurs. For example, when using NIO, it can use the Native function library to directly allocate off-heap memory, and then operate through the DirectByteBuffer object stored in the Java heap as a reference to this memory. Similar operations can avoid copying data back and forth between the Java heap and Native heap, thus improving performance.
When calling a Java method to pass parameters, is it passed by value or by value? What about quotes? In the face of many debates, let's take a look at the code. After all, the code doesn't lie. Let's first look at a very simple example: exchanging two int type data, the code is as follows:
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); } }
The two variables number1=9527, number2=1314 we declared in the main method; then these two variables The number is passed as a parameter to the method swapData(int a, int b), and the data is exchanged within this method. As for the code itself, there is no need to explain too much; however, please think about the output result? After you think about it, please see the following printed information:
In the main method, before data exchange: number1=9527, number2=1314
In the swapData method, before data exchange: a=9527, b=1314
In the swapData method, after data exchange: a=1314, b=9527
In the main method, after data exchange: 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是世界上最好的语言。
The above is the detailed content of Exploring Java memory allocation. For more information, please follow other related articles on the PHP Chinese website!