Java內存區域java
線程私有數據區域:虛擬機棧,本地方法棧,程序計數器算法
線程共享數據區域:方法區,堆數組
程序計數器:當前線程所執行的字節碼的行號指示器,JVM經過這個字節碼解釋器改變計數器的值,以選擇下一條須要執行的字節指令碼。分支,循環,跳轉,異常處理,線程恢復等操做都依賴順序計數器來完成(JVM執行指令的邏輯控制器)。網絡
虛擬機棧:虛擬機棧是線程獨立的,也就是說每一個線程有本身私有的虛擬機棧,它的生命週期是與線程同步。虛擬機棧是Java方法執行的內存模型, 線程的每一個方法在執行的同時都會建立一個棧幀用於存儲局部變量表,操做數棧,動態連接,方法出口等信息。每個方法的調用到執行結束,就對應着一個棧幀入棧出棧的過程。棧幀的入棧出棧規則爲先進後出,後進先出。數據結構
本地方法棧:與虛擬機棧相似,本地方法棧爲虛擬機使用到的Native方法服務。HotSpot虛擬機把本地方法棧與虛擬機棧合二爲一。多線程
方法區:方法區爲線程共享區域,它用於存儲已被虛擬機加載的類信息,常量,靜態變量,即編譯器編譯後的代碼數據。Java虛擬機規範把方法區描述爲堆的一部分,可是他有個名字叫作Non-Heap(非堆)。HotSpHot虛擬機也把他叫作永久代-Permanent Generation。由於HotSpot虛擬機把GC擴展到了方法區,或者說使用永久代實現方法區,這樣HotSpot的垃圾收集器就能夠像管理Java堆內存同樣管理這部份內存。模塊化
運行常量池:爲方法區的一部分,class文件除了有類的版本,字段,方法,接口等描述信息外,還有一項信息是常量池,用於存放編譯期生成的各類字面量和符號引用,這部分在類被加載後進入方法區的運行時常量池中存放。工具
堆:Java堆是線程共享數據區,用於存放對象實例,幾乎全部的對象都是在堆上分配。此部分是虛擬機管理的最大一塊內存,在虛擬機啓動時被建立。垃圾收集器主要是管理堆這部分區域。ui
如下摘自:http://blog.csdn.net/xyw591238/article/details/51888273this
(1)內存分配的策略
按照編譯原理的觀點,程序運行時的內存分配有三種策略,分別是靜態的,棧式的,和堆式的.
靜態存儲分配是指在編譯時就能肯定每一個數據目標在運行時刻的存儲空間需求,於是在編 譯時就能夠給他們分配固定的內存空間.這種分配策略要求程序代碼中不容許有可變數據結構(好比可變數組)的存在,也不容許有嵌套或者遞歸的結構出現,由於 它們都會致使編譯程序沒法計算準確的存儲空間需求.(方法區)
棧式存儲分配也可稱爲動態存儲分配,是由一個相似於堆棧的運行棧來實現的.和靜態存 儲分配相反,在棧式存儲方案中,程序對數據區的需求在編譯時是徹底未知的,只有到運行的時候纔可以知道,可是規定在運行中進入一個程序模塊時,必須知道該 程序模塊所需的數據區大小纔可以爲其分配內存.和咱們在數據結構所熟知的棧同樣,棧式存儲分配按照先進後出的原則進行分配。
靜態存儲分配要求在編譯時能知道全部變量的存儲要求,棧式存儲分配要求在過程的入口 處必須知道全部的存儲要求,而堆式存儲分配則專門負責在編譯時或運行時模塊入口處都沒法肯定存儲要求的數據結構的內存分配,好比可變長度串和對象實例.堆 由大片的可利用塊或空閒塊組成,堆中的內存能夠按照任意順序分配和釋放.
(2)JVM中的堆和棧
JVM是基於堆棧的虛擬機.JVM爲每一個新建立的線程都分配一個堆棧.也就是說,對於一個Java程序來講,它的運行就是經過對堆棧的操做來完成的。堆棧以幀爲單位保存線程的狀態。JVM對堆棧只進行兩種操做:以幀爲單位的壓棧和出棧操做。
咱們知道,某個線程正在執行的方法稱爲此線程的當前方法.咱們可能不知道,當前方法使用的幀稱爲當前幀。當線程激活一個Java方法,JVM就會在線程的 Java堆棧裏新壓入一個幀。這個幀天然成爲了當前幀.在此方法執行期間,這個幀將用來保存參數,局部變量,中間計算過程和其餘數據.這個幀在這裏和編譯 原理中的活動紀錄的概念是差很少的.
從Java的這種分配機制來看,堆棧又能夠這樣理解:堆棧(Stack)是操做系統在創建某個進程時或者線程(在支持多線程的操做系統中是線程)爲這個線程創建的存儲區域,該區域具備先進後出的特性。
每個Java應用都惟一對應一個JVM實例,每個實例惟一對應一個堆。應用程序在運行中所建立的全部類實例或數組都放在這個堆中,並由應用全部的線程 共享.跟C/C++不一樣,Java中分配堆內存是自動初始化的。Java中全部對象的存儲空間都是在堆中分配的,可是這個對象的引用倒是在堆棧中分配,也 就是說在創建一個對象時從兩個地方都分配內存,在堆中分配的內存實際創建這個對象,而在堆棧中分配的內存只是一個指向這個堆對象的指針(引用)而已。
tatic、final修飾符、內部類和Java內存分配
static修飾符
static修飾符可以與屬性、方法和內部類一塊兒使用,表示靜態的。類中的靜態變量和靜態方法可以與類名一塊兒使用,不須要建立一個類的對象來訪問該類的靜態成員,因此,static修飾的變量又稱做「類變量」。
static屬性的內存分配
一個類中,一個static變量只會有一個內存空間,雖然有多個類實例,但這些類實例中的這個static變量會共享同一個內存空間。
static的變量是在類裝載的時候就會被初始化,即,只要類被裝載,無論是否使用了static變量,都會被初始化。
static的基本規則
·一個類的靜態方法只能訪問靜態屬性
·一個類的靜態方法不能直接調用非靜態方法
·如訪問控制權限容許,static屬性和方法可使用類名加「.」的方式調用,也可使用實例加「.」的方式調用
·靜態方法中不存在當前對象,於是不能使用this,也不能使用super
·靜態方法不能被非靜態方法覆蓋
·構造方法不容許聲明爲static的
注,非靜態變量只限於實例,並只能經過實例引用被訪問。
靜態初始器——靜態塊
靜態初始器是一個存在與類中方法外面的靜態塊,僅僅在類裝載的時候執行一次,一般用來初始化靜態的類屬性。
final修飾符
在Java聲明類、屬性和方法時,可使用關鍵字final來修飾,final所標記的成分具備終態的特徵,表示最終的意思。
final的具體規則
·final標記的類不能被繼承
·final標記的方法不能被子類重寫
·final標記的變量(成員變量或局部變量)即成爲常量,只能賦值一次
·final標記的成員變量必須在聲明的同時賦值,若是在聲明的時候沒有賦值,那麼只有一次賦值的機會,並且只能在構造方法中顯式賦值,而後才能使用
·final標記的局部變量能夠只聲明不賦值,而後再進行一次性的賦值
·final通常用於標記那些通用性的功能、實現方式或取值不能隨意被改變的成分,以免被誤用
若是將引用類型(即,任何類的類型)的變量標記爲final,那麼,該變量不能指向任何其它對象,但能夠改變對象的內容,由於只有引用自己是final的。
內部類
在一個類(或方法、語句塊)的內部定義另外一個類,後者稱爲內部類,有時也稱爲嵌套類。
內部類的特色
·內部類能夠體現邏輯上的從屬關係,同時對於其它類能夠控制內部類對外不可見等
·外部類的成員變量做用域是整個外部類,包括內部類,但外部類不能訪問內部類的private成員
·邏輯上相關的類能夠在一塊兒,能夠有效地實現信息隱藏
·內部類能夠直接訪問外部類的成員,能夠用此實現多繼承
·編譯後,內部類也被編譯爲單獨的類,名稱爲outclass$inclass的形式
內部類能夠分爲四種
·類級:成員式,有static修飾
·對象級:成員式,普通,無static修飾
·本地內部類:局部式
·匿名級:局部式
成員式內部類的基本規則
·能夠有各類修飾符,能夠用4種權限、static、final、abstract定義
·如有static限定,就爲類級,不然爲對象級。類級能夠經過外部類直接訪問,對象級須要先生成外部的對象後才能訪問
·內外部類不能同名
·非靜態內部類中不能聲明任何static成員
·內部類能夠互相調用
成員式內部類的訪問
內部類訪問外層類對象的成員時,語法爲:
外層類名.this.屬性
使用內部類時,由外部類對象加「.new」操做符調用內部類的構造方法,建立內部類的對象。
在另外一個外部類中使用非靜態內部類中定義的方法時,要先建立外部類的對象,再建立與外部類相關的內部類的對象,再調用內部類的方法。
static內部類至關於其外部類的static成分,它的對象與外部類對象間不存在依賴關係,所以能夠直接建立。
因爲內部類能夠直接訪問其外部類的成分,所以,當內部類與其外部類中存在同名屬性或方法時,也將致使命名衝突。因此,在多層調用時要指明。
本地類是定義在代碼塊中的類,只在定義它們的代碼塊中可見。
本地類有如下幾個重要特性:
·僅在定義了它們的代碼塊中可見
·可使用定義它們的代碼塊中的任何本地final變量(注:本地類(也能夠是局部內部類/匿名內部類等等)使用外部類的變量,原意是希 望這個變量在本地類中的對象和在外部類中的這個變量對象是一致的,但若是這個變量不是final定義,它有可能在外部被修改,從而致使內外部類的變量對象 狀態不一致,所以,這類變量必須在外部類中加final前綴定義)
·本地類不能夠是static的,裏邊也不能定義static成員
·本地類不能夠用public、private、protected修飾,只能使用缺省的
·本地類能夠是abstract的
匿名內部類是本地內部類的一種特殊形式,即,沒有類名的內部類,並且具體的類實現會寫在這個內部類裏。
匿名類的規則
·匿名類沒有構造方法
·匿名類不能定義靜態的成員
·匿名類不能用4種權限、static、final、abstract修飾
·只能夠建立一個匿名類實例
Java的內存分配
Java程序運行時的內存結構分紅:方法區、棧內存、堆內存、本地方法棧幾種。
方法區存放裝載的類數據信息,包括:
·基本信息:每一個類的全限定名、每一個類的直接超類的全限定名、該類是類仍是接口、該類型的訪問修飾符、直接超接口的全限定名的有序列表。
·每一個已裝載類的詳細信息:運行時常量池、字段信息、方法信息、靜態變量、到類classloader的引用、到類class的引用。
棧內存
Java棧內存由局部變量區、操做數棧、幀數據區組成,以幀的形式存放本地方法的調用狀態(包括方法調用的參數、局部變量、中間結果……)。
堆內存
堆內存用來存放由new建立的對象和數組。在堆中分配的內存,由Java虛擬機的自動垃圾回收器來管理。
本地方法棧內存
Java經過Java本地接口JNI(Java Native Interface)來調用其它語言編寫的程序,在Java裏面用native修飾符來描述一個方法是本地方法。
String的內存分配
String是一個特殊的包裝類數據,因爲String類的值不可變性,當String變量須要常常變換其值時,應該考慮使用StringBuffer或StringBuilder類,以提升程序效率。
Java內存分配、管理小結
轉自: http://legend26.blog.163.com/blog/static/13659026020101122103954365/
首先是概念層面的幾個問題:
而後是運用層面:
Java中運行時內存結構
1.1 方法區:
方法區是系統分配的一個內存邏輯區域,是JVM在裝載類文件時,用於存儲類型信息的(類的描述信息)。
方法區存放的信息包括:
1.1.1類的基本信息:
1.1.2已裝載類的詳細信息:
運行時常量池:
在方法區中,每一個類型都對應一個常量池,存放該類型所用到的全部常量,常量池中存儲了諸如文字字符串、final變量值、類名和方法名常量。它們以數組形式經過索引被訪問,是外部調用與類聯繫及類型對象化的橋樑。(存的多是個普通的字符串,而後通過常量池解析,則變成指向某個類的引用)
字段信息存放類中聲明的每個字段的信息,包括字段的名、類型、修飾符。
字段名稱指的是類或接口的實例變量或類變量,字段的描述符是一個指示字段的類型的字符串,如private A a=null;則a爲字段名,A爲描述符,private爲修飾符
類中聲明的每個方法的信息,包括方法名、返回值類型、參數類型、修飾符、異常、方法的字節碼。
(在編譯的時候,就已經將方法的局部變量、操做數棧大小等肯定並存放在字節碼中,在裝載的時候,隨着類一塊兒裝入方法區。)
在運行時,JVM從常量池中得到符號引用,而後在運行時解析成引用項的實際地址,最後經過常量池中的全限定名、方法和字段描述符,把當前類或接口中的代碼與其它類或接口中的代碼聯繫起來。 |
這個沒什麼好說的,就是類變量,類的全部實例都共享,咱們只需知道,在方法區有個靜態區,靜態區專門存放靜態變量和靜態塊。
由此咱們能夠知道反射的基礎:
在裝載類的時候,加入方法區中的全部信息,最後都會造成Class類的實例,表明這個被裝載的類。方法區中的全部的信息,都是能夠經過這個Class類對象反射獲得。咱們知道對象是類的實例,類是相同結構的對象的一種抽象。同類的各個對象之間,實際上是擁有相同的結構(屬性),擁有相同的功能(方法),各個對象的區別只在於屬性值的不一樣。 一樣的,咱們全部的類,其實都是Class類的實例,他們都擁有相同的結構-----Field數組、Method數組。而各個類中的屬性都是Field屬性的一個具體屬性值,方法都是Method屬性的一個具體屬性值。 |
在運行時,JVM從常量池中得到符號引用,而後在運行時解析成引用項的實際地址,最後經過常量池中的全限定名、方法和字段描述符,把當前類或接口中的代碼與其它類或接口中的代碼聯繫起來。
1.2 Java棧
JVM棧是程序運行時單位,決定了程序如何執行,或者說數據如何處理。
在Java中,一個線程就會有一個線程的JVM棧與之對應,由於不過的線程執行邏輯顯然不一樣,所以都須要一個獨立的JVM棧來存放該線程的執行邏輯。
對方法的調用:
Java棧內存,以幀的形式存放本地方法的調用狀態,包括方法調用的參數、局部變量、中間結果等(方法都是以方法幀的形式存放在方法區的),每調用一個方法就將對應該方法的方法幀壓入Java棧,成爲當前方法幀。當調用結束(返回)時,就彈出該幀。
這意味着:
在方法中定義的一些基本類型的變量和引用變量都在方法的棧內存中分配。當在一段代碼塊定義一個變量時,Java就在棧中爲這個變量分配內存空間,當超過變量的做用域後(方法執行完成後),Java會自動釋放掉爲該變量所分配的內存空間,該內存空間能夠當即被另做它用。--------同時,由於變量被釋放,該變量對應的對象,也就失去了引用,也就變成了能夠被gc對象回收的垃圾。
所以咱們能夠知道成員變量與局部變量的區別:
局部變量,在方法內部聲明,當該方法運行完時,內存即被釋放。 成員變量,只要該對象還在,哪怕某一個方法運行完了,仍是存在。 從系統的角度來講,聲明局部變量有利於內存空間的更高效利用(方法運行完即回收)。 成員變量可用於各個方法間進行數據共享。 |
Java 棧內存的組成:
局部變量區、操做數棧、幀數據區組成。
(1):局部變量區爲一個以字爲單位的數組,每一個數組元素對應一個局部變量的 值。調用方法時,將方法的局部變量組成一個數組,經過索引來訪問。若爲非靜態方法,則加入一個隱含的引用參數this,該參數指向調用這個方法的對象。而 靜態方法則沒有this參數。所以,對象沒法調用靜態方法。
由此,咱們能夠知道,方法何時設計爲靜態,何時爲非靜態?
前面已經說過,對象是類的一個實例,各個對象結構相同,只是屬性不一樣。 而靜態方法是對象沒法調用的。 因此,靜態方法適合那些工具類中的工具方法,這些類只是用來實現一些功能,也不須要產生對象,經過設置對象的屬性來獲得各個不一樣的個體。 |
(2):操做數棧也是一個數組,可是經過棧操做來訪問。所謂操做數是那些被指令操做的數據。當須要對參數操做時如a=b+c,就將即將被操做的參數壓棧,如將b 和c 壓棧,而後由操做指令將它們彈出,並執行操做。虛擬機將操做數棧做爲工做區。
(3):幀數據區處理常量池解析,異常處理等
1.3 java堆
java的堆是一個運行時的數據區,用來存儲數據的單元,存放經過new關鍵字新建的對象和數組,對象從中分配內存。
在堆中聲明的對象,是不能直接訪問的,必須經過在棧中聲明的指向該引用的變量來調用。引用變量就至關因而爲數組或對象起的一個名稱,之後就能夠在程序中使用棧中的引用變量來訪問堆中的數組或對象。
由此咱們能夠知道,引用類型變量和對象的區別:
聲明的對象是在堆內存中初始化的, 真正用來存儲數據的。不能直接訪問。 引用類型變量是保存在棧當中的,一個用來引用堆中對象的符號而已(指針)。 |
堆與棧的比較:
JAVA堆與棧都是用來存放數據的,那麼他們之間到底有什麼差別呢?既然棧也能存放數據,爲何還要設計堆呢?
1.從存放數據的角度:
前面咱們已經說明:
棧中存放的是基本類型的變量or引用類型的變量
堆中存放的是對象or數組對象.
在棧中,引用變量的大小爲32位,基本類型爲1-8個字節。
可是對象的大小和數組的大小是動態的,這也決定了堆中數據的動態性,由於它是在運行時動態分配內存的,生存期也沒必要在編譯時肯定,Java 的垃圾收集器會自動收走這些再也不使用的數據。
2.從數據共享的角度:
1).在單個線程類,棧中的數據可共享
例如咱們定義:
int a=3; int b=3;
編 譯器先處理int a = 3;首先它會在棧中建立一個變量爲a 的引用,而後查找棧中是否有3 這個值,若是沒找到,就將3 存放進來,而後將a 指向3。接着處理int b = 3;在建立完b 的引用變量後,由於在棧中已經有3這個值,便將b 直接指向3。這樣,就出現了a 與b 同時均指向3的狀況。
而若是咱們定義:
Integer a=new Integer(3);//(1) Integer b=new Integer(3);//(2)
這個時候執行過程爲:在執行(1)時,首先在棧中建立一個變量a,而後在堆內存中實例化一個對象,而且將變量a指向這個實例化的對象。在執行(2)時,過程相似,此時,在堆內存中,會有兩個Integer類型的對象。
2).在進程的各個線程之間,數據的共享經過堆來實現
例:那麼,在多線程開發中,咱們的數據共享又是怎麼實現的呢?
如圖所示,堆中的數據是全部線程棧所共享的,咱們能夠經過參數傳遞,將一個堆中的數據傳入各個棧的工做內存中,從而實現多個線程間的數據共享
(多個進程間的數據共享則須要經過網絡傳輸了。)
3.從程序設計的的角度:
從軟件設計的角度看,JVM棧表明了處理邏輯,而JVM堆表明了數據。這樣分開,使得處理邏輯更爲清晰。分而治之的思想。這種隔離、模塊化的思想在軟件設計的方方面面都有體現。
4.值傳遞和引用傳遞的真相
有了以上關於棧和堆的種種瞭解後,咱們很容易就能夠知道值傳遞和引用傳遞的真相:
1.程序運行永遠都是在JVM棧中進行的,於是參數傳遞時,只存在傳遞基本類型和對象引用的問題。不會直接傳對象自己。 可是傳引用的錯覺是如何形成的呢? 在運行JVM棧中,基本類型和引用的處理是同樣的,都是傳值,因此,若是是傳引用的方法調用,也同時能夠理解爲「傳引用值」的傳值調用,即引用的處理跟基本類型是徹底同樣的。 可是當進入被調用方法時,被傳遞的這個引用的值,被程序解釋(或者查找)到JVM堆中的對象,這個時候纔對應到真正的對象。 若是此時進行修改,修改的是引用對應的對象,而不是引用自己,即:修改的是JVM堆中的數據。因此這個修改是能夠保持的了。 |
最後:
從某種意義上來講對象都是由基本類型組成的。
能夠把一個對象看做爲一棵樹,對象的屬性若是仍是對象,則仍是一顆樹(即非葉子節點),基本類型則爲樹的葉子節點。程序參數傳遞時,被傳遞的值自己都是不能進行修改的,可是,若是這個值是一個非葉子節點(即一個對象引用),則能夠修改這個節點下面的全部內容。 |
其實,面向對象方式的程序與之前結構化的程序在執行上沒有任何區別。
面向對象的引入,只是改變了咱們對待問題的思考方式,而更接近於天然方式的思考。
當咱們把對象拆開,其實對象的屬性就是數據,存放在JVM堆中;而對象的行爲(方法),就是運行邏輯,放在JVM棧中。咱們在編寫對象的時候,其實即編寫了數據結構,也編寫的處理數據的邏輯。