當前位置:首頁 » 服務存儲 » 內存存儲模型
擴展閱讀
webinf下怎麼引入js 2023-08-31 21:54:13
堡壘機怎麼打開web 2023-08-31 21:54:11

內存存儲模型

發布時間: 2022-10-19 15:57:00

⑴ maya如何導出占內存小的模型

1、打開maya導入或者製作你需要進行保存的模型。
2、接著在次級菜單中選擇「插件管理器並打開。
3、在菜單欄中選擇「需要進行導出的模型。
4、然後選擇保存的路徑和文件的命名,按回車鍵就可以導出文件了。

⑵ java內存模型裡面為什麼要分主存和本地內存

你說的應該是主存和工作內存吧,主存是公共空間,基本可以類比為虛擬機模型中的堆,對象創建好了都是在主存里,所有線程都可以訪問,工作內存是線程的私有內存,只有本線程可以訪問,如果線程要操作主存中的某個對象,必須從主存中拷貝到工作內存,在對工作內存中的副本進行操作,操作後再寫入主存,而不能對主存的對象直接操作

⑶ java課程分享java多線程的內存模型



硬體的內存模型

物理機並發處理的方案對於jvm的內存模型實現,也有很大的參考作用,畢竟jvm也是在硬體層上來做事情,底層架構也決定了上層的建築建模方式。


計算機並發並非只是多個處理器都參與進來計算就可以了,會牽扯到一些列硬體的問題,最直接的就是要和內存做交互。但計算機的存儲設備與處理器的預算速度相差太大,完全不能滿足處理器的處理速度,怎麼辦,這就是後續加入的一層讀寫速度接近處理器運算速度的高速緩存來作為處理器和內存之間的緩沖。


高速緩存一邊把使用的數據,從內存復制搬入,方便處理器快速運算,一邊把運算後的數據,再同步到主內存中,如此處理器就無需等待了。


高速緩存雖然解決了處理器和內存的矛盾,但也為計算機帶來了另一個問題:緩存一致性。特別是當多個處理器都涉及到同一塊主內存區域的時候,將可能會導致各自的緩存數據不一致。


那麼出現不一致情況的時候,以誰的為准?


為了解決這個問題,處理器和內存之間的讀寫的時候需要遵循一定的協議來操作,這類協議有:MSI、MESI、MOSI、Synapse、Firefly 以及 Dragon Protocol等。這就是上圖中處理器、高速緩存、以及內存之間的處理方式。


另外除了高速緩存之外,為了充分利用處理器,處理器還會把輸入的指令碼進行亂序執行優化,只要保證輸出一致,輸入的信息可以亂序執行重組,所以程序中的語句計算順序和輸入代碼的順序並非一致。



JVM內存模型

上面我們了解了硬體的內存模型,以此為借鑒,我們看看jvm的內存模型。



jvm定義的一套java內存模型為了能夠跨平台達到一致的內存訪問效果,從而屏蔽掉了各種硬體和操作系統的內存訪問差異。這點和c和c++並不一樣,C和C++會直接使用物理硬體和操作系統的內存模型來處理,所以在各個平台上會有差異,這一點java不會。


java的內存模型規定了所有的變數都存儲在主內存中,java課程http://www.kmbdqn.com/發現每個線程擁有自己的工作內存,工作內存保存了該線程使用到的變數的主內存拷貝,線程對變數所有操作,讀取,賦值,都必須在工作內存中進行,不能直接寫主內存變數,線程間變數值的傳遞均需要主內存來完成。


⑷ java內存模型的JMM簡介

1)JSR133:
在Java語言規范裡面指出了JMM是一個比較開拓性的嘗試,這種嘗試視圖定義一個一致的、跨平台的內存模型,但是它有一些比較細微而且很重要的缺點。其實Java語言裡面比較容易混淆的關鍵字主要是synchronized和volatile,也因為這樣在開發過程中往往開發者會忽略掉這些規則,這也使得編寫同步代碼比較困難。
JSR133本身的目的是為了修復原本JMM的一些缺陷而提出的,其本身的制定目標有以下幾個: 保留目JVM的安全保證,以進行類型的安全檢查: 提供(out-of-thin-air safety)無中生有安全性,這樣「正確同步的」應該被正式而且直觀地定義 程序員要有信心開發多線程程序,當然沒有其他辦法使得並發程序變得很容易開發,但是該規范的發布主要目標是為了減輕程序員理解內存模型中的一些細節負擔 提供大范圍的流行硬體體系結構上的高性能JVM實現,現在的處理器在它們的內存模型上有著很大的不同,JMM應該能夠適合於實際的盡可能多的體系結構而不以性能為代價,這也是Java跨平台型設計的基礎 提供一個同步的習慣用法,以允許發布一個對象使他不用同步就可見,這種情況又稱為初始化安全(initialization safety)的新的安全保證 對現有代碼應該只有最小限度的影響 2)同步、非同步【這里僅僅指概念上的理解,不牽涉到計算機底層基礎的一些操作】:
在系統開發過程,經常會遇到這幾個基本概念,不論是網路通訊、對象之間的消息通訊還是Web開發人員常用的Http請求都會遇到這樣幾個概念,經常有人提到Ajax是非同步通訊方式,那麼究竟怎樣的方式是這樣的概念描述呢?
同步:同步就是在發出一個功能調用的時候,在沒有得到響應之前,該調用就不返回,按照這樣的定義,其實大部分程序的執行都是同步調用的,一般情況下,在描述同步和非同步操作的時候,主要是指代需要其他部件協作處理或者需要協作響應的一些任務處理。比如有一個線程A,在A執行的過程中,可能需要B提供一些相關的執行數據,當然觸發B響應的就是A向B發送一個請求或者說對B進行一個調用操作,如果A在執行該操作的時候是同步的方式,那麼A就會停留在這個位置等待B給一個響應消息,在B沒有任何響應消息回來的時候,A不能做其他事情,只能等待,那麼這樣的情況,A的操作就是一個同步的簡單說明。
非同步:非同步就是在發出一個功能調用的時候,不需要等待響應,繼續進行它該做的事情,一旦得到響應了過後給予一定的處理,但是不影響正常的處理過程的一種方式。比如有一個線程A,在A執行的過程中,同樣需要B提供一些相關數據或者操作,當A向B發送一個請求或者對B進行調用操作過後,A不需要繼續等待,而是執行A自己應該做的事情,一旦B有了響應過後會通知A,A接受到該非同步請求的響應的時候會進行相關的處理,這種情況下A的操作就是一個簡單的非同步操作。
3)可見性、可排序性
Java內存模型的兩個關鍵概念:可見性(Visibility)和可排序性(Ordering)
開發過多線程程序的程序員都明白,synchronized關鍵字強制實施一個線程之間的互斥鎖(相互排斥),該互斥鎖防止每次有多個線程進入一個給定監控器所保護的同步語句塊,也就是說在該情況下,執行程序代碼所獨有的某些內存是獨占模式,其他的線程是不能針對它執行過程所獨占的內存進行訪問的,這種情況稱為該內存不可見。但是在該模型的同步模式中,還有另外一個方面:JMM中指出了,JVM在處理該強制實施的時候可以提供一些內存的可見規則,在該規則裡面,它確保當存在一個同步塊時,緩存被更新,當輸入一個同步塊時,緩存失效。因此在JVM內部提供給定監控器保護的同步塊之中,一個線程所寫入的值對於其餘所有的執行由同一個監控器保護的同步塊線程來說是可見的,這就是一個簡單的可見性的描述。這種機器保證編譯器不會把指令從一個同步塊的內部移到外部,雖然有時候它會把指令由外部移動到內部。JMM在預設情況下不做這樣的保證——只要有多個線程訪問相同變數時必須使用同步。簡單總結:
可見性就是在多核或者多線程運行過程中內存的一種共享模式,在JMM模型裡面,通過並發線程修改變數值的時候,必須將線程變數同步回主存過後,其他線程才可能訪問到。
可排序性提供了內存內部的訪問順序,在不同的程序針對不同的內存塊進行訪問的時候,其訪問不是無序的,比如有一個內存塊,A和B需要訪問的時候,JMM會提供一定的內存分配策略有序地分配它們使用的內存,而在內存的調用過程也會變得有序地進行,內存的折中性質可以簡單理解為有序性。而在Java多線程程序裡面,JMM通過Java關鍵字volatile來保證內存的有序訪問。 1)簡單分析:
Java語言規范中提到過,JVM中存在一個主存區(Main Memory或Java Heap Memory),Java中所有變數都是存在主存中的,對於所有線程進行共享,而每個線程又存在自己的工作內存(Working Memory),工作內存中保存的是主存中某些變數的拷貝,線程對所有變數的操作並非發生在主存區,而是發生在工作內存中,而線程之間是不能直接相互訪問,變數在程序中的傳遞,是依賴主存來完成的。而在多核處理器下,大部分數據存儲在高速緩存中,如果高速緩存不經過內存的時候,也是不可見的一種表現。在Java程序中,內存本身是比較昂貴的資源,其實不僅僅針對Java應用程序,對操作系統本身而言內存也屬於昂貴資源,Java程序在性能開銷過程中有幾個比較典型的可控制的來源。synchronized和volatile關鍵字提供的內存中模型的可見性保證程序使用一個特殊的、存儲關卡(memory barrier)的指令,來刷新緩存,使緩存無效,刷新硬體的寫緩存並且延遲執行的傳遞過程,無疑該機制會對Java程序的性能產生一定的影響。
JMM的最初目的,就是為了能夠支持多線程程序設計的,每個線程可以認為是和其他線程不同的CPU上運行,或者對於多處理器的機器而言,該模型需要實現的就是使得每一個線程就像運行在不同的機器、不同的CPU或者本身就不同的線程上一樣,這種情況實際上在項目開發中是常見的。對於CPU本身而言,不能直接訪問其他CPU的寄存器,模型必須通過某種定義規則來使得線程和線程在工作內存中進行相互調用而實現CPU本身對其他CPU、或者說線程對其他線程的內存中資源的訪問,而表現這種規則的運行環境一般為運行該程序的運行宿主環境(操作系統、伺服器、分布式系統等),而程序本身表現就依賴於編寫該程序的語言特性,這里也就是說用Java編寫的應用程序在內存管理中的實現就是遵循其部分原則,也就是前邊提及到的JMM定義了Java語言針對內存的一些的相關規則。然而,雖然設計之初是為了能夠更好支持多線程,但是該模型的應用和實現當然不局限於多處理器,而在JVM編譯器編譯Java編寫的程序的時候以及運行期執行該程序的時候,對於單CPU的系統而言,這種規則也是有效的,這就是是上邊提到的線程和線程之間的內存策略。JMM本身在描述過程沒有提過具體的內存地址以及在實現該策略中的實現方法是由JVM的哪一個環節(編譯器、處理器、緩存控制器、其他)提供的機制來實現的,甚至針對一個開發非常熟悉的程序員,也不一定能夠了解它內部對於類、對象、方法以及相關內容的一些具體可見的物理結構。相反,JMM定義了一個線程與主存之間的抽象關系,其實從上邊的圖可以知道,每一個線程可以抽象成為一個工作內存(抽象的高速緩存和寄存器),其中存儲了Java的一些值,該模型保證了Java裡面的屬性、方法、欄位存在一定的數學特性,按照該特性,該模型存儲了對應的一些內容,並且針對這些內容進行了一定的序列化以及存儲排序操作,這樣使得Java對象在工作內存裡面被JVM順利調用,(當然這是比較抽象的一種解釋)既然如此,大多數JMM的規則在實現的時候,必須使得主存和工作內存之間的通信能夠得以保證,而且不能違反內存模型本身的結構,這是語言在設計之處必須考慮到的針對內存的一種設計方法。這里需要知道的一點是,這一切的操作在Java語言裡面都是依靠Java語言自身來操作的,因為Java針對開發人員而言,內存的管理在不需要手動操作的情況下本身存在內存的管理策略,這也是Java自己進行內存管理的一種優勢。
[1]原子性(Atomicity):
這一點說明了該模型定義的規則針對原子級別的內容存在獨立的影響,對於模型設計最初,這些規則需要說明的僅僅是最簡單的讀取和存儲單元寫入的的一些操作,這種原子級別的包括——實例、靜態變數、數組元素,只是在該規則中不包括方法中的局部變數。
[2]可見性(Visibility):
在該規則的約束下,定義了一個線程在哪種情況下可以訪問另外一個線程或者影響另外一個線程,從JVM的操作上講包括了從另外一個線程的可見區域讀取相關數據以及將數據寫入到另外一個線程內。
[3]可排序性(Ordering):
該規則將會約束任何一個違背了規則調用的線程在操作過程中的一些順序,排序問題主要圍繞了讀取、寫入和賦值語句有關的序列。
如果在該模型內部使用了一致的同步性的時候,這些屬性中的每一個屬性都遵循比較簡單的原則:和所有同步的內存塊一樣,每個同步塊之內的任何變化都具備了原子性以及可見性,和其他同步方法以及同步塊遵循同樣一致的原則,而且在這樣的一個模型內,每個同步塊不能使用同一個鎖,在整個程序的調用過程是按照編寫的程序指定指令運行的。即使某一個同步塊內的處理可能會失效,但是該問題不會影響到其他線程的同步問題,也不會引起連環失效。簡單講:當程序運行的時候使用了一致的同步性的時候,每個同步塊有一個獨立的空間以及獨立的同步控制器和鎖機制,然後對外按照JVM的執行指令進行數據的讀寫操作。這種情況使得使用內存的過程變得非常嚴謹!
如果不使用同步或者說使用同步不一致(這里可以理解為非同步,但不一定是非同步操作),該程序執行的答案就會變得極其復雜。而且在這樣的情況下,該內存模型處理的結果比起大多數程序員所期望的結果而言就變得十分脆弱,甚至比起JVM提供的實現都脆弱很多。因為這樣所以出現了Java針對該內存操作的最簡單的語言規范來進行一定的習慣限制,排除該情況發生的做法在於:
JVM線程必須依靠自身來維持對象的可見性以及對象自身應該提供相對應的操作而實現整個內存操作的三個特性,而不是僅僅依靠特定的修改對象狀態的線程來完成如此復雜的一個流程。
[4]三個特性的解析(針對JMM內部):
原子性(Atomicity):
訪問存儲單元內的任何類型的欄位的值以及對其更新操作的時候,除開long類型和double類型,其他類型的欄位是必須要保證其原子性的,這些欄位也包括為對象服務的引用。此外,該原子性規則擴展可以延伸到基於long和double的另外兩種類型:volatile long和volatile double(volatile為java關鍵字),沒有被volatile聲明的long類型以及double類型的欄位值雖然不保證其JMM中的原子性,但是是被允許的。針對non-long/non-double的欄位在表達式中使用的時候,JMM的原子性有這樣一種規則:如果你獲得或者初始化該值或某一些值的時候,這些值是由其他線程寫入,而且不是從兩個或者多個線程產生的數據在同一時間戳混合寫入的時候,該欄位的原子性在JVM內部是必須得到保證的。也就是說JMM在定義JVM原子性的時候,只要在該規則不違反的條件下,JVM本身不去理睬該數據的值是來自於什麼線程,因為這樣使得Java語言在並行運算的設計的過程中針對多線程的原子性設計變得極其簡單,而且即使開發人員沒有考慮到最終的程序也沒有太大的影響。再次解釋一下:這里的原子性指的是原子級別的操作,比如最小的一塊內存的讀寫操作,可以理解為Java語言最終編譯過後最接近內存的最底層的操作單元,這種讀寫操作的數據單元不是變數的值,而是本機碼,也就是前邊在講《Java基礎知識》中提到的由運行器解釋的時候生成的Native Code。
可見性(Visibility):
當一個線程需要修改另外線程的可見單元的時候必須遵循以下原則: 一個寫入線程釋放的同步鎖和緊隨其後進行讀取的讀線程的同步鎖是同一個從本質上講,釋放鎖操作強迫它的隸屬線程【釋放鎖的線程】從工作內存中的寫入緩存裡面刷新(專業上講這里不應該是刷新,可以理解為提供)數據(flush操作),然後獲取鎖操作使得另外一個線程【獲得鎖的線程】直接讀取前一個線程可訪問域(也就是可見區域)的欄位的值。因為該鎖內部提供了一個同步方法或者同步塊,該同步內容具有線程排他性,這樣就使得上邊兩個操作只能針對單一線程在同步內容內部進行操作,這樣就使得所有操作該內容的單一線程具有該同步內容(加鎖的同步方法或者同步塊)內的線程排他性,這種情況的交替也可以理解為具有「短暫記憶效應」。這里需要理解的是同步的雙重含義:使用鎖機制允許基於高層同步協議進行處理操作,這是最基本的同步;同時系統內存(很多時候這里是指基於機器指令的底層存儲關卡memory barrier,前邊提到過)在處理同步的時候能夠跨線程操作,使得線程和線程之間的數據是同步的。這樣的機制也折射出一點,並行編程相對於順序編程而言,更加類似於分布式編程。後一種同步可以作為JMM機制中的方法在一個線程中運行的效果展示,注意這里不是多個線程運行的效果展示,因為它反應了該線程願意發送或者接受的雙重操作,並且使得它自己的可見區域可以提供給其他線程運行或者更新,從這個角度來看,使用鎖和消息傳遞可以視為相互之間的變數同步,因為相對其他線程而言,它的操作針對其他線程也是對等的。 一旦某個欄位被申明為volatile,在任何一個寫入線程在工作內存中刷新緩存的之前需要進行進一步的內存操作,也就是說針對這樣的欄位進行立即刷新,可以理解為這種volatile不會出現一般變數的緩存操作,而讀取線程每次必須根據前一個線程的可見域裡面重新讀取該變數的值,而不是直接讀取。 當某個線程第一次去訪問某個對象的域的時候,它要麼初始化該對象的值,要麼從其他寫入線程可見域裡面去讀取該對象的值;這里結合上邊理解,在滿足某種條件下,該線程對某對象域的值的讀取是直接讀取,有些時候卻需要重新讀取。這里需要小心一點的是,在並發編程裡面,不好的一個實踐就是使用一個合法引用去引用不完全構造的對象,這種情況在從其他寫入線程可見域裡面進行數據讀取的時候發生頻率比較高。從編程角度上講,在構造函數裡面開啟一個新的線程是有一定的風險的,特別是該類是屬於一個可子類化的類的時候。Thread.start由調用線程啟動,然後由獲得該啟動的線程釋放鎖具有相同的「短暫記憶效應」,如果一個實現了Runnable介面的超類在子類構造子執行之前調用了Thread(this).start()方法,那麼就可能使得該對象在線程方法run執行之前並沒有被完全初始化,這樣就使得一個指向該對象的合法引用去引用了不完全構造的一個對象。同樣的,如果創建一個新的線程T並且啟動該線程,然後再使用線程T來創建對象X,這種情況就不能保證X對象裡面所有的屬性針對線程T都是可見的除非是在所有針對X對象的引用中進行同步處理,或者最好的方法是在T線程啟動之前創建對象X。 若一個線程終止,所有的變數值都必須從工作內存中刷到主存,比如,如果一個同步線程因為另一個使用Thread.join方法的線程而終止,那麼該線程的可見域針對那個線程而言其發生的改變以及產生的一些影響是需要保證可知道的。 注意:如果在同一個線程裡面通過方法調用去傳一個對象的引用是絕對不會出現上邊提及到的可見性問題的。JMM保證所有上邊的規定以及關於內存可見性特性的描述——一個特殊的更新、一個特定欄位的修改都是某個線程針對其他線程的一個「可見性」的概念,最終它發生的場所在內存模型中Java線程和線程之間,至於這個發生時間可以是一個任意長的時間,但是最終會發生,也就是說,Java內存模型中的可見性的特性主要是針對線程和線程之間使用內存的一種規則和約定,該約定由JMM定義。
不僅僅如此,該模型還允許不同步的情況下可見性特性。比如針對一個線程提供一個對象或者欄位訪問域的原始值進行操作,而針對另外一個線程提供一個對象或者欄位刷新過後的值進行操作。同樣也有可能針對一個線程讀取一個原始的值以及引用對象的對象內容,針對另外一個線程讀取一個刷新過後的值或者刷新過後的引用。
盡管如此,上邊的可見性特性分析的一些特徵在跨線程操作的時候是有可能失敗的,而且不能夠避免這些故障發生。這是一個不爭的事實,使用同步多線程的代碼並不能絕對保證線程安全的行為,只是允許某種規則對其操作進行一定的限制,但是在最新的JVM實現以及最新的Java平台中,即使是多個處理器,通過一些工具進行可見性的測試發現其實是很少發生故障的。跨線程共享CPU的共享緩存的使用,其缺陷就在於影響了編譯器的優化操作,這也體現了強有力的緩存一致性使得硬體的價值有所提升,因為它們之間的關系在線程與線程之間的復雜度變得更高。這種方式使得可見度的自由測試顯得更加不切實際,因為這些錯誤的發生極為罕見,或者說在平台上我們開發過程中根本碰不到。在並行程開發中,不使用同步導致失敗的原因也不僅僅是對可見度的不良把握導致的,導致其程序失敗的原因是多方面的,包括緩存一致性、內存一致性問題等。
可排序性(Ordering):
可排序規則在線程與線程之間主要有下邊兩點: 從操作線程的角度看來,如果所有的指令執行都是按照普通順序進行,那麼對於一個順序運行的程序而言,可排序性也是順序的 從其他操作線程的角度看來,排序性如同在這個線程中運行在非同步方法中的一個「間諜」,所以任何事情都有可能發生。唯一有用的限制是同步方法和同步塊的相對排序,就像操作volatile欄位一樣,總是保留下來使用 【*:如何理解這里「間諜」的意思,可以這樣理解,排序規則在本線程裡面遵循了第一條法則,但是對其他線程而言,某個線程自身的排序特性可能使得它不定地訪問執行線程的可見域,而使得該線程對本身在執行的線程產生一定的影響。舉個例子,A線程需要做三件事情分別是A1、A2、A3,而B是另外一個線程具有操作B1、B2,如果把參考定位到B線程,那麼對A線程而言,B的操作B1、B2有可能隨時會訪問到A的可見區域,比如A有一個可見區域a,A1就是把a修改稱為1,但是B線程在A線程調用了A1過後,卻訪問了a並且使用B1或者B2操作使得a發生了改變,變成了2,那麼當A按照排序性進行A2操作讀取到a的值的時候,讀取到的是2而不是1,這樣就使得程序最初設計的時候A線程的初衷發生了改變,就是排序被打亂了,那麼B線程對A線程而言,其身份就是「間諜」,而且需要注意到一點,B線程的這些操作不會和A之間存在等待關系,那麼B線程的這些操作就是非同步操作,所以針對執行線程A而言,B的身份就是「非同步方法中的『間諜』。】
同樣的,這僅僅是一個最低限度的保障性質,在任何給定的程序或者平台,開發中有可能發現更加嚴格的排序,但是開發人員在設計程序的時候不能依賴這種排序,如果依賴它們會發現測試難度會成指數級遞增,而且在復合規定的時候會因為不同的特性使得JVM的實現因為不符合設計初衷而失敗。
注意:第一點在JLS(Java Language Specification)的所有討論中也是被採用的,例如算數表達式一般情況都是從上到下、從左到右的順序,但是這一點需要理解的是,從其他操作線程的角度看來這一點又具有不確定性,對線程內部而言,其內存模型本身是存在排序性的。【*:這里討論的排序是最底層的內存裡面執行的時候的NativeCode的排序,不是說按照順序執行的Java代碼具有的有序性質,本文主要分析的是JVM的內存模型,所以希望讀者明白這里指代的討論單元是內存區。】 JMM最初設計的時候存在一定的缺陷,這種缺陷雖然現有的JVM平台已經修復,但是這里不得不提及,也是為了讀者更加了解JMM的設計思路,這一個小節的概念可能會牽涉到很多更加深入的知識,如果讀者不能讀懂沒有關系先看了文章後邊的章節再返回來看也可以。
1)問題1:不可變對象不是不可變的
學過Java的朋友都應該知道Java中的不可變對象,這一點在本文最後講解String類的時候也會提及,而JMM最初設計的時候,這個問題一直都存在,就是:不可變對象似乎可以改變它們的值(這種對象的不可變指通過使用final關鍵字來得到保證),(Publis Service Reminder:讓一個對象的所有欄位都為final並不一定使得這個對象不可變——所有類型還必須是原始類型而不能是對象的引用。而不可變對象被認為不要求同步的。但是,因為在將內存寫方面的更改從一個線程傳播到另外一個線程的時候存在潛在的延遲,這樣就使得有可能存在一種競態條件,即允許一個線程首先看到不可變對象的一個值,一段時間之後看到的是一個不同的值。這種情況以前怎麼發生的呢?在JDK 1.4中的String實現里,這兒基本有三個重要的決定性欄位:對字元數組的引用、長度和描述字元串的開始數組的偏移量。String就是以這樣的方式在JDK 1.4中實現的,而不是只有字元數組,因此字元數組可以在多個String和StringBuffer對象之間共享,而不需要在每次創建一個String的時候都拷貝到一個新的字元數組里。假設有下邊的代碼:
String s1 = /usr/tmp;
String s2 = s1.substring(4); // /tmp
這種情況下,字元串s2將具有大小為4的長度和偏移量,但是它將和s1共享「/usr/tmp」裡面的同一字元數組,在String構造函數運行之前,Object的構造函數將用它們默認的值初始化所有的欄位,包括決定性的長度和偏移欄位。當String構造函數運行的時候,字元串長度和偏移量被設置成所需要的值。但是在舊的內存模型中,因為缺乏同步,有可能另一個線程會臨時地看到偏移量欄位具有初始默認值0,而後又看到正確的值4,結果是s2的值從「/usr」變成了「/tmp」,這並不是我們真正的初衷,這個問題就是原始JMM的第一個缺陷所在,因為在原始JMM模型裡面這是合理而且合法的,JDK 1.4以下的版本都允許這樣做。
2)問題2:重新排序的易失性和非易失性存儲
另一個主要領域是與volatile欄位的內存操作重新排序有關,這個領域中現有的JMM引起了一些比較混亂的結果。現有的JMM表明易失性的讀和寫是直接和主存打交道的,這樣避免了把值存儲到寄存器或者繞過處理器特定的緩存,這使得多個線程一般能看見一個給定變數最新的值。可是,結果是這種volatile定義並沒有最初想像中那樣如願以償,並且導致了volatile的重大混亂。為了在缺乏同步的情況下提供較好的性能,編譯器、運行時和緩存通常是允許進行內存的重新排序操作的,只要當前執行的線程分辨不出它們的區別。(這就是within-thread as-if-serial semantics[線程內似乎是串列]的解釋)但是,易失性的讀和寫是完全跨線程安排的,編譯器或緩存不能在彼此之間重新排序易失性的讀和寫。遺憾的是,通過參考普通變數的讀寫,JMM允許易失性的讀和寫被重排序,這樣以為著開發人員不能使用易失性標志作為操作已經完成的標志。比如:
Map configOptions;
char[] configText;
volatile boolean initialized = false;
// 線程1
configOptions = new HashMap();
configText = readConfigFile(filename);
processConfigOptions(configText,configOptions);
initialized = true;
// 線程2
while(!initialized)
sleep();
這里的思想是使用易失性變數initialized擔任守衛來表明一套別的操作已經完成了,這是一個很好的思想,但是不能在JMM下工作,因為舊的JMM允許非易失性的寫(比如寫到configOptions欄位,以及寫到由configOptions引用Map的欄位中)與易失性的寫一起重新排序,因此另外一個線程可能會看到initialized為true,但是對於configOptions欄位或它所引用的對象還沒有一個一致的或者說當前的針對內存的視圖變數,volatile的舊語義只承諾在讀和寫的變數的可見性,而不承諾其他變數,雖然這種方法更加有效的實現,但是結果會和我們設計之初大相徑庭。

⑸ 關於C#訪問內存

與C#內存管理對比分析
與C#管理內存方式概述

C#最大的一個改進其實就是對內存訪問與管理方法的改進。在.NET中內存的管理是全權委託給垃圾回收器,由垃圾回收器來決定何時該釋放內存空間。現在普遍採用兩種技術來釋放程序動態申請的系統內存:首先是以C++為代表的必須以手工方式使應用程序代碼完成這些工作,讓對象維護引用計數。然後是以.NET以及Java使用的垃圾回收器來完成內存釋放工作。

在C++中讓應用程序代碼負責釋放內存是低級、高性能的語言使用技術。這種技術非常有效,且可以讓資源在不需要時就釋放,因為這種技術可以直接訪問內存,所以其最大的缺點是可能導致錯誤。而且如果程序員的記性不太好的話,也會常常忘記釋放內存而導致內存泄漏。

在C#中內存的管理是依靠垃圾回收器,垃圾回收器是一個清理內存的程序。所有採用new關鍵字申請的動態內存空間都會分配到堆上,當.NET檢測到給定過程的堆已經滿時,需要清理時,就會調用垃圾回收器。垃圾回收器將採用垃圾回收演算法將那些不再被引用的對象所佔用的內存空間釋放掉。顯然由於程序員無法直接控制內存的釋放,所開發出的軟體性能和效率上一定會受到很大的影響。不過這種影響是隨著計算機硬體技術的發展日益縮小的。

究竟是C++中直接由程序員管理內存好,還是像.NET中那樣由單獨一個程序來統一管理好呢?這個問題是公說公有理,婆說婆有理。但是我相信隨著計算機硬體技術不斷的發展、存儲器空間越來越大、軟體的復雜性和軟體健壯性要求的不斷提高,程序員直接管理內存的方式必將會退出歷史舞台。當今的程序員不必再為該如何把程序分塊放到容量有限的內存中運行而擔心,因為這項任務已經交給了操作系統的虛擬內存來管理。相信不久將來人們也會習慣完全交由諸如垃圾回收器一類的專門程序來管理程序申請的內存空間。

中內存的分配方式

在C++中內存的分配方式大致有三種:

(1) 從靜態存儲區域分配。內存在程序編譯的時候已經分配完畢了。並且這塊內存中的所有數據在程序的整個運行期間都始終存在的。例如:全局變數,static變數等等。

(2) 在棧上創建。在函數執行期間,無論什麼時候到達一個特殊的執行點(左花括弧)時,存儲單元都可以在棧上被創建。出了執行點(右花括弧),這個存儲單元自動被釋放。這些棧分配運算內置在處理器的指令集中,非常有效,並且不存在失敗的危險,但是可供分配的內存容量很有限。

(3) 存儲單元也可以從一塊稱為堆(也被稱為自由存儲單元)的地方分配,從堆(heap)上分配,亦成為動態分配。在C++中,程序在運行期間可以用malloc或者new申請任意數量的內存,程序員自己掌握釋放內存的適當時機(使用free或者delete)。動態內存的生存期間是由程序員決定,使用非常靈活,但也最容易產生問題。

用戶管理內存常出現的問題

在C++中,我們必須非常小心第三種內存分配方式,因為內存的分配和釋放都得由程序員來控制,一不小心就會出錯。下面我就分析下在C++中,由於第三種內存分配方式而導致的一些常見的內存泄漏以及一系列的指針問題。

#include<iostream.h>

#include<string.h>

void GetMemory(char *p, int num)

{

p = new char[12];

}

void main()

{

char *str = NULL;

GetMemory(str,100);

strcpy(str,"hello");

}

注意到函數GetMemory(char *p,int num),中的第一個字元型指針參數。寫程序的人的本意可能是希望通過此函數為str指針申請內存。但事實上卻是str並不會得到所期望得到的內存,str依舊是NULL。因為函數GetMemory(char *p,int num)中所得到的只是指針str的一個副本使得p = str ,他們所存儲的內容均是指向同一個內存的地址,但是由於p申請了新的內存,但str指針的值並沒被改變,所以函數GetMemory並不能得到任何有用的東西。並且由於每執行一次GetMemory就會泄漏一塊內存,因為沒有使用free釋放內存。

#include<iostream>

using namespace std;

class X

{

public:

int *ptrArray;

int size;

X(int *ptr , int size)

{

ptrArray = new int[size]; //(A)

for(int i=0;i<size;i++)ptrArray[i]=ptr[i];

}

};

int main()

{

int arrayData[100]={0};

X *bill = new X(arrayData,100); //(B)

delete bill; //(C)

return 0;

}

例如上面程序中的X類,程序員忘記了編寫析構函數來釋放在類中所動態申請內存空間。注意到程序第B行代碼處,聲明了一個X類的對象指針bill。然後在代碼第A行處代碼動態申請了一段內存空間,並且把數組arrayData中的數值都復制到類中。

接著在代碼第C行處,我們刪除了指針bill所指向的內存空間。但是類中第A行所申請的內存空間並不會被刪除,所以將造成內存泄漏。

#include<iostream.h>

#include<string.h>

void main()

{

char *pointer = new char[100];

strcpy(pointer,"Hello , I am Howells !");

cout<<pointer<<endl;

cout<<"Before call delete function, the address of pointer is : "<<&pointer<<endl;

delete pointer; //(A)

cout<<"After call delete function, the address of pointer is : "<<&pointer<<endl;

//其間程序非常長,程序員也許忘記了p所指向的內存空間已經釋放。

if(pointer != NULL)

{

strcpy(pointer, "world"); // (B)

}

}

程序運行結果:

Hello , I am Howells !

Before call delete function, the address of pointer is : 0x0013FF7C

After call delete function, the address of pointer is : 0x0013FF7C

Press any key to continue

注意到程序中第A行代碼,雖然在第A行代碼對pointer指針進行了delete操作,但是delete方法只是會釋放掉該地址所指向的內存,而pointer指針仍然指向原來所指向的內存地址。通常由於程序比較長,程序員有時可能記不住pointer所指向的內存是否被釋放掉了,所以我們會使用一個語句if(pointer != NULL)來進行測試,但是很遺憾的是,pointer並不是NULL,它指向一塊不合法的內存單元。第B行的代碼在VC++6.0是可行的,也就是說strcpy操作會將一塊不屬於自己的一塊內存單元賦值,這非常可怕會對其他程序造成潛在的危害。

#include<iostream.h>

class A

{

public:

void Func(void){cout<<"A::func() " <<endl;}

int I;

};

A *Test(void)

{

A a;

return &a; / /(A)

}

void main()

{

A *p = NULL;

p = Test();

p->Func();

}

在Test函數中我們聲明了一個對象a,然後返回對象a的地址。我們知道由於a是臨時變數,所以當函數出了執行點(右花括弧)時,a被退棧了,但是a所在的存儲單元並沒有被清除掉。所以仍然可以通過指針p來訪問函數Func()。這顯然將造成潛在的危險。

中內存管理機制

中動態內存分配方式
在C#中對內存的管理是依靠.NET 垃圾回收器來完成的,垃圾回收器為高速的分配服務提供了很好的內存使用機制。它可以恢復正在運行中的應用程序需要的內存。垃圾回收器負責清理內存,當.NET檢測到給定過程的堆已滿時,需要清理時,就需要調用垃圾回收器,下面我將詳細介紹.NET的內存分配機制。和C++一樣,在.NET中用戶所申請的動態內存空間將被分配到堆上,不同的是在.NET上的堆是託管堆。自動內存管理是公共語言運行庫在託管執行過程過程中提供的服務之一。公共語言運行庫的垃圾回收器為應用程序管理內存的分配和釋放。對開發人員而言,這就意味著在開發託管應用程序時不必編寫執行內存管理任務的代碼。

在C#中大致有三種不同的存儲單元:

(1) Managed Heap:這是動態配置(Dynamic Allocation)的存儲單元,由Gargage Collector在執行時自動管理,整個進程將公用一個Managed Heap。

(2) Call Stack:這是由.NET CLR在執行時自動管理的存儲單元,每個Thread都有自己專門的Call Stack。每呼叫一次method,就會使得Call Stack上多一個Record Frame;方法執行完畢之後,此Record Frame會被丟棄。這一點與C++類似。

(3) Evaluation Stack:這是由.NET CLR在執行時自動管理的存儲單元,每個Thread都有自己專門的Evaluation Stack。這個堆棧也叫做堆疊式虛擬機,既程序執行時的資料都是先放在堆疊中,再進行運算。

其三種存儲單元的物理結構模型如下:

圖1-1

下圖是託管堆的簡化模型。

圖1-2

在C#中動態分配內存時,.NET是採用如下規則進行內存管理的。

(1) 堆被劃分為代,以便只需查找堆的一小部分就能清除大多數垃圾。

(2) 同代中的對象大體上均為同齡。

(3) 代的編號越高,表示堆的這一片區域所包含的對象越老,這些對象就越有可能是穩定的。最老的對象位於最低的地址內,而新的對象則創建在增加的地址內。

(4) 新對象的分配指針標記了內存的已使用(已分配)內存區域和未使用(可用)內存區域之間的邊界。

(5) 通過刪除死對象並將活對象轉移到堆的低地址末尾,堆周期性地進行壓縮。這就擴展了在創建新對象的圖表底部的未使用區域。

(6) 對象在內存中的順序仍然是創建它們的順序,以便於定位。

(7) 在堆中,對象之間永遠不會有任何空隙。

(8) 只有某些可用空間是已提交的。需要時,操作系統會從「保留的」地址范圍中分配更多的內存。

(9) 所有可進行垃圾回收的對象都分配在一個連續的地址空間范圍內。

中動態內存回收機制
在C#中大致有三種垃圾回收機制:完全回收、部分回收、使代與寫入屏障配合工作。

圖1-3

1) 完全回收

在完全回收時,程序將停止執行,並且到託管堆中找到所有的根。這些根以各種形式出現,它們可以是堆棧上的指針或者指向堆中的全局變數。從根開始,我們訪問每個對象,並沿途追溯包含在每個被訪問對象內的每個對象指針,指針用於標記這些對象。一旦找出了不可達到的對象,我們就需要回收空間以便隨後使用;在這里,回收器的目標是要將活的對象向上移動,並清除浪費的空間。在執行過程停止的情況下,回收器可以安全地移動所有這些對象,並修復所有指針,以便所有對象在新的位置上被正確鏈接。倖存的對象將被提升到下一代的編號(就是說,代的邊界得到更新),並且執行過程可以恢復。

2) 部分回收

假設最近執行了一次完全回收,程序繼續執行,在發生足夠多的分配之後,內存管理系統決定是進行回收的時候了。假設我們非常的幸運,自從上一次回收以後,在我們運行的所有時間里,我們根本沒有對任何較老的對象執行寫操作,而只是對新分配的(第零代 (gen0))對象執行了寫操作。因此,當執行垃圾回收的時候,只需要檢查所有的根,如果有任何根指向舊對象,就忽略這些對象。而對於其他根(指向 gen0 的根)我們進行追溯所有指針。一旦我們發現有內部指針指回較老的對象,我們就忽略它。完成以後,我們就訪問完gen0中的所有活的對象,但沒有訪問過任何老的對象(gen1,gen2對象)。接著就對gen0區域進行回收空間處理。

3) 使代與寫入屏障配合工作

但事實上,部分回收演算法的充分條件是不太可能的,因為總會有一些較老的對象肯定會發生更改。發生這種情況時,.NET使用另外一種輔助的數據結構來配合部分回收演算法。card table的數據結構來記住臟對象的位置;牌桌中的每個位代表堆中的一個內存范圍,比如說是 128 個位元組。程序每次將對象寫入某個地址時,寫入屏障代碼必須計算哪個 128 位元組塊被寫入,然後在牌桌中設置相應的位。

如果我們正在執行一次 gen0 垃圾回收,我們可以使用上面討論的演算法(忽略指向較老代的任何指針),但一旦我們完成該操作,那麼我們還必須查找位於牌桌中被標記為已修改的塊中的每個對象中的每個對象指針。我們必須像對待根一樣對待這些指針。如果我們同樣地考慮這些指針,那麼我們將准確無誤地只回收 gen0 對象。

中動態分配內存注意事項
我們了解了.NET垃圾回收器的工作原理後,就可以針對它來制定出編寫高效程序的准則:

(1) 最大程度地減少對象指針的寫入次數,尤其是對較老對象的寫入。

(2) 減少數據結構中的指針密度。第一,將有很多對象寫入。第二,當回收該數據結構的時間到來時,您將使垃圾回收器追溯所有這些指針,如果需要,還要隨著對象的到處移動全部更改這些指針。如果您的數據結構的生命周期很長,並且不會有很多更改,那麼,當完全回收發生時(在 gen2 級別),回收器只需要訪問所有這些指針。但如果您創建的此類結構的生命周期短暫(就是說,作為處理事務的一部分),那麼您將支付比正常情況下大出很多的開銷。

(3) 如果可以通過只增加少量的程序復雜性,則應該避免過多的動態內存臨時分配。如在比較兩個字元串的時候,應該避免使用String.Split。因為 String.Split 將創建一個字元串數組,這意味著原來在關鍵字字元串中的每個關鍵字都有一個新的字元串對象,再加上該數組也有一個對象。現在,您的兩行比較函數就創建了數量非常多的臨時對象。垃圾回收器突然因為您而負載大增,甚至使用最智能的回收方案也會有很多垃圾需要清理。最好編寫一個根本不需要分配內存的比較函數。

(4) 盡量避免使用析構函數。一個帶有析構函數的對象意味著它是需要終結的對象。垃圾回收器第一次遇到應死而未死但仍需要終結的對象時,它必須在這個時候放棄回收該對象的空間的嘗試。而是將對象添加到需要終結的對象列表中,而且,回收器隨後必須確保對象內的所有指針在終結完成之前仍然繼續有效。這基本上等同於說,從回收器的觀察角度來看,需要終結的每個對象都像是臨時的根對象。回收完成後,終結線程將遍歷需要終結的對象列表,並調用終結器。該操作完成時,對象再一次成為死對象,並且將以正常方式被自然回收。

與C++內存優缺點對比總結

在對C#以及C++的內存管理機制分析完畢以後,我們可以對比出它們間的優缺點如下:

(1) C#內存分配比C++更加有效率:因為不需要像傳統分配器那樣搜索可用的內存塊;所有需要發生的操作只是需要移動在可用的和已分配的區域之間的邊界。

(2) C#清理內存機制可以使得程序員無需為管理內存而單獨編寫在大多數時候都是重復的代碼(內存緊縮)。

(3) 在相當出色的程序員編寫的程序中沒有任何操縱與內存相關的錯誤代碼(通常非常難), 利用C++中程序員直接控制內存方式肯定比C#利用垃圾回收器更加有效。因為程序員通常更加清楚何時回收內存是最佳時刻。

(4) 由於C#中由垃圾回收器回收無用已分配的內存快,所以不會發生由於程序員疏忽而產生的內存泄漏。當然也可能會丟失一些資源,如忘記關閉與資料庫的連接等。

http://blog.csdn.net/hardwayboy/archive/2009/08/31/4499242.aspx

⑹ 關於java內存模型中read和load操作的一個問題,急求大神指點

我這是么個理解。
所謂的「線程的工作內存」就是線程棧,這個棧大小是通過 vm參數 -xss 來設置的。不同版本的jdk對應的大小不一樣,有的是512k。上面的read操作和load操作,我理解實際整體是在一個操作步驟中,所謂的read應是根據引用地址從堆中(即通過-Xms -Xmx 參數來設置的)讀出來;然後load過程,就是在線程運算需要使用此變數時,將變數的具體值(即引用指向的內存地址存儲的數據)壓入線程棧。
這也就是為什麼如果在並發時,同一個變數不加鎖會導致結果不可預料的問題。因為不同線程運算時,都會將變數值壓入當前線程棧而不是直接對堆上變更直接進行操作,這樣就存在一個時間窗口會導致不同的線程對堆中同一個變數值的不同「副本」進行操作,從而導致結果不可預料,也就引用了並發沖突問題。

⑺ 解釋概念:主存、輔存、Cache、RAM、SRAM、DRAM、ROM、PROM、EPROM、EEPROM、CDROM、Flash Memory。

主存,又稱內存,是計算機中重要的部件之一,它是與CPU進行溝通的橋梁。計算機中所有程序的運行都是在內存中進行的,因此內存的性能對計算機的影響非常大。 內存(Memory)也被稱為內存儲器,其作用是用於暫時存放CPU中的運算數據,以及與硬碟等外部存儲器交換的數據。只要計算機在運行中,CPU就會把需要運算的數據調到內存中進行運算,當運算完成後CPU再將結果傳送出來,內存的運行也決定了計算機的穩定運行。 內存是由內存晶元、電路板、金手指等部分組成的。

輔存狹義上是我們平時講的硬碟。科學地說是外部存儲器(需要通過I/O系統與之交換數據,又稱為輔助存儲器)。存儲容量大、成本低、存取速度慢,以及可以永久地離線保存信息。主要包括磁表面存儲器、軟盤存儲器、磁帶存儲設備、光碟存儲設備。

cache 高速緩沖存儲器 一種特殊的存儲器子系統,其中復制了頻繁使用的數據以利於快速訪問。存儲器的高速緩沖存儲器存儲了頻繁訪問的 RAM 位置的內容及這些數據項的存儲地址。當處理器引用存儲器中的某地址時,高速緩沖存儲器便檢查是否存有該地址。如果存有該地址,則將數據返回處理器;如果沒有保存該地址,則進行常規的存儲器訪問。因為高速緩沖存儲器總是比主RAM 存儲器速度快,所以當 RAM 的訪問速度低於微處理器的速度時,常使用高速緩沖存儲器。

RAM(Random Access Memory)隨機存取存儲器 主要用於存儲計算機運行時的程序和數據,需要執行的程序或者需要處理的數據都必須先裝入RAM內,是指既可以從該設備讀取數據,也可以往裡面寫數據。RAM的特點是:計算機通電狀態下RAM中的數據可以反復使用,只有向其中寫入新數據時才被更新;斷電後RAM中的數據隨之消失。

SRAM是英文Static RAM的縮寫,它是一種具有靜止存取功能的內存,不需要刷新電路即能保存它內部存儲的數據。SRAM不需要刷新電路即能保存它內部存儲的數據。
而DRAM(Dynamic Random Access Memory)每隔一段時間,要刷新充電一次,否則內部的數據即會消失,因此SRAM具有較高的性能,但是SRAM也有它的缺點,即它的集成度較低,相同容量的DRAM內存可以設計為較小的體積,但是SRAM卻需要很大的體積,且功耗較大。所以在主板上SRAM存儲器要佔用一部分面積。

ROM(Read Only Memory)只讀存儲器,是指只能從該設備中讀取數據而不能往裡面寫數據的存儲器。Rom中的數據是由設計者和製造商事先編好固化在裡面的一些程序,使用者不能隨意更改。ROM主要用於檢查計算機系統的配置情況並提供最基本的輸入輸出(I/O)程序,如存儲BIOS參數的CMOS晶元。Rom的特點是計算機斷電後存儲器中的數據仍然存在。

PROM (Programmable Read-Only Memory)-可編程只讀存儲器,也叫One-Time Programmable (OTP)ROM「一次可編程只讀存儲器」,是一種可以用程序操作的只讀內存。最主要特徵是只允許數據寫入一次,如果數據燒入錯誤只能報廢。

EPROM(Erasable Programmable ROM,可擦除可編程ROM)晶元可重復擦除和寫入,解決了PROM晶元只能寫入一次的弊端。EPROM晶元有一個很明顯的特徵,在其正面的陶瓷封裝上,開有一個玻璃窗口,透過該窗口,可以看到其內部的集成電路,紫外線透過該孔照射內部晶元就可以擦除其內的數據,完成晶元擦除的操作要用到EPROM擦除器。EPROM內資料的寫入要用專用的編程器,並且往晶元中寫內容時必須要加一定的編程電壓(VPP=12—24V,隨不同的晶元型號而定)。EPROM的型號是以27開頭的,如27C020(8*256K)是一片2M Bits容量的EPROM晶元。EPROM晶元在寫入資料後,還要以不透光的貼紙或膠布把窗口封住,以免受到周圍的紫外線照射而使資料受損。 EPROM晶元在空白狀態時(用紫外光線擦除後),內部的每一個存儲單元的數據都為1(高電平)。

EEPROM (Electrically Erasable Programmable Read-Only Memory),電可擦可編程只讀存儲器--一種掉電後數據不丟失的存儲晶元。 EEPROM 可以在電腦上或專用設備上擦除已有信息,重新編程。一般用在即插即用。EEPROM(電可擦寫可編程只讀存儲器)是可用戶更改的只讀存儲器(ROM),其可通過高於普通 EEPROM
電壓的作用來擦除和重編程(重寫)。不像EPROM晶元,EEPROM不需從計算機中取出即可修改。在一個EEPROM中,當計算機在使用的時候是可頻繁地重編程的,EEPROM的壽命是一個很重要的設計考慮參數。EEPROM的一種特殊形式是快閃記憶體,其應用通常是個人電腦中的電壓來擦寫和重編程。 EEPROM,一般用於即插即用(Plug & Play)。 常用在介面卡中,用來存放硬體設置數據。 也常用在防止軟體非法拷貝的"硬體鎖"上面。

CD-ROM(Compact Disc Read-Only Memory)即只讀光碟,是一種在電腦上使用的光碟。這種光碟只能寫入數據一次,信息將永久保存在光碟上,使用時通過光碟驅動器讀出信息。CD的格式最初是為音樂的存儲和回放設計的,1985年,由SONY和飛利浦制定的黃皮書標准使得這種格式能夠適應各種二進制數據。有些CD-ROM既存儲音樂,又存儲計算機數據,這種CD-ROM的音樂能夠被CD播放器播放,計算機數據只能被計算機處理。

Flash Memory,也稱快閃記憶體(Flash Memory)是一種長壽命的非易失性(在斷電情況下仍能保持所存儲的數據信息)的存儲器,數據刪除不是以單個的位元組為單位而是以固定的區塊為單位(注意:NOR Flash 為位元組存儲。),區塊大小一般為256KB到20MB。快閃記憶體是電子可擦除只讀存儲器(EEPROM)的變種,EEPROM與快閃記憶體不同的是,它能在位元組水平上進行刪除和重寫而不是整個晶元擦寫,這樣快閃記憶體就比EEPROM的更新速度快。由於其斷電時仍能保存數據,快閃記憶體通常被用來保存設置信息,如在電腦的BIOS(基本輸入輸出程序)、PDA(個人數字助理)、數碼相機中保存資料等

⑻ jvm中線程本地內存是真實存在的,還是一個抽象概念

jvm內存模型:Java代碼是運行在Java虛擬機之上的,由Java虛擬機通過解釋執行(解釋器)或編譯執行(即時編譯器)來完成,故Java內存模型,也就是指Java虛擬機的運行時內存模型。

運行時內存模型,分為線程私有和共享數據區兩大類,其中線程私有的數據區包含程序計數器、虛擬機棧、本地方法區,所有線程共享的數據區包含Java堆、方法區,在方法區內有一個常量池。java運行時的內存模型圖,如下:

從圖中,可知內存分為線程私有和共享兩大類:

(1)線程私有區,包含以下3類:
程序計數器,記錄正在執行的虛擬機位元組碼的地址;
虛擬機棧:方法執行的內存區,每個方法執行時會在虛擬機棧中創建棧幀;
本地方法棧:虛擬機的Native方法執行的內存區;

(2)線程共享區,包含以下2類
Java堆:對象分配內存的區域;
方法區:存放類信息、常量、靜態變數、編譯器編譯後的代碼等數據;
常量池:存放編譯器生成的各種字面量和符號引用,是方法區的一部分。

樓主提到的Java棧,一般而言是指圖中的虛擬機棧,在代碼中的方法調用過程中,往往需要從一個方法跳轉到另一個方法,執行完再返回,那麼在跳轉之前需要在當前方法的基本信息壓入棧中保存再跳轉。

三、關於寄存器的問題

對於java最常用的虛擬機,sun公司提供的hotspot虛擬機,是基於棧的虛擬機;而對於android的虛擬機,則採用google提供的dalvik,art兩種虛擬機,在android 5.0以後便默認採用art虛擬機,這是基於寄存器的虛擬機。 樓主問的是jvm(即java vm),這是基於棧的虛擬機。那麼關於虛擬機棧,這塊內存的內容,我們再進一步詳細分析,如下圖:

可以看到,在虛擬機棧有一幀幀的 棧幀組成,而棧幀包含局部變數表,操作棧等子項,那麼線程在運行的時候,代碼在運行時,是通過程序計數器不斷執行下一條指令。真正指令運算等操作時通過控制操作棧的操作數入棧和出棧,將操作數在局部變數表和操作棧之間轉移。

⑼ 玉溪電腦培訓學校告訴你Java內存模型原理

這篇文章主要介紹模型產生的問題背景,解決的問題,處理思路,相關實現規則,環環相扣,希望讀者看完這篇文章後能對Java內存模型體系產生一個相對清晰的理解,知其然知其所以然。

內存模型產生背景

在介紹Java內存模型之前,java課程http://www.kmbdqn.cn/認為應該先了解一下物理計算機中的並發問題,理解這些問題可以搞清楚內存模型產生的背景。

物理機遇到的並發問題與虛擬機中的情況有不少相似之處,物理機的解決方案對虛擬機的實現有相當的參考意義。

物理機的並發問題

硬體的效率問題

計算機處理器處理絕大多數運行任務都不可能只靠處理器「計算」就能完成,處理器至少需要與內存交互,如讀取運算數據、存儲運算結果,這個I/O操作很難消除(無法僅靠寄存器完成所有運算任務)。

由於計算機的存儲設備與處理器的運算速度有幾個數量級的差距,為了避免處理器等待緩慢的內存完成讀寫操作,現代計算機系統通過加入一層讀寫速度盡可能接近處理器運算速度的高速緩存。

緩存作為內存和處理器之間的緩沖:將運算需要使用到的數據復制到緩存中,讓運算能快速運行,當運算結束後再從緩存同步回內存之中。

緩存一致性問題

基於高速緩存的存儲系統交互很好的解決了處理器與內存速度的矛盾,但是也為計算機系統帶來更高的復雜度,因為引入了一個新問題:緩存一致性。

在多處理器的系統中(或者單處理器多核的系統),每個處理器(每個核)都有自己的高速緩存,而它們有共享同一主內存(MainMemory)。

當多個處理器的運算任務都涉及同一塊主內存區域時,將可能導致各自的緩存數據不一致。

為此,需要各個處理器訪問緩存時都遵循一些協議,在讀寫時要根據協議進行操作,來維護緩存的一致性。


⑽ C中指針變數何時需要初始化malloc

首先你要明白什麼是指針,指針是用來操作內存的。那麼指針又如何操作內存呢?在C語言里可以定義指針變數,這個指針變數里可以存儲內存的地址,一個32位的無符號整型值。它就像普通的int, double型變數一樣。以下面為例說明:
int iMax = 1;
int * pMax = NULL;
我們定義了一個int型的變數iMax 和一個int型的指針變數pMax,並對他們進行了初始化。這里iMax的值為1; pMax的值為NULL,也就是一個無符號整形0。注意NULL是一個宏,代表0。現在pMax的值為NULL,一般來講0也是一塊內存地址, 我們也可以操作。注意這個NULL你現在認為他表示無效即可。用來給指針進行初始化。其它的先不用管。
現在我們來使用pMax, 如果要使用pMax, 那麼就要對pMax賦值,使它指向一塊內存。我們這里定義指針的類型為一個指向一個int型值的指針變數。所以可以將iMax的地址賦值給pMax。注意,不管什麼樣的類型的指針變數,其值都是一個unsigned int型的值,表示內存的地址。理解這一點很重要。為什麼需要定義指針所指向的類型呢。如 char *, int * , double *型的指針,原因是我們使用指針是為了操作存儲在內存中的特定類型的值。如果沒有定義指針的類型,那我們在操作內存時,只能一個位元組, 一個位元組使用。這樣的指針沒有什麼意義,也許你還不太理解。但多應用就能明白這一點。
現在我們來給pMax賦值,然後操作它.
pMax = &iMax;
好了,可以使用pMax了。就像使用iMax一樣用它。不過你得在它前面加個指針運算符'*';
*pMax = *pMax + 2;
現在的pMax指針變數中存儲的是iMax變數地址的值。對*pMax操作, 就是對iMax操作。現在*pMax = 3 , iMax = 3;
如果說我臨時需要一塊內存,這塊內存用來存儲n個int的變數。我就需要使用malloc為pMax分配一塊內存。可以這樣做:
pMax = malloc(sizeof(int) * n);
if (pMax == NULL) // 錯誤處理
{ TODO...}
這樣我們就為pMax分配了一塊內存大小為sizeof(int) *n 位元組的內存。這里malloc返回一個指向這塊內存的首地址並將它賦給了int型指針變數pMax.
好了,pMax已經可以使用了。我們需要對它進行初始化。這個可以使用memset函數
memset(pMax, 0 , sizeof(int) * n);
現在就可以像數組一樣操作這塊int型的內存了。
pMax[0] = iMax;
pMax[1] = iMax + 1;
pMax[2] = pMax[0];
...
總的來說,指針非常靈活。因為它可以直接操作內存。這就會使指針這個東西很不容易控制。
像你說的p = temp ;將數組的首地址賦值給p , 這樣只是為了更容易操作字元串。temp也表示字元串的首地址, 但他是一個不可改變的量,即不能對temp賦值,它是只讀的。指針p就不同了,他可以進行一些數學運算。
對一個程序來講,如果你臨時需要一塊內存來存儲數據,你可以使用malloc, 但記得要free。否則容易造成內存泄露。
就這些吧。 希望對你有用。寫這么多也不容易,給點分吧。^_^