java零基礎入門-面向對象篇(七)? 各種變量在JVM中的位置和運行方式
本章對前面學的知識進行一個總結,為下面要介紹的封裝繼承和多態打好基礎。
學習就要不斷的復習和總結,才能讓學過的知識不斷的得到沉淀,變成自己的知識,切勿心浮氣躁,囫圇吞棗。
在我的編程生涯中,遇到過很多問題和BUG,最讓我難忘的就是那種既不報錯,又沒有異常的BUG,這種BUG解起來讓人痛苦不堪,比如我當年就碰到過一個由static 靜態變量引發的BUG,最后只能用輸出語句排查,這就是典型的知識點掌握的不牢引發的問題。當然那段代碼不是我寫的。
所以在我們使用變量的時候,一定要掌握一些原則,就是潛規則,不能隨便看心情來定義變量。如果能夠知道變量的內在原理,那對我們寫代碼會有更大的幫助。
變量的分類
根據變量的聲明位置一般分為兩種
在類里面方法外面聲明的變量,是成員變量。成員變量又分為類變量和實例變量。類變量就是static修飾的變量,它屬于類。實例變量就是沒有static修飾的變量,它屬于對象。
再就是在方法內部聲明的變量,是局部變量。局部變量包括方法的參數,方法內部定義的變量。
1.普通成員變量,屬于對象
2.靜態成員變量,屬于類
3.局部變量,方法的參數是局部變量
4.方法內部定義的變量是局部變量
前面說堆棧的時候,說變量在棧里面,其實那僅僅是指局部變量。普通成員變量在堆里面,靜態成員變量在方法區。是不是又有點懵,就一個變量整這么多地方干啥,東放一點西放一點,放一起不就完了。其實分開存放是有原因的,因為他們的生命周期不同,不同的變量有著不同的生命周期,導致他們必須存放在不同的地方。
為什么生命周期不同要放不同的地方?你想想你家有冰箱吧,你試試不把冰淇淋放冰箱,估計你還沒吃,它就變成液體了。因為冰淇淋的生命周期不同,它不在冰箱活不了多久,要根據它的特點指定存放的地方,不然你還沒開始調用(吃),他就被銷毀了(化了)。
棧幀
首先來看局部變量,局部變量是生命周期最短的,為什么呢?因為局部變量一般定義在方法里面,他隨著方法的調用創建,隨著方法的完畢銷毀。
我們前面說的棧是一種數據結構,它是一種設計概念,而我們虛擬機里面的虛擬機棧是用這個設計概念實現的程序,請不要混淆兩者的概念。
現在我們可以完善一下前面講的那個棧的結構了。在JVM中,我們每次入棧其實不是入棧的一個變量,而是一個棧幀,在我們知識體系不完善的時候用入棧變量來解釋可能會好理解一些,但是現在我們儲備了足夠的知識,我們就可以理解的更準確一點。
棧幀是什么?棧幀是虛擬機進行方法調用和方法執行的數據結構。就是說,我們每一個方法,都對應了一個棧幀。
棧幀主要由以下幾個部分組成,局部變量表,操作數棧,動態連接,方法返回地址。這里我們只關注局部變量表,其他幾個部分,知道有這個東西就行了。
對我們來說,棧幀里面最需要了解的就是局部變量表。我們方法中定義的局部變量,就是參數和方法內定義的變量都存在這里(第一張圖里面,3,4)。下面我們來看看調用方法的時候,棧幀是怎么進出的。
1.當方法執行到 studyFrameWork 這個方法的時候,棧幀入棧。方法的局部變量 frameWork 和 spring 在棧幀的局部變量表中,一起入棧。
2.當studyFrameWork這個方法執行完畢后,方法對應的棧幀出棧。
3.當程序繼續運行到下一個方法 studyWebFrameWork 的時候,這個方法對應的棧幀入棧,并且這個方法的局部變量 webFrameWork 和 vue 保存在局部變量表,一同入棧。
以上這個類是將兩個方法分開執行的情況,我們再看看下面這種情況
1.調用方法 studyAllFrameWork 的時候,局部變量 frameWork 和 spring 在局部變量表中入棧
2.這個時候,studyAllFrameWork 這個方法沒有執行完畢,在這個方法內部又再次調用了 studyWebFrameWork 這個方法,局部變量vue,webFrameWork 在局部變量表中入棧。這個時候棧中有兩個棧幀,先入棧的在棧底,后入棧的在棧頂。
3.后入棧的方法執行完畢,返回值返回給先入棧的方法,這個時候后入棧的方法出棧,程序繼續執行。
4.最后當第一個入棧的方法也執行完畢后,第一個方法對應的棧幀也出棧了。
看到這里,很多同學心里的問題應該有答案了。
為什么只有棧頂可以操作?我直接用棧底的數據不行嗎?
因為當我們調用方法的時候,方法中的數據按照順序入棧,如果沒有運行完畢或者出現異常,這個方法對應的棧幀是不會出棧的,如果往下執行,又調用了另外一個方法,那么另外一個方法對應的棧幀會繼續入棧,在棧頂,然后程序開始執行后入棧的這個棧幀對應的方法,所以只有棧頂的這個棧幀對應的方法才可以操作,當他運行完畢出棧以后,會繼續運行第一個沒有運行完畢的方法,這時候,第一個方法對應的棧幀就是棧頂(因為只有一個棧幀了)。所以只有棧頂可以操作。
當然,如果我們第二個方法中又調用了第三個方法,就會有第三個方法對應的棧幀入棧。然后依次運行,出棧。
入棧的時候,局部變量開始生效,出棧的時候,局部變量就銷毀掉了,所以局部變量的生命周期只在方法內部有效,出了方法就不能使用了。現在大家應該對這個有更深刻的理解了吧。
成員變量的位置
成員變量有兩種,普通成員變量屬于對象,因為當我們使用 new 創建一個對象的時候,這個對象會在堆里面開辟一個空間,而成員變量會隨著這個對象的創建,也在堆里面。而當程序里面沒有任何變量指向這個對象的時候,這個對象就會被清潔阿姨回收掉,這個時候成員變量也會一起被回收掉。
所以普通成員變量的生命周期和對象一樣,隨著對象的創建而創建,隨著對象的回收而回收。
再看另外一種,靜態成員變量。靜態成員變量在方法區,它屬于類,所以它的生命周期跟類一樣。也就是說當對象生老病死以后,這個靜態成員變量依然活的好好的。
本文開頭說過一個靜態變量引發的BUG就是,某位不知名程序員為了滿足一個 “全局” 的需求,結果使用了靜態變量,然后他每次 new出來的對象都會對這個全局變量進行修改, 由于方法區是線程共享的,結果出現了意料之外的結果。所以理解每種變量所在區域和該區域的特點很有必要。
對于各種變量,我們知道它們的生命周期和作用范圍,對我們后面的學習很有必要。比如我們要控制變量的作用范圍,可以使用局部變量的時候就使用局部變量,過多的使用成員變量會增加堆內存的開銷。再就是我們講到封裝等概念的時候,也需要控制變量的作用范圍,使我們的程序更加符合程序設計的規范。
最后,本章解釋的jvm棧幀結構其實也不是很完全,但是作為初學者,學這些已經遠遠超出了我們需要掌握的知識,能理解最好,實在不能理解也沒有關系。