Ytl's Java Blog

          厚積而薄發(fā)---每一天都是一個(gè)全新的開(kāi)始

          2011年8月7日

               摘要: 歐幾里德算法  閱讀全文

          posted @ 2013-03-21 09:39 ytl 閱讀(321) | 評(píng)論 (0)編輯 收藏


          淺談java內(nèi)存模型 

                 不同的平臺(tái),內(nèi)存模型是不一樣的,但是jvm的內(nèi)存模型規(guī)范是統(tǒng)一的。其實(shí)java的多線(xiàn)程并發(fā)問(wèn)題最終都會(huì)反映在java的內(nèi)存模型上,所謂線(xiàn)程安全無(wú) 非是要控制多個(gè)線(xiàn)程對(duì)某個(gè)資源的有序訪(fǎng)問(wèn)或修改。總結(jié)java的內(nèi)存模型,要解決兩個(gè)主要的問(wèn)題:可見(jiàn)性和有序性。我們都知道計(jì)算機(jī)有高速緩存的存在,處 理器并不是每次處理數(shù)據(jù)都是取內(nèi)存的。JVM定義了自己的內(nèi)存模型,屏蔽了底層平臺(tái)內(nèi)存管理細(xì)節(jié),對(duì)于java開(kāi)發(fā)人員,要清楚在jvm內(nèi)存模型的基礎(chǔ) 上,如果解決多線(xiàn)程的可見(jiàn)性和有序性。
                 那么,何謂可見(jiàn)性? 多個(gè)線(xiàn)程之間是不能互相傳遞數(shù)據(jù)通信的,它們之間的溝通只能通過(guò)共享變量來(lái)進(jìn)行。Java內(nèi)存模型(JMM)規(guī)定了jvm有主內(nèi)存,主內(nèi)存是多個(gè)線(xiàn)程共享 的。當(dāng)new一個(gè)對(duì)象的時(shí)候,也是被分配在主內(nèi)存中,每個(gè)線(xiàn)程都有自己的工作內(nèi)存,工作內(nèi)存存儲(chǔ)了主存的某些對(duì)象的副本,當(dāng)然線(xiàn)程的工作內(nèi)存大小是有限制 的。當(dāng)線(xiàn)程操作某個(gè)對(duì)象時(shí),執(zhí)行順序如下:
           (1) 從主存復(fù)制變量到當(dāng)前工作內(nèi)存 (read and load)
           (2) 執(zhí)行代碼,改變共享變量值 (use and assign)
           (3) 用工作內(nèi)存數(shù)據(jù)刷新主存相關(guān)內(nèi)容 (store and write)

          JVM規(guī)范定義了線(xiàn)程對(duì)主存的操作指 令:read,load,use,assign,store,write。當(dāng)一個(gè)共享變量在多個(gè)線(xiàn)程的工作內(nèi)存中都有副本時(shí),如果一個(gè)線(xiàn)程修改了這個(gè)共享 變量,那么其他線(xiàn)程應(yīng)該能夠看到這個(gè)被修改后的值,這就是多線(xiàn)程的可見(jiàn)性問(wèn)題。
                  那么,什么是有序性呢 ?線(xiàn)程在引用變量時(shí)不能直接從主內(nèi)存中引用,如果線(xiàn)程工作內(nèi)存中沒(méi)有該變量,則會(huì)從主內(nèi)存中拷貝一個(gè)副本到工作內(nèi)存中,這個(gè)過(guò)程為read-load,完 成后線(xiàn)程會(huì)引用該副本。當(dāng)同一線(xiàn)程再度引用該字段時(shí),有可能重新從主存中獲取變量副本(read-load-use),也有可能直接引用原來(lái)的副本 (use),也就是說(shuō) read,load,use順序可以由JVM實(shí)現(xiàn)系統(tǒng)決定。
                  線(xiàn)程不能直接為主存中中字段賦值,它會(huì)將值指定給工作內(nèi)存中的變量副本(assign),完成后這個(gè)變量副本會(huì)同步到主存儲(chǔ)區(qū)(store- write),至于何時(shí)同步過(guò)去,根據(jù)JVM實(shí)現(xiàn)系統(tǒng)決定.有該字段,則會(huì)從主內(nèi)存中將該字段賦值到工作內(nèi)存中,這個(gè)過(guò)程為read-load,完成后線(xiàn) 程會(huì)引用該變量副本,當(dāng)同一線(xiàn)程多次重復(fù)對(duì)字段賦值時(shí),比如:
          Java代碼 
          for(int i=0;i<10;i++)   
           a++;  
          線(xiàn)程有可能只對(duì)工作內(nèi)存中的副本進(jìn)行賦值,只到最后一次賦值后才同步到主存儲(chǔ)區(qū),所以assign,store,weite順序可以由JVM實(shí)現(xiàn)系統(tǒng)決 定。假設(shè)有一個(gè)共享變量x,線(xiàn)程a執(zhí)行x=x+1。從上面的描述中可以知道x=x+1并不是一個(gè)原子操作,它的執(zhí)行過(guò)程如下:
          1 從主存中讀取變量x副本到工作內(nèi)存
          2 給x加1
          3 將x加1后的值寫(xiě)回主 存
          如果另外一個(gè)線(xiàn)程b執(zhí)行x=x-1,執(zhí)行過(guò)程如下:
          1 從主存中讀取變量x副本到工作內(nèi)存
          2 給x減1
          3 將x減1后的值寫(xiě)回主存 
          那么顯然,最終的x的值是不可靠的。假設(shè)x現(xiàn)在為10,線(xiàn)程a加1,線(xiàn)程b減1,從表面上看,似乎最終x還是為10,但是多線(xiàn)程情況下會(huì)有這種情況發(fā)生:
          1:線(xiàn)程a從主存讀取x副本到工作內(nèi)存,工作內(nèi)存中x值為10
          2:線(xiàn)程b從主存讀取x副本到工作內(nèi)存,工作內(nèi)存中x值為10
          3:線(xiàn)程a將工作內(nèi)存中x加1,工作內(nèi)存中x值為11
          4:線(xiàn)程a將x提交主存中,主存中x為11
          5:線(xiàn)程b將工作內(nèi)存中x值減1,工作內(nèi)存中x值為9
          6:線(xiàn)程b將x提交到中主存中,主存中x為

           

          jvm的內(nèi)存模型之eden區(qū)

          所謂線(xiàn)程的“工作內(nèi)存”到底是個(gè)什么東西?有的人認(rèn)為是線(xiàn)程的棧,其實(shí)這種理解是不正確的。看看JLS(java語(yǔ)言規(guī)范)對(duì)線(xiàn)程工作 內(nèi)存的描述,線(xiàn)程的working memory只是cpu的寄存器和高速緩存的抽象描述

                可能 很多人都覺(jué)得莫名其妙,說(shuō)JVM的內(nèi)存模型,怎么會(huì)扯到cpu上去呢?在此,我認(rèn)為很有必要闡述下,免 得很多人看得不明不白的。先拋開(kāi)java虛擬機(jī)不談,我們都知道,現(xiàn)在的計(jì)算機(jī),cpu在計(jì)算的時(shí)候,并不總是從內(nèi)存讀取數(shù)據(jù),它的數(shù)據(jù)讀取順序優(yōu)先級(jí) 是:寄存器-高速緩存-內(nèi)存。線(xiàn)程耗費(fèi)的是CPU,線(xiàn)程計(jì)算的時(shí)候,原始的數(shù)據(jù)來(lái)自?xún)?nèi)存,在計(jì)算過(guò)程中,有些數(shù)據(jù)可能被頻繁讀取,這些數(shù)據(jù)被存儲(chǔ)在寄存器 和高速緩存中,當(dāng)線(xiàn)程計(jì)算完后,這些緩存的數(shù)據(jù)在適當(dāng)?shù)臅r(shí)候應(yīng)該寫(xiě)回內(nèi)存。當(dāng)個(gè)多個(gè)線(xiàn)程同時(shí)讀寫(xiě)某個(gè)內(nèi)存數(shù)據(jù)時(shí),就會(huì)產(chǎn)生多線(xiàn)程并發(fā)問(wèn)題,涉及到三個(gè)特 性:原子性,有序性,可見(jiàn)性。在《線(xiàn)程安全總結(jié)》這篇文章中,為了理解方便,我把原子性和有序性統(tǒng)一叫做“多線(xiàn)程執(zhí)行有序性”。支持多線(xiàn)程的平臺(tái)都會(huì)面臨 這種問(wèn)題,運(yùn)行在多線(xiàn)程平臺(tái)上支持多線(xiàn)程的語(yǔ)言應(yīng)該提供解決該問(wèn)題的方案。

                synchronized, volatile,鎖機(jī)制(如同步塊,就緒隊(duì) 列,阻塞隊(duì)列)等等。這些方案只是語(yǔ)法層面的,但我們要從本質(zhì)上去理解它,不能僅僅知道一個(gè) synchronized 可以保證同步就完了。   在這里我說(shuō)的是jvm的內(nèi)存模型,是動(dòng)態(tài)的,面向多線(xiàn)程并發(fā)的,沿襲JSL的“working memory”的說(shuō)法,只是不想牽扯到太多底層細(xì)節(jié),因?yàn)?《線(xiàn)程安全總結(jié)》這篇文章意在說(shuō)明怎樣從語(yǔ)法層面去理解java的線(xiàn)程同步,知道各個(gè)關(guān)鍵字的使用場(chǎng) 景。

          說(shuō)說(shuō)JVM的eden區(qū)吧。JVM的內(nèi)存,被劃分了很多的區(qū)域:

          1.程序計(jì)數(shù)器
          每一個(gè)Java線(xiàn)程都有一個(gè)程序計(jì)數(shù)器來(lái)用于保存程序執(zhí)行到當(dāng)前方法的哪一個(gè)指令。
          2.線(xiàn)程棧
          線(xiàn)程的每個(gè)方法被執(zhí)行的時(shí)候,都會(huì)同時(shí)創(chuàng)建一個(gè)幀(Frame)用于存儲(chǔ)本地變量表、操作棧、動(dòng)態(tài)鏈接、方法出入口等信息。每一個(gè)方法的調(diào)用至完成,就意味著一個(gè)幀在VM棧中的入棧至出棧的過(guò)程。如果線(xiàn)程請(qǐng)求的棧深度大于虛擬機(jī)所允許的深度,將拋出StackOverflowError異常;如果VM棧可以動(dòng)態(tài)擴(kuò)展(VM Spec中允許固定長(zhǎng)度的VM棧),當(dāng)擴(kuò)展時(shí)無(wú)法申請(qǐng)到足夠內(nèi)存則拋出OutOfMemoryError異常。
          3.本地方法棧
          4.堆
          每個(gè)線(xiàn)程的棧都是該線(xiàn)程私有的,堆則是所有線(xiàn)程共享的。當(dāng)我們new一個(gè)對(duì)象時(shí),該對(duì)象就被分配到了堆中。但是堆,并不是一個(gè)簡(jiǎn)單的概念,堆區(qū)又劃分了很多區(qū)域,為什么堆劃分成這么多區(qū)域,這是為了JVM的內(nèi)存垃圾收集,似乎越扯越遠(yuǎn)了,扯到垃圾收集了,現(xiàn)在的jvm的gc都是按代收集,堆區(qū)大致被分為三大塊:新生代,舊生代,持久代(虛擬的);新生代又分為eden區(qū),s0區(qū),s1區(qū)。新建一個(gè)對(duì)象時(shí),基本小的對(duì)象,生命周期短的對(duì)象都會(huì)放在新生代的eden區(qū)中,eden區(qū)滿(mǎn)時(shí),有一個(gè)小范圍的gc(minor gc),整個(gè)新生代滿(mǎn)時(shí),會(huì)有一個(gè)大范圍的gc(major gc),將新生代里的部分對(duì)象轉(zhuǎn)到舊生代里。
          5.方法區(qū) 
          其實(shí)就是永久代(Permanent Generation),方法區(qū)中存放了每個(gè)Class的結(jié)構(gòu)信息,包括常量池、字段描述、方法描述等等。VM Space描述中對(duì)這個(gè)區(qū)域的限制非常寬松,除了和Java堆一樣不需要連續(xù)的內(nèi)存,也可以選擇固定大小或者可擴(kuò)展外,甚至可以選擇不實(shí)現(xiàn)垃圾收集。相對(duì)來(lái)說(shuō),垃圾收集行為在這個(gè)區(qū)域是相對(duì)比較少發(fā)生的,但并不是某些描述那樣永久代不會(huì)發(fā)生GC(至 少對(duì)當(dāng)前主流的商業(yè)JVM實(shí)現(xiàn)來(lái)說(shuō)是如此),這里的GC主要是對(duì)常量池的回收和對(duì)類(lèi)的卸載,雖然回收的“成績(jī)”一般也比較差強(qiáng)人意,尤其是類(lèi)卸載,條件相當(dāng)苛刻。
          6.常量池
           Class文件中除了有類(lèi)的版本、字段、方法、接口等描述等信息外,還有一項(xiàng)信息是常量表(constant_pool table),用于存放編譯期已可知的常量,這部分內(nèi)容將在類(lèi)加載后進(jìn)入方法區(qū)(永久代)存放。但是Java語(yǔ)言并不要求常量一定只有編譯期預(yù)置入Class的常量表的內(nèi)容才能進(jìn)入方法區(qū)常量池,運(yùn)行期間也可將新內(nèi)容放入常量池(最典型的String.intern()方法)。

          posted @ 2012-03-01 18:12 ytl 閱讀(991) | 評(píng)論 (0)編輯 收藏

          ArrayList
                 關(guān)于Java中的transient,volatile和strictfp關(guān)鍵字 http://www.iteye.com/topic/52957
                 (1), ArrayList底層使用Object數(shù)據(jù)實(shí)現(xiàn), private transient Object[] elementData;且在使用不帶參數(shù)的方式實(shí)例化時(shí),生成數(shù)組默認(rèn)的長(zhǎng)度是10。
                (2),  add方法實(shí)現(xiàn)
                public boolean add(E e) {
                     //ensureCapacityInternal判斷添加新元素是否需要重新擴(kuò)大數(shù)組的長(zhǎng)度,需要?jiǎng)t擴(kuò)否則不
                    ensureCapacityInternal(size + 1);  // 此為JDK7調(diào)用的方法 JDK5里面使用的ensureCapacity方法
                    elementData[size++] = e; //把對(duì)象插入數(shù)組,同時(shí)把數(shù)組存儲(chǔ)的數(shù)據(jù)長(zhǎng)度size加1
                    return true;
                }
               JDK 7中 ensureCapacityInternal實(shí)現(xiàn)
             private void ensureCapacityInternal(int minCapacity) {
                  modCount++;修改次數(shù)
                  // overflow-conscious code
                  if (minCapacity - elementData.length > 0)
                      grow(minCapacity);//如果需要擴(kuò)大數(shù)組長(zhǎng)度
              }
          /**
               * The maximum size of array to allocate. --申請(qǐng)新數(shù)組最大長(zhǎng)度
               * Some VMs reserve some header words in an array.
               * Attempts to allocate larger arrays may result in
               * OutOfMemoryError: Requested array size exceeds VM limit  --如果申請(qǐng)的數(shù)組占用的內(nèi)心大于JVM的限制拋出異常
               */
              private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;//為什么減去8看注釋第2行
              /**
               * Increases the capacity to ensure that it can hold at least the
               * number of elements specified by the minimum capacity argument.
               *
               * @param minCapacity the desired minimum capacity
               */
              private void grow(int minCapacity) {
                  // overflow-conscious code
                  int oldCapacity = elementData.length;
                  int newCapacity = oldCapacity + (oldCapacity >> 1); //新申請(qǐng)的長(zhǎng)度為old的3/2倍同時(shí)使用位移運(yùn)算更高效,JDK5中: (oldCapacity *3)/2+1
                  if (newCapacity - minCapacity < 0)  
                      newCapacity = minCapacity; 
                  if (newCapacity - MAX_ARRAY_SIZE > 0) //你懂的
                      newCapacity = hugeCapacity(minCapacity);
                  // minCapacity is usually close to size, so this is a win:
                  elementData = Arrays.copyOf(elementData, newCapacity);
              }
           //可以申請(qǐng)的最大長(zhǎng)度
              private static int hugeCapacity(int minCapacity) { 
                  if (minCapacity < 0) // overflow
                      throw new OutOfMemoryError();
                  return (minCapacity > MAX_ARRAY_SIZE) ?
                      Integer.MAX_VALUE :
                      MAX_ARRAY_SIZE;
              }



          posted @ 2011-09-24 15:30 ytl| 編輯 收藏

               摘要:   閱讀全文

          posted @ 2011-08-07 11:02 ytl| 編輯 收藏

          主站蜘蛛池模板: 泰顺县| 洞头县| 福安市| 儋州市| 韶关市| 沙河市| 柘荣县| 中西区| 甘洛县| 佳木斯市| 静海县| 安吉县| 宁武县| 晋城| 浦东新区| 新沂市| 吴桥县| 襄汾县| 渭源县| 山阳县| 岗巴县| 平果县| 上虞市| 永年县| 沅陵县| 白城市| 龙里县| 汝阳县| 黄陵县| 威海市| 麟游县| 齐河县| 龙里县| 武川县| 南乐县| 革吉县| 库车县| 商南县| 旅游| 浦东新区| 南城县|