假設在我的 main 方法中我有代碼行LinkedList<E> myLinkedList = new LinkedList<>(),所以現在我有一個名為myLinkedList1物件的參考/指標變數(并且建構式存盤在另一個.java檔案中它自己的 LinkedList 類中,而不是.javamain 方法所在的檔案) . 現在我創建了另一個名為myLinkedList2. 我用的是方法addLast(E newElement)(這個方法當然是存盤在LinkedList類里的),但是我只用在上面myLinkedList1(原來是這樣myLinkedList.addLast(E newElement)),JVM怎么知道只在上面用這個方法myLinkedList1不myLinkedList2,是堆里存放的物件方法嗎用它?我以為它們被放在了堆疊上。
uj5u.com熱心網友回復:
記憶體中的物件包含以下資訊:
- 指向該物件是其實體的實際類的指標。
- 所有領域都有足夠的空間。鑒于 java 是基于參考的,每個欄位最多為 64 位 - 它們都是固定大小的,所以這并不復雜。
- 與您的問題無關的其他內容。
至關重要的是,它們根本不包含任何方法。
我以為它們被放在了堆疊上。
方法?在堆疊上?這是沒有意義的。你一定是被誤導了。方法不在堆疊上。它們也不是真正的堆。它們在類定義中作為單例存在,對于任何類只加載一次。在現代 JVM 上,從技術上講,它們確實存在于堆中,但至關重要的是,它們與專用于存盤物件的堆空間相去甚遠。它們位于專門用于存盤類的定義(位元組碼,或者更確切地說是轉換后的、熱點化的等位元組碼)的堆空間中。無論您創建多少個 LinkedList 實體,都只有一個 LinkedList 類,因此 100 萬個 LinkedList 實體仍然意味著您只將addLast方法的實際正文內容存盤在記憶體中一次。是的,addLast是一個實體方法。記憶體中仍然只有一個副本(與實體欄位不同;每個實體都有自己的每個非靜態欄位的副本)。
對于整個 JVM,任何給定的類最多加載一次(為什么要多次加載它?這些東西是不變的,這會浪費記憶體)。一個類包含所有方法(實體和靜態)。
實際上,就方法而言,就 JVM 而言,靜態方法和非靜態方法完全沒有區別。實體方法只是將其“接收者”作為第一個引數 - 例如,String'toLowerCase()方法是一個采用 1 個引數的方法,型別為String。兩者之間幾乎沒有區別:
public String toLowerCase() {
return this.doTheThing();
}
和
public static String toLowerCase(String in) {
return in.doTheThing();
}
因此,當您在 java 中撰寫時foo.bar();,您會得到 2 個不相關的步驟:首先,javac將其轉換為存盤在類檔案中的位元組碼。然后,5 天后,在一臺完全不同的機器上,有人運行你的類檔案,然后 JVM 看到位元組碼并運行它。
javac首先嘗試bar()通過檢查型別foo是什么來確定你在那里呼叫的精確值。一旦javac弄清楚了,你就得到了位元組碼:
INVOKEVIRTUAL com.pkg.FullTypeOfWhateverFooIsThere :: bar :: ()V
That third bit is the 'signature' (the parameter types and return types, which in java are an inherent part of a method's identity). That's it - the arguments to all things are on the stack. This particular method has one argument (the receiver - something that is an instance of com.pkg.FullTypeOfWhateverFooIsThere), which will have to be on the stack. javac ensures it is true. The JVM checks the bytecode and if it can't confirm it is true, it will reject the class file with a VerifierError (this cannot happen unless you manually mess with the bytecode, or have a corrupt disk).
Then, the JVM 'follows the pointer' and checks what that first argument's actual type is, and will then find the loaded class that represents that precise type. It then checks that class (and not com.pkg.FullTypeOfWhateverFooIsThere - at least, not if the actual class is a subclass) for a method named foo with signature ()V. If it finds it, it runs it. If it doesn't, it goes up one class in the hierarchy and keeps looking for foo::()V until it finds it (which it will, otherwise your code wouldn't have compiled in the first place).
When the code for addLast runs, there are 2 things on the stack as that method begins execution:
- An instance of
LinkedListor some subclass thereof. - A new element of type Object. (at the JVM level, generics are erased).
The method can do its work just fine with that; LinkedLists have fields that store this data, the code of addLast will interact with these fields in order to do what its javadoc says it should do. Specifically, a LinkedList has a 'head' field that points at a node that contains a reference to the object (that'll be the first object in the list) and a pointer to another node. The addLast code keeps looping, grabbing the 'next pointer', until the next pointer is null. At that point it makes a new Node object, sets its 'value' to that second thing on the stack, and then updates the 'next' pointer of the last visited node to point at this newly created one, and then it is done.
Hence:
All that the JVM needs to 'find' the
addLastcode, is to know which method was intended (which is in the bytecode), and a pointer to the singleton 'loaded class' in memory for the actual type that is top-of-stack (well, below the parameters) when the INVOKEVIRTUAL command is called, which is easy, as all objects have a pointer to it, so that's just a matter of looking it up. Thus, the JVM can execute addLast.All that the
addLastcode needs to know which list to operate on, is.. the list. The receiver is passed as first parameter:foo.addLast(elem)ends up being called with the stack having firstfooand thenelemon it. This'd be no different from a static method having signature (addLast(LinkedList<E> list, E elem)) - any invocations to such a method would have 2 things on stack as well when they begin execution (static methods don't have a receiver, they just have their params on stack).
uj5u.com熱心網友回復:
您可以將呼叫方法的物件(即 之前的事物.)視為函式的額外引數。所以,從概念上講,你可以認為myLinkedList1.addLast(elt)有點像
LinkedList.addLast(myLinkedList1, elt)
所以“呼叫者”是傳遞給方法的附加資訊。一些語言明確表示這一點。例如,在 Lua 中,完全foo:bar(1)等價于,而在 Python中大致等價于. 但是在 Java 中,這一切都發生在后臺,并且有點復雜,但從概念上講,這是相同的想法。foo.bar(foo, 1)foo.bar(1)Foo.bar(foo, 1)
轉載請註明出處,本文鏈接:https://www.uj5u.com/gongcheng/450467.html
上一篇:如何從不同的類呼叫ActionPerformed方法
下一篇:更改子類的條件類實體
