萬點五實現(xiàn)1

  • java基礎volidate、線程生命周期、反射、NIO

  • 內(nèi)存分區(qū)GC、類加載

  • 強弱等引用

  • 基本數(shù)據(jù)結構

  • 線程池

  • 安卓架構

  • 系統(tǒng)機制(啟動原理、安裝原理等)

安全

  • 框架層(SystemServer、binder、PMS、AMS、WMS)

  • 內(nèi)存、網(wǎng)絡、電量等優(yōu)化

  • 卡頓解決

  • 應用常駐、防強殺

  • 系統(tǒng)管理功能,(后臺任務、內(nèi)存加速、通知欄管理、聯(lián)網(wǎng)管理等)

著重

  • java內(nèi)存

  • 架構

  • 系統(tǒng)機制(啟動原理、安裝原理等)

  • 各種優(yōu)化

  • 應用常駐、防強殺

類加載過程

啟動一個Java程序時,會通過Javac編譯程序調(diào)用Java啟動JVM(編譯過程沒有深入研究)類加載過程其實就是JVM把class文件加載到內(nèi)存,并對數(shù)據(jù)進行校驗解析和初始化,最終形成JVM可以直接使用Java的過程??偣部梢苑譃榧虞d、鏈接、初始化三部。

  • 加載將class文件字節(jié)碼內(nèi)容加載到內(nèi)存中,并將這些靜態(tài)數(shù)據(jù)轉換成方法區(qū)中的運行時2進制數(shù)據(jù)結構。在堆中生成一個代表這個類的Java.lang.Class對象,作為方法區(qū)類數(shù)據(jù)的訪問入口。
  • 鏈接將Java類的二進制代碼合并到JVM的運行狀態(tài)之中的過程,鏈接有分為三個小步1、驗證確保加載的類的信息符合JVM規(guī)范,有沒有安全方面的問題2、準備正式為類變量(static變量)分配內(nèi)存并設置類變量初始值的階段,這些內(nèi)存都將在方法去中分配3、解析虛擬機常量池內(nèi)的符號引用替換為直接引用的過程
  • 初始化初始化階段是執(zhí)行類構造器<clinit>()方法的過程,類構造器<clinit>()方法是由編譯器自動收集類中的所有類變量的賦值動作和靜態(tài)語句塊(static塊)中的語句合并產(chǎn)生的。當初始化一個類的時候,如果發(fā)現(xiàn)其父類還沒有進行過初始化,則需要先對其父類初始化虛擬機會保證一個類的<clinit>()方法在多線程環(huán)境中被正確的加鎖和同步當訪問一個類的靜態(tài)域時只有真正聲明這個域的類才會被初始化。

引用

  • 強引用(Strong Reference):Object obj = new Object();只要強引用還存在,GC永遠不會回收掉被引用的對象。
  • 軟引用(Soft Reference):描述一些還有用但非必需的對象。在系統(tǒng)將會發(fā)生內(nèi)存溢出之前,會把這些對象列入回收范圍進行二次回收(即系統(tǒng)將會發(fā)生內(nèi)存溢出了,才會對他們進行回收。)
  • 弱引用(Weak Reference):程度比軟引用還要弱一些。這些對象只能生存到下次GC之前。當GC工作時,無論內(nèi)存是否足夠都會將其回收(即只要進行GC,就會對他們進行回收。)
  • 虛引用(Phantom Reference):一個對象是否存在虛引用,完全不會對其生存時間構成影響。

方法區(qū)的GC

關于方法區(qū)中需要回收的是一些廢棄的常量無用的類。

  1. 廢棄的常量的回收。這里看引用計數(shù)就可以了。沒有對象引用該常量就可以放心的回收了。
  1. 無用的類的回收。什么是無用的類呢?
  • 該類所有的實例都已經(jīng)被回收。也就是Java堆中不存在該類的任何實例;
  • 加載該類的ClassLoader已經(jīng)被回收;
  • 該類對應的java.lang.Class對象沒有任何地方被引用,無法在任何地方通過反射訪問該類的方法。

總而言之,對于堆中的對象,主要用可達性分析判斷一個對象是否還存在引用,如果該對象沒有任何引用就應該被回收。而根據(jù)我們實際對引用的不同需求,又分成了4中引用,每種引用的回收機制也是不同的。對于方法區(qū)中的常量和類,當一個常量沒有任何對象引用它,它就可以被回收了。而對于類,如果可以判定它為無用類,就可以被回收了。

How? -- 如何回收?

標記-清除(Mark-Sweep)算法

分為兩個階段:首先標記出所有需要回收的對象,在標記完成后統(tǒng)一回收所有被標記的對象。缺點:效率問題,標記和清除兩個過程的效率都不高;空間問題,會產(chǎn)生很多碎片。

復制算法

將可用內(nèi)存按容量劃分為大小相等的兩塊,每次只用其中一塊。當這一塊用完了,就將還存活的對象復制到另外一塊上面,然后把原始空間全部回收。高效、簡單。缺點:將內(nèi)存縮小為原來的一半。

標記-整理(Mark-Compat)算法

標記過程與標記-清除算法過程一樣,但后面不是簡單的清除,而是讓所有存活的對象都向一端移動,然后直接清除掉端邊界以外的內(nèi)存。

分代收集(Generational Collection)算法

  • 新生代中,每次垃圾收集時都有大批對象死去,只有少量存活,就選用復制算法,只需要付出少量存活對象的復制成本就可以完成收集;

  • 老年代中,其存活率較高、沒有額外空間對它進行分配擔保,就應該使用“標記-整理”或“標記-清理”算法進行回收。


內(nèi)存分配

Java技術體系中所提倡的自動內(nèi)存管理最終可以歸結為自動化的解決2個問題:給對象分配內(nèi)存以及回收分配給對象的內(nèi)存。

對象優(yōu)先在Eden分配

大多數(shù)情況下,對象在新生代Eden區(qū)分配。當Eden區(qū)沒有足夠的內(nèi)存時,虛擬機將發(fā)起一次Minor GC。

  • Minor GC(新生代GC):指發(fā)生在新生代的垃圾收集動作,因為Java對象大多都具備朝生夕滅的特性,所以Minor GC發(fā)生的非常頻繁。

  • Full GC/Major GC(老年代GC):指發(fā)生在老年代的GC,出現(xiàn)了Major GC,經(jīng)常會伴隨至少一次的Minor GC。

大對象直接進老年代

大對象是指需要大量連續(xù)內(nèi)存空間的Java對象(例如很長的字符串以及數(shù)組)。

長期存活的對象將進入老年代

JVM為每個對象定義一個對象年齡計數(shù)器。

  • 如果對象在Eden出生并經(jīng)歷過第一次Minor GC后仍然存活,并且能夠被Survivor容納,則應該被移動到Survivor空間中,并且年齡對象設置為1;

  • 對象在Survivor區(qū)中每熬過一次Minor GC,年齡就會增加1歲,當它的年齡增加到一定程度(默認為15歲,可通過參數(shù)-XX:MaxTenuringThreshold設置),就會被晉升到老年代中。

  • 要注意的是:JVM并不是永遠的要求對象的年齡必須達到MaxTenuringThreshold才能晉升老年代,如果在Survivor空間中相同年齡所有對象大小的總和大于Survivor空間的一般,年齡大于等于該年齡的對象就可以直接進入老年代,無需等到MaxTenuringThreshold中要求的年齡。

空間分配擔保

  • 在發(fā)生Minor GC之前,虛擬機會先檢查老年代最大可用的連續(xù)空間是否大于新生代所有對象總空間,如果這個條件成立,則進行Minor GC是安全的;

  • 如果不成立,則虛擬機會查看HandlePromotionFailure設置值是否允許擔保失敗。如果允許,則急促檢查老年代最大可用連續(xù)空間是否大于歷次晉升到老年代對象的平均大小,如果大于,將嘗試著進行一次Minor GC,盡管它是有風險的;

  • 如果小于或者HandePromotionFailure設置為不允許冒險,則這時要改為進行一次Full GC.


Minor GC ,F(xiàn)ull GC 觸發(fā)條件

Minor GC觸發(fā)條件:當Eden區(qū)滿時,觸發(fā)Minor GC。

Full GC觸發(fā)條件:

(1)調(diào)用System.gc時,系統(tǒng)建議執(zhí)行Full GC,但是不必然執(zhí)行

(2)老年代空間不足

(3)方法區(qū)空間不足

(4)通過Minor GC后進入老年代的平均大小大于老年代的可用內(nèi)存

(5)由Eden區(qū)、From Space區(qū)向To Space區(qū)復制時,對象大小大于To Space可用內(nèi)存,則把該對象轉存到老年代,且老年代的可用內(nèi)存小于該對象大小


JVM主要包括四個部分:

image

1.類加載器(ClassLoader):在JVM啟動時或者在類運行時將需要的class加載到JVM中。(右圖表示了從java源文件到JVM的整個過程,可配合理解。 關于類的加載機制,可以參考http://blog.csdn.net/tonytfjing/article/details/47212291

2.執(zhí)行引擎:負責執(zhí)行class文件中包含的字節(jié)碼指令(執(zhí)行引擎的工作機制,這里也不細說了,這里主要介紹JVM結構);

3.內(nèi)存區(qū)(也叫運行時數(shù)據(jù)區(qū)):是在JVM運行的時候操作所分配的內(nèi)存區(qū)。運行時內(nèi)存區(qū)主要可以劃分為5個區(qū)域,如圖:

image

4.本地方法接口:

主要是調(diào)用C或C++實現(xiàn)的本地方法及返回結果。


垃圾檢測、回收算法

垃圾收集器一般必須完成兩件事:檢測出垃圾;回收垃圾。怎么檢測出垃圾?一般有以下幾種方法:

引用計數(shù)法:給一個對象添加引用計數(shù)器,每當有個地方引用它,計數(shù)器就加1;引用失效就減1。

好了,問題來了,如果我有兩個對象A和B,互相引用,除此之外,沒有其他任何對象引用它們,實際上這兩個對象已經(jīng)無法訪問,即是我們說的垃圾對象。但是互相引用,計數(shù)不為0,導致無法回收,所以還有另一種方法:

可達性分析算法:以根集對象為起始點進行搜索,如果有對象不可達的話,即是垃圾對象。這里的根集一般包括java棧中引用的對象、方法區(qū)常良池中引用的對象

本地方法中引用的對象等。

總之,JVM在做垃圾回收的時候,會檢查堆中的所有對象是否會被這些根集對象引用,不能夠被引用的對象就會被垃圾收集器回收。一般回收算法也有如下幾種:

1.標記-清除(Mark-sweep)

算法和名字一樣,分為兩個階段:標記和清除。標記所有需要回收的對象,然后統(tǒng)一回收。這是最基礎的算法,后續(xù)的收集算法都是基于這個算法擴展的。

不足:效率低;標記清除之后會產(chǎn)生大量碎片。效果圖如下:

image

2.復制(Copying)

此算法把內(nèi)存空間劃為兩個相等的區(qū)域,每次只使用其中一個區(qū)域。垃圾回收時,遍歷當前使用區(qū)域,把正在使用中的對象復制到另外一個區(qū)域中。此算法每次只處理正在使用中的對象,因此復制成本比較小,同時復制過去以后還能進行相應的內(nèi)存整理,不會出現(xiàn)“碎片”問題。當然,此算法的缺點也是很明顯的,就是需要兩倍內(nèi)存空間。效果圖如下:

image

3.標記-整理(Mark-Compact)

此算法結合了“標記-清除”和“復制”兩個算法的優(yōu)點。也是分兩階段,第一階段從根節(jié)點開始標記所有被引用對象,第二階段遍歷整個堆,把清除未標記對象并且把存活對象“壓縮”到堆的其中一塊,按順序排放。此算法避免了“標記-清除”的碎片問題,同時也避免了“復制”算法的空間問題。效果圖如下:

image

(1,2,3 圖文摘自 http://pengjiaheng.iteye.com/blog/520228,感謝原作者。)

4.分代收集算法

這是當前商業(yè)虛擬機常用的垃圾收集算法。分代的垃圾回收策略,是基于這樣一個事實:不同的對象的生命周期是不一樣的。因此,不同生命周期的對象可以采取不同的收集方式,以便提高回收效率。

為什么要運用分代垃圾回收策略?在java程序運行的過程中,會產(chǎn)生大量的對象,因每個對象所能承擔的職責不同所具有的功能不同所以也有著不一樣的生命周期,有的對象生命周期較長,比如Http請求中的Session對象,線程,Socket連接等;有的對象生命周期較短,比如String對象,由于其不變類的特性,有的在使用一次后即可回收。試想,在不進行對象存活時間區(qū)分的情況下,每次垃圾回收都是對整個堆空間進行回收,那么消耗的時間相對會很長,而且對于存活時間較長的對象進行的掃描工作等都是徒勞。因此就需要引入分治的思想,所謂分治的思想就是因地制宜,將對象進行代的劃分,把不同生命周期的對象放在不同的代上使用不同的垃圾回收方式。

如何劃分?將對象按其生命周期的不同劃分成:年輕代(Young Generation)、年老代(Old Generation)、持久代(Permanent Generation)。其中持久代主要存放的是類信息,所以與java對象的回收關系不大,與回收息息相關的是年輕代和年老代。這里有個比喻很形象

“假設你是一個普通的 Java 對象,你出生在 Eden 區(qū),在 Eden 區(qū)有許多和你差不多的小兄弟、小姐妹,可以把 Eden 區(qū)當成幼兒園,在這個幼兒園里大家玩了很長時間。Eden 區(qū)不能無休止地放你們在里面,所以當年紀稍大,你就要被送到學校去上學,這里假設從小學到高中都稱為 Survivor 區(qū)。開始的時候你在 Survivor 區(qū)里面劃分出來的的“From”區(qū),讀到高年級了,就進了 Survivor 區(qū)的“To”區(qū),中間由于學習成績不穩(wěn)定,還經(jīng)常來回折騰。直到你 18 歲的時候,高中畢業(yè)了,該去社會上闖闖了。于是你就去了年老代,年老代里面人也很多。在年老代里,你生活了 20 年 (每次 GC 加一歲),最后壽終正寢,被 GC 回收。有一點沒有提,你在年老代遇到了一個同學,他的名字叫愛德華 (慕光之城里的帥哥吸血鬼),他以及他的家族永遠不會死,那么他們就生活在永生代?!?/p>

具體區(qū)域可以通過VisualVM中的VisaulGC插件查看,如圖(openjdk 1.7):

image
image

年輕代:是所有新對象產(chǎn)生的地方。年輕代被分為3個部分——Enden區(qū)和兩個Survivor區(qū)(From和to)當Eden區(qū)被對象填滿時,就會執(zhí)行Minor GC。并把所有存活下來的對象轉移到其中一個survivor區(qū)(假設為from區(qū))。Minor GC同樣會檢查存活下來的對象,并把它們轉移到另一個survivor區(qū)(假設為to區(qū))。這樣在一段時間內(nèi),總會有一個空的survivor區(qū)。經(jīng)過多次GC周期后,仍然存活下來的對象會被轉移到年老代內(nèi)存空間。通常這是在年輕代有資格提升到年老代前通過設定年齡閾值來完成的。需要注意,Survivor的兩個區(qū)是對稱的,沒先后關系,from和to是相對的。

年老代:在年輕代中經(jīng)歷了N次回收后仍然沒有被清除的對象,就會被放到年老代中,可以說他們都是久經(jīng)沙場而不亡的一代,都是生命周期較長的對象。對于年老代和永久代,就不能再采用像年輕代中那樣搬移騰挪的回收算法,因為那些對于這些回收戰(zhàn)場上的老兵來說是小兒科。通常會在老年代內(nèi)存被占滿時將會觸發(fā)Full GC,回收整個堆內(nèi)存。

持久代:用于存放靜態(tài)文件,比如java類、方法等。持久代對垃圾回收沒有顯著的影響。

分代回收的效果圖如下:

image

我這里之所以最后講分代,是因為分代里涉及了前面幾種算法。年輕代:涉及了復制算法;年老代:涉及了“標記-整理(Mark-Sweep)”的算法。


觸發(fā)JVM進行Full GC幾種情況

堆內(nèi)存劃分為 Eden、Survivor 和 Tenured/Old 空間,如下圖所示:

image

從年輕代空間(包括 Eden 和 Survivor 區(qū)域)回收內(nèi)存被稱為 Minor GC,對老年代GC稱為Major GC,而Full GC是對整個堆來說的,在最近幾個版本的JDK里默認包括了對永生帶即方法區(qū)的回收(JDK8中無永生帶了),出現(xiàn)Full GC的時候經(jīng)常伴隨至少一次的Minor GC,但非絕對的。Major GC的速度一般會比Minor GC慢10倍以上。下邊看看有那種情況觸發(fā)JVM進行Full GC及應對策略。

1、System.gc()方法的調(diào)用

此方法的調(diào)用是建議JVM進行Full GC,雖然只是建議而非一定,但很多情況下它會觸發(fā) Full GC,從而增加Full GC的頻率,也即增加了間歇性停頓的次數(shù)。強烈影響系建議能不使用此方法就別使用,讓虛擬機自己去管理它的內(nèi)存,可通過通過-XX:+ DisableExplicitGC來禁止RMI調(diào)用System.gc。

2、老年代代空間不足

老年代空間只有在新生代對象轉入及創(chuàng)建為大對象、大數(shù)組時才會出現(xiàn)不足的現(xiàn)象,當執(zhí)行Full GC后空間仍然不足,則拋出如下錯誤:java.lang.OutOfMemoryError: Java heap space 為避免以上兩種狀況引起的Full GC,調(diào)優(yōu)時應盡量做到讓對象在Minor GC階段被回收、讓對象在新生代多存活一段時間及不要創(chuàng)建過大的對象及數(shù)組。

3、永生區(qū)空間不足

JVM規(guī)范中運行時數(shù)據(jù)區(qū)域中的方法區(qū),在HotSpot虛擬機中又被習慣稱為永生代或者永生區(qū),Permanet Generation中存放的為一些class的信息、常量、靜態(tài)變量等數(shù)據(jù),當系統(tǒng)中要加載的類、反射的類和調(diào)用的方法較多時,Permanet Generation可能會被占滿,在未配置為采用CMS GC的情況下也會執(zhí)行Full GC。如果經(jīng)過Full GC仍然回收不了,那么JVM會拋出如下錯誤信息:java.lang.OutOfMemoryError: PermGen space 為避免Perm Gen占滿造成Full GC現(xiàn)象,可采用的方法為增大Perm Gen空間或轉為使用CMS GC。

4、CMS GC時出現(xiàn)promotion failed和concurrent mode failure

對于采用CMS進行老年代GC的程序而言,尤其要注意GC日志中是否有promotion failed和concurrent mode failure兩種狀況,當這兩種狀況出現(xiàn)時可能

會觸發(fā)Full GC。promotion failed是在進行Minor GC時,survivor space放不下、對象只能放入老年代,而此時老年代也放不下造成的;concurrent mode failure是在

執(zhí)行CMS GC的過程中同時有對象要放入老年代,而此時老年代空間不足造成的(有時候“空間不足”是CMS GC時當前的浮動垃圾過多導致暫時性的空間不足觸發(fā)Full GC)。對措施為:增大survivor space、老年代空間或調(diào)低觸發(fā)并發(fā)GC的比率,但在JDK 5.0+、6.0+的版本中有可能會由于JDK的bug29導致CMS在remark完畢

后很久才觸發(fā)sweeping動作。對于這種狀況,可通過設置-XX: CMSMaxAbortablePrecleanTime=5(單位為ms)來避免。

5、統(tǒng)計得到的Minor GC晉升到舊生代的平均大小大于老年代的剩余空間

這是一個較為復雜的觸發(fā)情況,Hotspot為了避免由于新生代對象晉升到舊生代導致舊生代空間不足的現(xiàn)象,在進行Minor GC時,做了一個判斷,如果之

前統(tǒng)計所得到的Minor GC晉升到舊生代的平均大小大于舊生代的剩余空間,那么就直接觸發(fā)Full GC。例如程序第一次觸發(fā)Minor GC后,有6MB的對象晉升到舊生代,那么當下一次Minor GC發(fā)生時,首先檢查舊生代的剩余空間是否大于6MB,如果小于6MB,

則執(zhí)行Full GC。當新生代采用PS GC時,方式稍有不同,PS GC是在Minor GC后也會檢查,例如上面的例子中第一次Minor GC后,PS GC會檢查此時舊生代的剩余空間是否

大于6MB,如小于,則觸發(fā)對舊生代的回收。除了以上4種狀況外,對于使用RMI來進行RPC或管理的Sun JDK應用而言,默認情況下會一小時執(zhí)行一次Full GC??赏ㄟ^在啟動時通過- java -

Dsun.rmi.dgc.client.gcInterval=3600000來設置Full GC執(zhí)行的間隔時間或通過-XX:+ DisableExplicitGC來禁止RMI調(diào)用System.gc。

6、堆中分配很大的對象

所謂大對象,是指需要大量連續(xù)內(nèi)存空間的java對象,例如很長的數(shù)組,此種對象會直接進入老年代,而老年代雖然有很大的剩余空間,但是無法找到足夠大的連續(xù)空間來分配給當前對象,此種情況就會觸發(fā)JVM進行Full GC。

為了解決這個問題,CMS垃圾收集器提供了一個可配置的參數(shù),即-XX:+UseCMSCompactAtFullCollection開關參數(shù),用于在“享受”完Full GC服務之后額外免費贈送一個碎片整理的過程,內(nèi)存整理的過程無法并發(fā)的,空間碎片問題沒有了,但提頓時間不得不變長了,JVM設計者們還提供了另外一個參數(shù) -XX:CMSFullGCsBeforeCompaction,這個參數(shù)用于設置在執(zhí)行多少次不壓縮的Full GC后,跟著來一次帶壓縮的。


JAVA volidate

一、基本概念**

先補充一下概念:Java 內(nèi)存模型中的可見性、原子性和有序性。

可見性:

可見性是一種復雜的屬性,因為可見性中的錯誤總是會違背我們的直覺。通常,我們無法確保執(zhí)行讀操作的線程能適時地看到其他線程寫入的值,有時甚至是根本不可能的事情。為了確保多個線程之間對內(nèi)存寫入操作的可見性,必須使用同步機制。

可見性,是指線程之間的可見性,一個線程修改的狀態(tài)對另一個線程是可見的。也就是一個線程修改的結果。另一個線程馬上就能看到。比如:用volatile修飾的變量,就會具有可見性。volatile修飾的變量不允許線程內(nèi)部緩存和重排序,即直接修改內(nèi)存。所以對其他線程是可見的。但是這里需要注意一個問題,volatile只能讓被他修飾內(nèi)容具有可見性,但不能保證它具有原子性。比如 volatile int a = 0;之后有一個操作 a++;這個變量a具有可見性,但是a++ 依然是一個非原子操作,也就是這個操作同樣存在線程安全問題。

在 Java 中 volatile、synchronized 和 final 實現(xiàn)可見性。

原子性:

原子是世界上的最小單位,具有不可分割性。比如 a=0;(a非long和double類型) 這個操作是不可分割的,那么我們說這個操作時原子操作。再比如:a++; 這個操作實際是a = a + 1;是可分割的,所以他不是一個原子操作。非原子操作都會存在線程安全問題,需要我們使用同步技術(sychronized)來讓它變成一個原子操作。一個操作是原子操作,那么我們稱它具有原子性。java的concurrent包下提供了一些原子類,我們可以通過閱讀API來了解這些原子類的用法。比如:AtomicInteger、AtomicLong、AtomicReference等。

在 Java 中 synchronized 和在 lock、unlock 中操作保證原子性。

有序性:

  Java 語言提供了 volatile 和 synchronized 兩個關鍵字來保證線程之間操作的有序性,volatile 是因為其本身包含“禁止指令重排序”的語義,synchronized 是由“一個變量在同一個時刻只允許一條線程對其進行 lock 操作”這條規(guī)則獲得的,此規(guī)則決定了持有同一個對象鎖的兩個同步塊只能串行執(zhí)行。

下面內(nèi)容摘錄自《Java Concurrency in Practice》:

下面一段代碼在多線程環(huán)境下,將存在問題。

[
image

](javascript:void(0);) [+ View code](javascript:;)

<pre class="md-fences md-end-block" lang="java" contenteditable="false" cid="n371" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Monaco, Consolas, "Andale Mono", "DejaVu Sans Mono", monospace; font-size: 0.9rem; white-space: pre; margin-top: 0px; margin-bottom: 20px; background: var(--code-block-bg-color); display: block; break-inside: avoid; text-align: left; position: relative !important; padding: 10px 30px; border: 1px solid; width: inherit; color: rgb(184, 191, 198); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"> 1 /**
2 * @author zhengbinMac
3 */
4 public class NoVisibility {
5 private static boolean ready;
6 private static int number;
7 private static class ReaderThread extends Thread {
8 @Override
9 public void run() {
10 while(!ready) {
11 Thread.yield();
12 }
13 System.out.println(number);
14 }
15 }
16 public static void main(String[] args) {
17 new ReaderThread().start();
18 number = 42;
19 ready = true;
20 }
21 }</pre>

[
image

](javascript:void(0);)

NoVisibility可能會持續(xù)循環(huán)下去,因為讀線程可能永遠都看不到ready的值。甚至NoVisibility可能會輸出0,因為讀線程可能看到了寫入ready的值,但卻沒有看到之后寫入number的值,這種現(xiàn)象被稱為“重排序”。只要在某個線程中無法檢測到重排序情況(即使在其他線程中可以明顯地看到該線程中的重排序),那么就無法確保線程中的操作將按照程序中指定的順序來執(zhí)行。當主線程首先寫入number,然后在沒有同步的情況下寫入ready,那么讀線程看到的順序可能與寫入的順序完全相反。

在沒有同步的情況下,編譯器、處理器以及運行時等都可能對操作的執(zhí)行順序進行一些意想不到的調(diào)整。在缺乏足夠同步的多線程程序中,要想對內(nèi)存操作的執(zhí)行春旭進行判斷,無法得到正確的結論。

這個看上去像是一個失敗的設計,但卻能使JVM充分地利用現(xiàn)代多核處理器的強大性能。例如,在缺少同步的情況下,Java內(nèi)存模型允許編譯器對操作順序進行重排序,并將數(shù)值緩存在寄存器中。此外,它還允許CPU對操作順序進行重排序,并將數(shù)值緩存在處理器特定的緩存中。

二、Volatile原理


Java語言提供了一種稍弱的同步機制,即volatile變量,用來確保將變量的更新操作通知到其他線程。當把變量聲明為volatile類型后,編譯器與運行時都會注意到這個變量是共享的,因此不會將該變量上的操作與其他內(nèi)存操作一起重排序。volatile變量不會被緩存在寄存器或者對其他處理器不可見的地方,因此在讀取volatile類型的變量時總會返回最新寫入的值。

在訪問volatile變量時不會執(zhí)行加鎖操作,因此也就不會使執(zhí)行線程阻塞,因此volatile變量是一種比sychronized關鍵字更輕量級的同步機制。

image

當對非 volatile 變量進行讀寫的時候,每個線程先從內(nèi)存拷貝變量到CPU緩存中。如果計算機有多個CPU,每個線程可能在不同的CPU上被處理,這意味著每個線程可以拷貝到不同的 CPU cache 中。

而聲明變量是 volatile 的,JVM 保證了每次讀變量都從內(nèi)存中讀,跳過 CPU cache 這一步。

當一個變量定義為 volatile 之后,將具備兩種特性:

1.保證此變量對所有的線程的可見性,這里的“可見性”,如本文開頭所述,當一個線程修改了這個變量的值,volatile 保證了新值能立即同步到主內(nèi)存,以及每次使用前立即從主內(nèi)存刷新。但普通變量做不到這點,普通變量的值在線程間傳遞均需要通過主內(nèi)存(詳見:Java內(nèi)存模型)來完成。

2.禁止指令重排序優(yōu)化。有volatile修飾的變量,賦值后多執(zhí)行了一個“l(fā)oad addl $0x0, (%esp)”操作,這個操作相當于一個內(nèi)存屏障(指令重排序時不能把后面的指令重排序到內(nèi)存屏障之前的位置),只有一個CPU訪問內(nèi)存時,并不需要內(nèi)存屏障;(什么是指令重排序:是指CPU采用了允許將多條指令不按程序規(guī)定的順序分開發(fā)送給各相應電路單元處理)。

volatile 性能:

volatile 的讀性能消耗與普通變量幾乎相同,但是寫操作稍慢,因為它需要在本地代碼中插入許多內(nèi)存屏障指令來保證處理器不發(fā)生亂序執(zhí)行。


APP 架構設計原則

摘要:好的軟件設計必須能夠幫助開發(fā)者發(fā)展和擴充解決方案,保持代碼清晰健壯,并且可擴展,易于維護,而不必每件事都重寫代碼。面對軟件存在的問題,必須遵守SOLID原則,不要過度工程化,盡可能降低框架中模塊的依賴性。

*CSDN移動將持續(xù)為您優(yōu)選移動開發(fā)的精華內(nèi)容,共同探討移動開發(fā)的技術熱點話題,涵蓋移動應用、開發(fā)工具、移動游戲及引擎、智能硬件、物聯(lián)網(wǎng)等方方面面。如果您想投稿、參與內(nèi)容翻譯工作,或尋求近匠報道,請發(fā)送郵件至tangxy#csdn.net(請把#改成@)。 *

嘿!經(jīng)過一段時間收集了大量反饋意見后,我認為應該來說說這個話題了。我會在這里給出我認為構建現(xiàn)代移動應用(Android)的好方法,這會是另一番體味。

開始之前,假設你已經(jīng)閱讀過我之前撰寫的文章“ Architecting Android…The clean way?”。如果還沒有閱讀過,為了更好地理解這篇文章,應借此機會讀一讀:

image

架構演變

演變意味著一個循序漸進的過程,由某些狀態(tài)改變到另一種不同的狀態(tài),且新狀態(tài)通常更好或更復雜。

照這么一說,軟件是隨著時間發(fā)展和改變的,是架構上的發(fā)展和改變。實際上,好的軟件設計必須能夠幫助我們發(fā)展和擴充解決方案,保持其健壯性,而不必每件事都重寫代碼(雖然在某些情況下重寫的方法更好,但是那是另一篇文章的話題,所以相信我,讓我們聚焦在前面所討論的話題上)。

在這篇文章中,我將講解我認為是必需的和重要的要點,為了保持基本代碼條理清晰,要記住下面這張圖片,我們開始吧!

image

響應式方法:RxJava

因為已經(jīng)有很多這方面的文章,還有這方面做得很好、令人景仰的人,所以我不打算在這里討論RxJava的好處(我假設您已經(jīng)對它有所體驗了)。但是,我將指出在Android應用程序開發(fā)方面的有趣之處,以及如何幫助我形成第一個清晰的架構的方法。

首先,我選擇了一種響應式的模式通過轉換usecase(在這個清晰的架構命名規(guī)則中,其被稱為interactor)返回Observables<T>,表示所有底層都遵循這一鏈條,也返回Observables<T> 。

image

正如你所看到的,所有用例繼承這個抽象類,并實現(xiàn)抽象方法buildUseCaseObservable()。該方法將建立一個Observables<T>,它承擔了繁重的工作,還要返回所需的數(shù)據(jù)。

需要強調(diào)是,在execute()方法中,要確保Observables<T> 是在獨立線程執(zhí)行,因此,要盡可能減輕阻止android主線程的程度。其結果就是會通過android主線程調(diào)度程序將主線程壓入線程隊列的尾部(push back)。

到目前為止,我們的Observables<T>啟動并且運行了。但是,正如你所知,必須要觀察它所發(fā)出的數(shù)據(jù)序列。要做到這一點,我改進了presenters(MVP模式表現(xiàn)層的一部分),把它變成了觀察者(Subscribers),它通過用例對發(fā)出的項目做出“react”,以便更新用戶界面。

觀察者是這樣的:

image

每個觀察者都是每個presenter的內(nèi)部類,并實現(xiàn)了一個Defaultsubscriber<T>接口,創(chuàng)建了基本的默認錯誤處理。

將所有的片段放在一起后,通過下面的圖,你可以獲得完整的概念:

image

讓我們列舉一些擺脫基于RxJava方法的好處:

在觀察者(Subscribers)與被觀察者(Observables)之間去耦合:更加易于維護和測試。

  • 簡化異步任務:如果要求多個異步執(zhí)行時,如果需要一個以上異步執(zhí)行的級別,Java的thread和future的操作和同步比較復雜,因此通過使用調(diào)度程序,我們可以很方便地(不需要額外工作)在后臺與主線程之間跳轉,特別是當我們需要更新UI時。還可以避免“回調(diào)的坑”—— 它使我們代碼可讀性差,且難以跟進。

  • 數(shù)據(jù)轉換/組成:在不影響客戶端情況下,我們能夠整合多個Observables<T>,使解決方案更靈活。

  • 錯誤處理:在任何Observables<T>內(nèi)發(fā)生錯誤時,就向消費者發(fā)出信號。

從我的角度看有一點不足,甚至要為此需要付出代價,那些還不熟悉概念的開發(fā)人員還是要遵循學習曲線。但你從中得到了極有價值的東西。為了成功而reactive起來吧!

依賴注入:Dagger 2

關于依賴注入,因為我已經(jīng)寫了一篇完整的文章,我不想說太多。強烈建議你閱讀它,這樣我們就可以接著說下面的內(nèi)容了。

值得一提的是,通過實現(xiàn)一個像Dagger 2那樣的依賴注入框架我們能夠獲得:

  • 組件重用,因為依賴的對象可以在外部注入和配置。

  • 當注入對象作為協(xié)作者(collaborators)時,由于對象的實例存在于在一個隔離和解耦地方,這樣在我們的代碼庫中,就不需要做很多的改變,就可以改變?nèi)魏螌ο蟮膶崿F(xiàn)。

  • 依賴可以注入到一個組件:這些將這些模擬實現(xiàn)的依賴對象注入成為可能,這使得測試更容易。

Lambda表達式:Retrolambda

沒有人會抱怨在代碼中使用Java 8的lambada表達式,甚至在簡化并擺脫了很多樣板代碼以后,使用得更多,如你看到這段代碼:

image

然而,我百感交集,為什么呢?我們曾在@SoundCloud討論Retrolambada,主要是是否使用它,結果是:

<pre class="md-fences md-end-block" lang="" contenteditable="false" cid="n485" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Monaco, Consolas, "Andale Mono", "DejaVu Sans Mono", monospace; font-size: 0.9rem; white-space: pre; margin-top: 0px; margin-bottom: 20px; background: var(--code-block-bg-color); display: block; break-inside: avoid; text-align: left; position: relative !important; padding: 10px 30px; border: 1px solid; width: inherit; color: rgb(184, 191, 198); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">
1. 贊成的理由:</pre>

  • Lambda表達式和方法引用

  • “try-with-resources”語句

  • 使用karma做開發(fā)

\2. 反對的理由:

  • Java 8 API的意外使用

  • 十分令人反感的第三方庫

  • 要與Android一起使用的第三方插件Gradle

最后,我們認定它不能為我們解決任何問題:你的代碼看起來很好且具有可讀性,但這不是我們與之共存的東西,由于現(xiàn)在所有功能最強大的IDE都包含代碼折疊式選項,這就涵蓋這一需求了,至少是一個可接受的方式。

說實話,盡管我可能會在業(yè)余時間的項目中使用它,但在這里使用它的主要原因是嘗試和體驗Android中Lambda表達式。是否使用它由你自己決定。在這里我只是展示我的視野。當然,對于這樣一項了不起的工作,這個的作者值得稱贊。

測試方法

在測試方面,與示例的第一個版本相關的部分變化不大:

  • 表現(xiàn)層:用Espresso 2和Android Instrumentation測試框架測試UI。

  • 領域層:JUnit + Mockito —— 它是Java的標準模塊。

  • 數(shù)據(jù)層:將測試組合換成了Robolectric 3 + JUnit + Mockito。這一層的測試曾經(jīng)存在于單獨的Android模塊。由于當時(當前示例程序的第一個版本)沒有內(nèi)置單元測試的支持,也沒有建立像robolectric那樣的框架,該框架比較復雜,需要一群黑客的幫忙才能讓其正常工作。

幸運的是,這都是過去的一部分,而現(xiàn)在所有都是即刻可用,這樣我可以把它們重新放到數(shù)據(jù)模塊內(nèi),專門為其默認的測試路徑:src/test/java。

包的組織

我認為一個好的架構關鍵因素之一是代碼/包的組織:程序員瀏覽源代碼遇到的第一件事情就是包結構。一切從它流出,一切依賴于它。

我們能夠辨別出將應用程序封裝進入包(package)的2個路徑:

  • 按層分包:每一個包(package)中包含的項通常不是彼此密切相關的。這樣包的內(nèi)聚性低、模塊化程度低,包之間偶合度高。因此,編輯某個特性要編輯來自不同包的文件。另外,單次操作幾乎不可能刪除掉某個功能特性。

  • 按特性分包:用包來體現(xiàn)特性集。把所有相關某一特性(且僅特性相關)的項放入一個包中。這樣包的內(nèi)聚性高,模塊化程度高,包之間偶合度低。緊密相關的項放在一起。它們沒有分散到整個應用程序中。

我的建議是去掉按特性分包,會帶來的好處有以下主要幾點:

  • 模塊化程度更高

  • 代碼導航更容易

  • 功能特性的作用域范圍最小化了

如果與功能特性團隊一起工作(就像我們在@SoundCloud的所作所為),也會是非常有趣的事情。代碼的所有權會更容易組織,也更容易被模塊化。在許多開發(fā)人員共用一個代碼庫的成長型組織當中,這是一種成功。

image

如你所見,我的方法看起來就像按層分包:這里我可能會犯錯(例如,在“users”下組織一切),但在這種情況下我會原諒自己,因為這是個以學習為目的的例子,而且我想顯示的是清晰架構方法的主要概念。領會其意,切勿盲目模仿:-)。

還需要做的事:組織構建邏輯

我們都知道,房子是從地基上建立起來的。軟件開發(fā)也是這樣,我想說的是,從我的角度來看,構建系統(tǒng)(及其組織)是軟件架構的重要部分。

在Android平臺上,我們采用Gradle,它事實上是一種與平臺無關的構建系統(tǒng),功能非常強大。這里的想法是通過一些提示和技巧,讓你組織構建應用程序時能夠得到簡化。

  • 在單獨的gradle構建文件中按功能對內(nèi)容進行分組
image
image

因此,你可以用“apply from: ‘buildsystem/ci.gradle’”插入到任何Gradle建立的文件中進行配置。不要把所有都放置在一個build.gradle文件中,否則就是去創(chuàng)建一個怪物,這是教訓。

  • 創(chuàng)建依賴關系圖
image
image

如果想在項目的不同模塊間重用相同的組件版本,這很好;否則就要在不同的模塊間使用不同的版本的組件依賴。另外一點,你是在同一個地方控制依賴關系,像組件版本發(fā)生沖突這樣的事情一眼就能看出來。

結語

到目前為止講了那么多,一句話,要記住沒有靈丹妙藥。但好的軟件架構會幫助代碼保持清晰和健壯,還可以保持代碼的可擴展性,易于維護。

我想指出一些事情。面對軟件存在的問題,要報以本應當解決的態(tài)度:

  • 遵守SOLID原則

  • 不要過度思考(不過度工程化)

  • 務實

  • 盡可能降低(Android)框架中模塊的依賴性


Android MVC、MVP和MVVP的概念、運用及區(qū)別


mvc mvp mvvp區(qū)別**

image

image

1 簡介

英文原文:MVC vs. MVP vs. MVVM

三者的目的都是分離關注,使得UI更容易變換(從Winform變?yōu)閃ebform),使得UI更容易進行單元測試。

2 MVC/MVP

image

** 2.1 MVC**

1、View接受用戶的交互請求

2、View將請求轉交給Controller

3、Controller操作Model進行數(shù)據(jù)更新

4、數(shù)據(jù)更新之后,Model通知View數(shù)據(jù)變化

5、View顯示更新之后的數(shù)據(jù)

View和Controller使用Strategy模式實現(xiàn),View使用Composite模式,View和Model通過Observer模式同步信息。Controller不知道任何View的細節(jié),一個Controller能被多個View使用。MVC的一個缺點是很難對Controller進行單元測試,Controller操作數(shù)據(jù),但是如何從View上斷言這些數(shù)據(jù)的變化呢?例如,點擊一個View的按鈕,提交一個事件給Controller,Controller修改Model的值。這個值反映到View上是字體和顏色的變化。測試這個Case還是有點困難的。

2.2 MVP

1、View接受用戶的交互請求

2、View將請求轉交給Presenter

3、Presenter操作Model進行數(shù)據(jù)庫更新

4、數(shù)據(jù)更新之后,Model通知Presenter數(shù)據(jù)發(fā)生變化

5、Presenter更新View的數(shù)據(jù)

Presenter將Model的變化返回給View。和MVC不同的是,Presenter會反作用于View,不像Controller只會被動的接受View的指揮。正常情況下,發(fā)現(xiàn)可以抽象View,暴露屬性和事件,然后Presenter引用View的抽象。這樣可以很容易的構造View的Mock對象,提高可單元測試性。在這里,Presenter的責任變大了,不僅要操作數(shù)據(jù),而且要更新View。

在現(xiàn)實中,MVP的實現(xiàn)會根據(jù)View的充、貧血而有一些不同,一部分傾向于在View中放置簡單的邏輯,在Presenter放置復雜的邏輯;另一部分傾向于在presenter中放置全部的邏輯。這兩種分別被稱為:Passive View和Superivising Controller。

在Passive View中,為了減少UI組件的行為,使用Controller不僅控制用戶事件的響應,而且將結果更新到View上??梢约袦y試Controller,減小View出問題的風險。

在Superivising Controller中的Controller既處理用戶輸入的響應,又操作View處理View的復雜邏輯。

3 M-V-VM

MVVM 模式將 Presenter 改名為 ViewModel,基本上與 MVP 模式完全一致。

image

MVVM是在原有領域Model的基礎上添加一個ViewModel,這個ViewModel除了正常的屬性意外,還包括一些供View顯示用的屬性。例如在經(jīng)典的MVP中,View有一個屬性IsCheck,需要在Presenter中設置View的IsCheck值。但是在MVVM中的Presenter也會有一個IsCheck屬性來同步View的IsCheck屬性,可能會用到Observer模式同步IsCheck的值。在MVVM中,Presenter被改名為ViewModel,就演變成了你看到的MVVM。在支持雙向綁定的平臺,MVVM更受歡迎。例如:微軟的WPF和Silverlight。

復雜的軟件必須有清晰合理的架構,否則無法開發(fā)和維護。

MVC(Model-View-Controller)是最常見的軟件架構之一,業(yè)界有著廣泛應用。它本身很容易理解,但是要講清楚,它與衍生的 MVP 和 MVVM 架構的區(qū)別就不容易了。

昨天晚上,我讀了《Scaling Isomorphic Javascript Code》,突然意識到,它們的區(qū)別非常簡單。我用幾段話,就可以說清。

一、MVC

MVC模式的意思是,軟件可以分成三個部分。

視圖(View):用戶界面。

控制器(Controller):業(yè)務邏輯

模型(Model):數(shù)據(jù)保存

1接受用戶指令時,MVC 可以分成兩種方式。一種是通過 View 接受指令,傳遞給 Controller。

image

2.另一種是直接通過controller接受指令。

image

View 傳送指令到 Controller

Controller 完成業(yè)務邏輯后,要求 Model 改變狀態(tài)

Model 將新的數(shù)據(jù)發(fā)送到 View,用戶得到反饋

基本的MVC所有通信都是單向的。

3.有些實際項目有了變形的MVC是前兩種混合并且M與V是雙向

實際項目往往采用更靈活的方式,以 Backbone.js 為例。

\1. 用戶可以向 View 發(fā)送指令(DOM 事件),再由 View 直接要求 Model 改變狀態(tài)。

\2. 用戶也可以直接向 Controller 發(fā)送指令(改變 URL 觸發(fā) hashChange 事件),再由 Controller 發(fā)送給 View。

\3. Controller 非常薄,只起到路由的作用,而 View 非常厚,業(yè)務邏輯都部署在 View。所以,Backbone 索性取消了 Controller,只保留一個 Router(路由器) 。

image

四、MVP

MVP 模式將 Controller 改名為 Presenter,同時改變了通信方向。

\1. 各部分之間的通信,都是雙向的。

\2. View 與 Model 不發(fā)生聯(lián)系,都通過 Presenter 傳遞。

\3. View 非常薄,不部署任何業(yè)務邏輯,稱為"被動視圖"(Passive View),即沒有任何主動性,而 Presenter非常厚,所有邏輯都部署在那里。

image

五、MVVM

MVVM 模式將 Presenter 改名為 ViewModel,基本上與 MVP 模式完全一致。

唯一的區(qū)別是,它采用雙向綁定(data-binding):View的變動,自動反映在 ViewModel,反之亦然。Angular 和 Ember 都采用這種模式。

image

MVC、MVP、MVVP

https://www.cnblogs.com/wytiger/p/5305087.html

http://blog.csdn.net/qq_17250009/article/details/51161074

http://cache.baiducontent.com/c?m=9f65cb4a8c8507ed19fa950d100b92235c438014648c83493e8ed45f93130a1c187ba1a626201306d6c17e6500a81e5efeb56b32610c7ff3cadf883b82ffd03f2ff97870345e&p=882a9644d68113fc57efe63d46498e&newp=8d71c64ad4d533f508e2977f0d0580231610db2151d7da126b82c825d7331b001c3bbfb42325130ed7c478620aac4357e9f5317535012ba3dda5c91d9fb4c574799e556e&user=baidu&fm=sc&query=mvc+mvp+mvvp&qid=e4e3612300017f95&p1=10

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容