Medium高贊系列,如何正確的在Stack Overflow提問

在我們寫程序的時候,經常會遇到各色各樣的問題,在國內,小夥伴們經常去知乎、CSDN、博客園、思否、安卓巴士等地方提問並獲得答案。

這些地方彙集了很多優秀的、愛分享的國內資源。小編比較自豪的一件事情就是:當初學習dubbo期間,因為一個數據關閉錯誤一直找不到正確的解決方式,就順手把自己解決問題的步驟寫下,並附上參考資料中的方法,算是把那類問題做了一個增強版的總結,沒想過幾個月後,有位粉絲專門找上來感謝我,幫他解決了疑惑。

技術人,就是那麼容易得到滿足。得到別人的一句謝謝,開心的像個傻瓜。前行路上,愛分享、把資源提供給更多的人,是最開心和愉快的事情。

現在是移動互聯網的時代,倘若我們能鏈接到更多的人,倘若我們來連接的不僅僅有國內,還有國外,那豈不是更好?那麼如何在國外得到自己想要的答案?我們不妨去Stack Overflow這個平台去試試,優秀的問答平台,你們懂的。

但是提問也是一門藝術,所以趕緊來看看他們的總結,助你更好地在平台上提問。

原文地址:https://medium.com/better-programming/how-to-ask-a-question-that-gets-answered-on-stack-overflow-45f87f1a2fef

作者:Nabil Nalakath

時間:2019.11.12

當有人告訴我他們在開發中遇到的問題時,在大多數情況下,我的直接答覆是:“您在Stack Overflow上發現了什麼?”

但是,很多開發者會給出奇怪的答案,例如:“我不知道如何使用它,我因提出較差的問題而被禁止,人們總是不贊成我的帖子,或者給我有關如何提問的鏈接,”等。

Stack Overflow是互聯網上最有用,訪問最多的網站之一,但它也是互聯網上最殘酷的平台之一。

如果您犯了一個錯誤或提出了一個愚蠢的問題,人們不會理財你,這就是該平台自成立以來一直保持其標準的方式。因此,別指望有什麼收穫。

相反,我們需要習慣它並改變提問的方式。夠了,讓我們來看看您在提問時要注意哪些重要事項。

發布問題時要注意的事項

  1. 標題要具體(不要在標題中張貼整個問題或廣泛的問題)

  2. 使用正確的標籤(這對於快速獲得答案非常重要)

  3. 張貼代碼的相關部分,並在問題編輯器中使用代碼標籤將其格式化為代碼(如果代碼不是整齊的,大多數人都不會去回答)

  4. 如果您要解釋運行時出現的問題,請嘗試發布屏幕截圖

  5. 如果有日誌的話,發布正確的錯誤日誌(特別是在應用崩潰的情況下)

  6. 如果您的部分輸出沒有錯誤,並且想要對輸出進行特定的修改,而且您似乎無法弄清楚如何,將問題分為兩部分,在問題中清楚提及:

  • 你現在有什麼
  • 你需要達到的目標
  1. 如果與UI相關,請發布線框屏幕截圖,如果不可用,請嘗試在現有的UI屏幕截圖中使用諸如Paint之類的簡單工具標記所需的內容或您要進行的更改

  2. 如果您認為版本代碼可能與解決問題有關,請發布版本代碼(例如:果問題僅在舊版本的PHP或Android中發生,而在新版本中則沒有)

發布時要避免的錯誤

  • 切勿發布代碼中包含品牌名稱或公司名稱的部分

  • 裁剪屏幕截圖以僅显示相關內容

  • 如果代碼包含部分內容,例如鍵或密碼(例如PHP郵件程序代碼中的电子郵件密碼),請始終用****或特殊字符替換密碼字段

  • 不要發布自己創建的特殊算法或應用引擎代碼,除非您不介意其他人使用它或將其開源

壞問題和好問題

讓我們看一下146票贊成的這篇文章:

地址:https://stackoverflow.com/questions/3905734/how-to-send-100-000-emails-weekly?source=post_page-----45f87f1a2fef----------------------

如您在本示例中看到的,已發布的問題不是特定問題。如果您要這樣的教程類型答案,那麼Stack Overflow並不是一個好地方。

以該示例為例,在這種情況下,用戶要求每周使用PHP向100,000個用戶發送一封电子郵件。但問題並沒有显示用戶方面的任何努力。

到目前為止,還沒有提及用戶已經嘗試了什麼或他們面臨的任何特定錯誤。這是不能回答問題的完美範例。

另外,這裡有一些很好的示例問題供您參考。

地址:https://stackoverflow.com/questions/11227809/why-is-processing-a-sorted-array-faster-than-processing-an-unsorted-array?source=post_page-----45f87f1a2fef----------------------

地址:https://stackoverflow.com/questions/51096796/how-to-enable-horizontal-scrolling-for-chart-js-in-ionic?source=post_page-----45f87f1a2fef----------------------

地址:https://stackoverflow.com/questions/47923524/app-is-crashing-on-some-devices-android-studio-shows-out-of-memory-exception-er?source=post_page-----45f87f1a2fef----------------------

如您所見,即使有人不回答,如果您以適當的方式提出問題,您仍然會獲得贊成票

致謝

最後,如果您得到查詢的答案並且符合您的要求,請將其標記為可接受的答案以關閉問題。

這將幫助發布答案的人獲得聲譽,並鼓勵他們幫助更多人。

畢竟,平台的存在僅是因為這些樂於助人的無私奉獻者願意為您提供幫助,因此這是您為他們所做的最少的事情。

結尾

提問是一門藝術,小編也經常遇到很多提問看不懂、看不明白的情況。無論是在團隊里還是平時和大家交流的過程中,多多少少會遇到互相不理解的情況,所以,做技術的我們實在是太難了,哈哈。

當然,如果學會了一些必要的技巧,提問對我們來說還是just so so,畢竟共同語言這麼多,雖然問題形形色色,但是茫茫人海,總會有人遇到你遇到的問題,總存在能解決問題的方法。

這是一篇很好的提問的範例,不僅僅是在Stack Overflow上,包括我們自己國內的平台、自己項目組、都可以用類似的技巧來提問,能大大節省溝通成本,獲得更高效率。

本文由博客一文多發平台 發布!

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

JVM 中你不得不知的一些參數

有的同學雖然寫了一段時間 Java 了,但是對於 JVM 卻不太關注。有的同學說,參數都是團隊規定好的,部署的時候也不用我動手,關注它有什麼用,而且,JVM 這東西,聽上去就感覺很神秘很高深的樣子,還是算了吧。

沒錯,部署的時候可能用不到你親自動手,但是出現問題了怎麼辦,難道不用你解決問題嗎,如果對 JVM 了解不夠的話,有些問題可能排查起來就很費力,或者根本無法解決。

本篇以 JDK Hotspot 8 為背景,介紹一下 JVM 的常用參數。建議你在做一些小項目、小 demo 的時候,也把這些參數加上,加深印象。以我的經驗來看,有些知識你剛開始接觸的時候會感覺很難理解,但是沒關係,萬事開頭難嘛,知識點都是需要消化時間的。第一天不理解,甚至過了一個月也不理解,但是總有那麼一刻,你會突然有種茅塞頓開的感覺,感覺一下子通了。最後心裏面感謝自己在多少多少天以前能夠開始學習並堅持學習這些知識點。

只介紹一些常用參數,除了這些常用參數外,Hotspot 還提供了很多其他的參數,每一個都值得考究。

在使用這些參數之前,你需要對 Java 內存模型有一定的了解,可以讀一下 了解一下內存模型。

還是要把內存模型圖放在這裏,方便理解。

堆參數:

-Xms: 堆的初始值,例如 -Xmx2048,初始堆大小為 2G

-Xmx: 堆的最大值,例如 -Xmx2048M,允許最大堆內存 2G

-Xmn: 新生代大小

-XX:SurvivorRatio:Eden 區所佔比例,默認是 8,也就是 80%,例如 -XX:SurvivorRatio=8

最好將 -Xms 和 -Xmx 的值設置成一樣的值,這樣做是為了防止隨着堆空間使用量增加,會動態的調整堆空間大小,有一定的性能損耗,不如開始就設置成相同的值,來規避性能損失。

棧參數

-Xss:棧空間大小,棧是線程獨佔的,所以是一個線程使用棧空間的大小,例如 -Xss256K,如果不設置此參數,默認值是 1M,一般來講設置成 256K 就足夠了。

Metaspace 參數

-XX:MetaspaceSize:Metaspace 空間初始大小,如果不設置的話,默認是20.79M,這個初始大小是觸發首次 Metaspace Full GC 的閾值,例如 -XX:MetaspaceSize=256M

-XX:MaxMetaspaceSize:Metaspace 最大值,默認不限制大小,但是線上環境建議設置,例如

-XX:MaxMetaspaceSize=256M

-XX:MinMetaspaceFreeRatio:最小空閑比,當 Metaspace 發生 GC 后,會計算 Metaspace 的空閑比,如果空閑比(空閑空間/當前 Metaspace 大小)小於此值,就會觸發 Metaspace 擴容。默認值是 40 ,也就是 40%,例如 -XX:MinMetaspaceFreeRatio=40

-XX:MaxMetaspaceFreeRatio:最大空閑比,當 Metaspace 發生 GC 后,會計算 Metaspace 的空閑比,如果空閑比(空閑空間/當前 Metaspace 大小)大於此值,就會觸發 Metaspace 釋放空間。默認值是 70 ,也就是 70%,例如 -XX:MaxMetaspaceFreeRatio=70

建議將 MetaspaceSize 和 MaxMetaspaceSize 設置為同樣大小,避免頻繁擴容。

GC 日誌

簡單日誌

-verbose:gc 或者 -XX:+PrintGC

日誌格式:

[GC (Allocation Failure)  7892K->5646K(19456K), 0.0060442 secs]
[GC (Allocation Failure) , 0.0066315 secs]
[Full GC (Allocation Failure)  19302K->13646K(19456K), 0.0032698 secs]

詳細日誌

#打印詳細日誌
-XX:+PrintGCDetails
#打印 GC 的時間點
-XX:+PrintGCDateStamps

日誌格式:

2019-11-13T14:06:46.099-0800: [GC (Allocation Failure) 2019-11-13T14:06:46.099-0800: [DefNew (promotion failed) : 9180K->9157K(9216K), 0.0084297 secs]2019-11-13T14:06:46.107-0800: [Tenured: 10145K->10145K(10240K), 0.0035768 secs] 13802K->13646K(19456K), [Metaspace: 3895K->3895K(1056768K)], 0.0120887 secs] [Times: user=0.00 sys=0.00, real=0.02 secs] 
2019-11-13T14:06:47.243-0800: [Full GC (Allocation Failure) 2019-11-13T14:06:47.244-0800: [Tenured: 10145K->10145K(10240K), 0.0042686 secs] 19304K->19146K(19456K), [Metaspace: 3895K->3895K(1056768K)], 0.0043232 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 

以下幾個 GC 日誌相關的參數打印的內容比較多,生產環境可選擇性開啟,大多數時候不需要開啟。

GC 前後的堆信息

-XX:+PrintHeapAtGC

{Heap before GC invocations=0 (full 0):
 def new generation   total 9216K, used 7892K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
  eden space 8192K,  96% used [0x00000007bec00000, 0x00000007bf3b5200, 
  xxx....
  class space    used 445K, capacity 462K, committed 512K, reserved 1048576K
Heap after GC invocations=1 (full 0):
 def new generation   total 9216K, used 1023K [0x00000007bec00000,
 xxx...
 Metaspace       used 3892K, capacity 4646K, committed 4864K, reserved 1056768K
  class space    used 445K, capacity 462K, committed 512K, reserved 1048576K
}

GC 導致的 Stop the world 時間

-XX:+PrintGCApplicationStoppedTime

Total time for which application threads were stopped: 0.0070384 seconds, Stopping threads took: 0.0000200 seconds

加載類信息

-verbose:class

[Loaded java.net.URLClassLoader$3$1 from /Library/Java/JavaVirtualMachines/jdk1.8.0_111.jdk/Contents/Home/jre/lib/rt.jar]

GC 前後的類加載情況

-XX:+PrintClassHistogramBeforeFullGC
-XX:+PrintClassHistogramAfterFullGC
 num     #instances         #bytes  class name
----------------------------------------------
   1:           140       19016264  [B
   2:          2853         226256  [C
   3:           138         169072  [I
   4:           761          86240  java.lang.Class
   5:          2850          68400  java.lang.String
   6:           660          41024  [Ljava.lang.Object;

日誌輸出到文件

以上參數配置好之後,默認會輸出到控制台或者服務指定的統一日誌的位置。但是這裏還會有服務的一般性信息日誌、錯誤日誌等,都混在一起的話會比較亂,所以,一般都會把 jvm 日誌單獨存放。

#GC 活動日誌,根據配置的參數輸出內容
-Xloggc:/Users/fengzheng/jvmlog/gc.log

#致命錯誤日誌,只有在 jvm 發生崩潰的時候會輸出
-XX:ErrorFile=/Users/fengzheng/jvmlog/hs_err_pid%p.log

堆溢出現場保留

有些錯誤雖然不會導致 jvm 崩潰,但是對於服務而言也是非常嚴重的,比如stackOverflow、OutOfMemoryError,發生錯誤后,留存現場信息對分析錯誤原因是至關重要的。jvm 提供了保留堆溢出現場的方法,對於 JDK 8 而言,可能是 heap 溢出,也可能是 Metasapce 溢出。

-XX:HeapDumpPath=/Users/fengzheng/jvmlog
-XX:+HeapDumpOnOutOfMemoryError

最後出現異常后,保存的文件格式為 java_pidxxx.hprof,pid 後面是發生溢出的進程 id,之後可以用 VisualVM、JProfiler 等工具打開分析。

設置垃圾回收器類型

隨着 JDK 版本的升級,可使用的垃圾收集器類型也越來越多了。JDK 8 可使用的垃圾收集器有 7 種,當然有點只適用於年輕代,有點只使用於老年代,JDK 8 中最新的垃圾收集器是 G1,可以用於年輕代和老年代。到了 JDK 11,還出了 ZGC。

下圖是 JDK 8 中可使用的垃圾收集器以及它們配合使用的關係。

Serial、ParNew、Parallel Scavenge 只適用於年輕代,CMS、Serial Old、Parallel Old 只適用於老年代,而 G1 通用於年輕代和老年代。連線表示它們之間可配合使用的關係,其中 CMS 和 Serial Old 連線的意思是說 Serial Old 會作為 CMS 的后預案,當 CMS 發生 Concurrent Mode Failure 時啟用。

在 JDK 8 中,如果不指定垃圾收集器,默認使用參數 -XX:+UseParallelGC,新生代使用 Parallel Scavenge,老年代使用 Serial Old。

-XX:+UseSerialGC:使用 Serial + Serial Old ,運行於 client 模式下的默認設置

-XX:+UseConcMarkSweepGC:使用 ParNew+CMS+Serial Old,CMS 垃圾收集器

-XX:+UseParallelGC:Parallel Scavenge + Serial Old,JDK 8 server 模式下的默認設置

-XX:+UseParallelOldGC:Parallel Scavenge + Parallel Old

-XX:+UseG1GC:使用 G1 垃圾收集器

開啟遠程 JMX 監控

除了日誌外,當我們需要實時查看 JVM 運行情況的時候怎麼辦,當然可以到 JVM 所在服務器用 jstack、jmap、jinfo 等工具進行查看,但是又不夠直觀,這時候就需要開啟 JMX 遠程功能,使用 jConsole、VisualVM 等工具進行監控。或者自己開發監控平台,比如我之前就做了一個 web 版的簡易 VisualVm。

開啟參數如下:

-Dcom.sun.management.jmxremote
#指定 jvm 所在服務器 ip 或域名
-Djava.rmi.server.hostname=192.168.1.1
#指定端口
-Dcom.sun.management.jmxremote.port=9999
-Dcom.sun.management.jmxremote.rmi.port=9999
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false
相關閱讀:

不要吝惜你的「推薦」呦

歡迎關注,不定期更新本系列和其他文章
古時的風箏 ,進入公眾號可以加入交流群

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

※評比南投搬家公司費用收費行情懶人包大公開

Java鎖-Synchronized深層剖析

Java鎖-Synchronized深層剖析

前言

Java鎖的問題,可以說是每個JavaCoder繞不開的一道坎。如果只是粗淺地了解Synchronized等鎖的簡單應用,那麼就沒什麼談的了,也不建議繼續閱讀下去。如果希望非常詳細地了解非常底層的信息,如monitor源碼剖析,SpinLock,TicketLock,CLHLock等自旋鎖的實現,也不建議看下去,因為本文也沒有說得那麼深入。本文只是按照synchronized這條主線,探討一下Java的鎖實現,如對象頭部,markdown,monitor的主要組成,以及不同鎖之間的轉換。至於常用的ReentrantLock,ReadWriteLock等,我將在之後專門寫一篇AQS主線的Java鎖分析。

不是我不想解釋得更為詳細,更為底層,而是因為兩個方面。一方面正常開發中真的用不到那麼深入的原理。另一方面,而是那些非常深入的資料,比較難以收集,整理。當然啦,等到我的Java積累更加深厚了,也許可以試試。囧

由於Java鎖的內容比較雜,劃分的維度也是十分多樣,所以很是糾結文章的結構。經過一番考慮,還是採用類似正常學習,推演的一種邏輯來寫(涉及到一些複雜的新概念時,再詳細描述)。希望大家喜歡。

Java鎖的相關概念

如果讓我談一下對程序中鎖的最原始認識,那我就得說說PV操作(詳見我在系統架構師中系統內部原理的筆記)了。通過PV操作可以實現同步效果,以及互斥鎖等。

如果讓我談一下對Java程序中最常見的鎖的認識,那無疑就是Synchronized了。

Java鎖的定義

那麼Java鎖是什麼?網上許多博客都談到了偏向鎖,自旋鎖等定義,唯獨就是沒人去談Java鎖的定義。我也不能很好定義它,因為Java鎖隨着近些年的不斷擴展,其概念早就比原來膨脹了許多。硬要我說,Java鎖就是在多線程情況下,通過特定機制(如CAS),特定對象(如Monitor),配合LockRecord等,實現線程間資源獨佔,流程同步等效果。

當然這個定義並不完美,但也算差不多說出了我目前對鎖的認識(貌似這不叫定義,不要計較)。

Java鎖的分類標準

  1. 自旋鎖:是指當一個線程在獲取鎖的時候,如果鎖已經被其他線程獲取,那麼該線程將循環等待,然後不斷的判斷鎖是否能夠被成功獲取,直到獲取到鎖才會退出循環(之前文章提到的CAS就是自旋鎖)
  2. 樂觀鎖:假定沒有衝突,再修改數據時如果發現數據和之前獲取的不一致,則讀最新數據,修改后重試修改(之前文章提到的CAS就是樂觀鎖)
  3. 悲觀所:假定一定會發生併發衝突,同步所有對數據的相關操作,從讀數據就開始上鎖(Synchronized就是悲觀鎖)
  4. 獨享鎖:給資源加上獨享鎖,該資源同一時刻只能被一個線程持有(如JUC中的寫鎖)
  5. 共享鎖:給資源加上共享鎖,該資源可同時被多個線程持有(如JUC中的讀鎖)
  6. 可重入鎖:線程拿到某資源的鎖后,可自由進入同一把鎖同步的其他代碼(即獲得鎖的線程,可多次進入持有的鎖的代碼中,如Synchronized就是可重入鎖)
  7. 不可重入鎖:線程拿到某資源的鎖后,不可進入同一把鎖同步的其他代碼
  8. 公平鎖:爭奪鎖的順序,獲得鎖的順序是按照先來後到的(如ReentrantLock(true))
  9. 非公平所:爭奪鎖的順序,獲得鎖的順序並非按照先來後到的(如Synchronized)

其實這裏面有很多有意思的東西,如自旋鎖的特性,大家都可以根據CAS的實現了解到了。Java的自選鎖在JDK4的時候就引入了(但當時需要手動開啟),並在JDK1.6變為默認開啟,更重要的是,在JDK1.6中Java引入了自適應自旋鎖(簡單說就是自旋鎖的自旋次數不再固定)。又比如自旋鎖一般都是樂觀鎖,獨享鎖是悲觀所的子集等等。

** Java鎖還可以按照底層實現分為兩種。一種是由JVM提供支持的Synchronized鎖,另一種是JDK提供的以AQS為實現基礎的JUC工具,如ReentrantLock,ReadWriteLock,以及CountDownLatch,Semaphore,CyclicBarrier等。**

Java鎖-Synchronized

Synchronized應該是大家最早接觸到的Java鎖,也是大家一開始用得最多的鎖。畢竟它功能多樣,能力又強,又能滿足常規開發的需求。

有了上面的概念鋪墊,就很好定義Synchronized了。Synchronized是悲觀鎖,獨享鎖,可重入鎖

當然Synchronized有多種使用方式,如同步代碼塊(類鎖),同步代碼塊(對象鎖),同步非靜態方法,同步靜態方法四種。後面有機會,我會掛上我筆記的相關頁面。但是總結一下,其實很簡單,注意區分鎖的持有者與鎖的目標就可以了。static就是針對類(即所有對該類的實例對象)。

其次,Synchronized不僅實現同步,並且JMM中規定,Synchronized要保證可見性(詳細參照筆記中對volatile可見性的剖析)。

然後Synchronized有鎖優化:鎖消除,鎖粗化(JDK做了鎖粗化的優化,但可以通過代碼層面優化,可提高代碼的可讀性與優雅性)

另外,Synchronized確實很方便,很簡單,但是也希望大家不要濫用,看起來很糟糕,而且也讓後來者很難下叉。

Java鎖的實現原理

終於到了重頭戲,也到了最消耗腦力的部分了。這裏要說明一點,這裏提及的只是常見的鎖的原理,並不是所有鎖原理展示(如Synchronized展示的是對象鎖,而不是類鎖,網上也基本沒有博客詳細寫類鎖的實現原理,但不代表沒有)。如Synchronized方法是通過ACC_SYNCHRONIZED進行隱式同步的。

對象在內存中的結構(重點)

首先,我們需要正常對象在內存中的結構,才可以繼續深入研究。

JVM運行時數據區分為線程共享部分(MetaSpace,堆),線程私有部分(程序計數器,虛擬機棧,本地方法棧)。這部分不清楚的,自行百度或查看我之前有關JVM的筆記。那麼堆空間存放的就是數組與類對象。而MetaSpace(原方法區/持久代)主要用於存儲類的信息,方法數據,方法代碼等。

我知道,沒有圖,你們是不會看的。

PS:為了偷懶,我放的都是網絡圖片,如果掛了。嗯,你們就自己百度吧

PS2:如果使用的網絡圖片存在侵權問題,請聯繫我,抱歉。

第一張圖,簡單地表述了在JVM中堆,棧,方法區三者之間的關係

我來說明一下,我們代碼中類的信息是保存在方法區中,方法區保存了類信息,如類型信息,字段信息,方法信息,方法表等。簡單說,方法區是用來保存類的相關信息的。詳見下圖:

而堆,用於保存類實例出來的對象。

以hotspot的JVM實現為例,對象在對內存中的數據分為三個部分:

  1. 對象頭(Header):保存對象信息與狀態(重點,後面詳細說明)
  2. 實例數據(Instance Data):對象真正存儲的有效數據(代碼定義字段,即對象中的實際數據)
  3. 對齊填充(Padding):VM的自動內存管理要求對象起始地址必須是8字節的整數倍(說白了,就是拋棄的內存空間)

簡單說明一下,對齊填充的問題,可以理解為系統內存管理中頁式內存管理的內存碎片。畢竟內存都是要求整整齊齊,便於管理的。如果還不能理解,舉個栗子,正常人規劃自己一天的活動,往往是以小時,乃至分鐘劃分的時間塊,而不會劃分到秒,乃至微妙。所以為了便於內存管理,那些零頭內存就直接填充好了,就像你制定一天的計劃, 晚上睡眠的時間可能總是差幾分鐘那樣。如果你還是不能理解,你可以查閱操作系統的內存管理相關知識(各類內存管理的概念,如頁式,段式,段頁式等)。

如果你原先對JVM有一定認識,卻理解不深的話,可能就有點迷糊了。

Java對象中的實例數據部分存儲對象的實際數據,什麼是對象的實際數據?這些數據與虛擬機棧中的局部變量表中的數據又有什麼區別?

且聽我給你編,啊呸,我給你說明。為了便於理解,插入圖片

Java對象中所謂的實際數據就是屬於對象中的各個變量(屬於對象的各個變量不包括函數方法中的變量,具體後面會談到)。這裡有兩點需要注意:

  • 代碼中是由實際變量與引用變量的概念之分的。實際變量就是實際保存值的變量,而引用變量是一個類似C語言指針的存在,它不保存目標值,而是保存實際變量的引用地址。如果你還是沒法理解,你可以通過數組實驗,或認識Netty零拷貝,原型模式等方法去了解相關概念,增強積累。
  • 內存中對象存儲的變量多為引用變量。
  • 那麼對象除了各種實際數據外,就是各種函數方法了(函數方法的內存表示,網上很多博客都描述的語焉不詳,甚至錯誤)。函數方法可以分為兩個部分來看:一方面是整體邏輯流程,這個是所有實例對象所共有的,故保存在方法區(而不是某些博客所說的,不是具體實現,所以內存中不存在。代碼都壓入內存了,你和我說執行邏輯不存在?)。另一方面是數據(屬性,變量這種),這個即使是同一個實例對象不同調用時也是不一樣的,故運行時保存在棧(具體保存在虛擬機棧,還是本地方法棧,取決於方法是否為本地方法,即native方法。這部分網上說明較多)。

針對第二點,我舉個實際例子。

如StudentManager對象中有Student stu = new Student(“ming”);,那麼在內存中是存在兩個對象的:StudentManger實例對象,Student實例對象(其傳入構造方法的參數為”ming”)。而在StudentManager實例對象中有一個Student類型的stu引用變量,其值指向了剛才說的Student實例對象(其傳入構造方法的參數為”ming”)。那麼再深入一些,為什麼StudentManager實例對象中的stu引用變量要強調是Student類型的,因為JVM要在堆中為StudentManager實例對象分配明確大小的內存啊,所以JVM要知道實例對象中各個引用變量需要分配的內存大小。那麼stu引用變量是如何指向Student實例對象(其傳入構造方法的參數為”ming”)的?這個問題的答案涉及到句柄的概念,這裏簡單立即為指針指向即可。

數組是如何確定內存大小的。
那麼數組在內存中的表現是怎樣的呢?其實和之前的思路還是一樣的。引用變量指向實際值。

二維數組的話,第一層數組中保存的是一維數組的引用變量。其實如果學習過C語言,並且學得還行的話,這些概念都很好理解的。

關於對象中的變量與函數方法中的變量區別及緣由:眾所周知,Java有對內存與棧內存,兩者都有着保存數據的職責。堆的優勢可以動態分配內存大小,也正由於動態性,所以速度較慢。而棧由於其特殊的數據結構-棧,所以速度較快。一般而言,對象中的變量的生命周期比對象中函數方法的變量的生命周期更長(至少前者不少於後者)。當然還有一些別的原因,最終對象中的變量保存在堆中,而函數方法的變量放在棧中。

補充一下,Java的內存分配策略分為靜態存儲,棧式存儲,堆式存儲。后兩者本文都有提到,說一下靜態存儲。靜態存儲就是編譯時確定每個數據目標在運行時的存儲需求,保存在堆內對應對象中。

針對虛擬機棧(本地方法不在此討論),簡單說明一下(因為後面用得到)。

先上個圖

虛擬機棧屬於JVM中線程私有的部分,即每個線程都有屬於自己的虛擬機棧(Stack)。而虛擬機棧是由一個個虛擬機棧幀組成的,虛擬機棧幀(Stack Frame)可以理解為一次方法調用的整體邏輯流程(Java方法執行的內存模型)。而虛擬機棧是由局部變量表(Local Variable Table),操作棧(Operand Stack),動態連接(Dynamic Linking),返回地址(Reture Address)等組成。簡單說明一下,局部變量表就是用於保存方法的局部變量(生命周期與方法一致。注意基本數據類型與對象的不同,如果是對象,則該局部變量為一個引用變量,指向堆內存中對應對象),操作棧用於實現各種加減乘除的操作等(如iadd,iload等),動態鏈接(這個解釋比較麻煩,詳見《深入理解Java虛擬機》p243),返回地址(用於在退出棧幀時,恢復上層棧幀的執行狀態。說白了就是A方法中調用B方法,B方法執行結束后,如何確保回到A方法調用B方法的位置與狀態,畢竟一個線程就一個虛擬機棧)。

到了這一步,就滿足了接下來學習的基本要求了。如果希望有更為深入的理解,可以坐等我之後有關JVM的博客,或者查看我的相關筆記,或者查詢相關資料(如百度,《深入理解Java虛擬機》等。

Java對象頭的組成(不同狀態下的不同組成)

說了這麼多,JVM是如何支持Java鎖呢?

前面Java對象的部分,我們提到了對象是由對象頭,實例數據,對齊填充三個部分組成。其中后兩者已經進行了較為充分的說明,而對象頭還沒有進行任何解釋,而鎖的實現就要靠對象頭完成

對象頭由兩到三個部分組成:

  • Mark Word:存儲對象hashCode,分代年齡,鎖類型,鎖標誌位等信息(長度為JVM的一個字大小)
  • Class Metadata Address:類型指針,指向對象的類元數據(JVM通過這個指針確定該對象是哪個類的實例,指針的長度為JVM的一個字大小);
  • Array Length:[只有數組對象有該部分] 數組對象的對象頭必須有一塊記錄數組長度的數據(因為JVM可通過對象的元數據信息確定Java對象大小,但從數組的元數據中無法確定數組大小)(長度為JVM的一個字大小)。

后兩者不是重點,也與本次主題無關,不再贅述。讓我們來細究一下Mark Word的具體數據結構,及其在內存中的表現。

來,上圖。

一般第一次看看這個圖,都有點蒙,什麼玩意兒啊,到底怎麼理解啊。

所以這個時候需要我來給你舉個簡單例子。

如一個對象頭是這樣的:AAA..(一共23個A)..AAA BB CCCC D EE 。其中23個A表示線程ID,2位B表示Epoch,4位C表示對象的分代年齡,1位D表示該對象的鎖是否為偏向鎖,2位E表示鎖標誌位。

至於其它可能嘛。看到大佬已經寫了一個,情況說明得挺好的,就拿來主義了。

圖中展現了對象在無鎖,偏向鎖,輕量級鎖,重量級鎖,GC標記五種狀態下的Mark Word的不同。

biased_lock lock 狀態
0 01 無鎖
1 01 偏向鎖
0 00 輕量級鎖
0 10 重量級鎖
0 11 GC標記

引用一下這位大佬的哈(畢竟大佬解釋得蠻全面的,我就不手打了,只做補充)。

  • thread:持有偏向鎖的線程ID。
  • epoch:偏向時間戳。
  • age:4位的Java對象年齡。在GC中,如果對象在Survivor區複製一次,年齡增加1。當對象達到設定的閾值時,將會晉陞到老年代。默認情況下,并行GC的年齡閾值為15,併發GC的年齡閾值為6。由於age只有4位,所以最大值為15,這就是-XX:MaxTenuringThreshold選項最大值為15的原因。
  • biased_lock:對象是否啟用偏向鎖標記,只佔1個二進制位。為1時表示對象啟用偏向鎖,為0時表示對象沒有偏向鎖。
  • identity_hashcode:25位的對象標識Hash碼,採用延遲加載技術。調用方法System.identityHashCode()計算,並會將結果寫到該對象頭中。當對象被鎖定時,該值會移動到管程Monitor中。
  • ptr_to_lock_record:指向棧中鎖記錄的指針。
  • ptr_to_heavyweight_monitor:指向管程Monitor的指針。

可能你看到這裏,會對上面的解釋產生一定的疑惑,什麼是棧中鎖記錄,什麼是Monitor。別急,接下來的Synchronized鎖的實現就會應用到這些東西。

Java鎖的內存實現

現在就讓我們來看看我們平時使用的Java鎖在JVM中到底是怎樣的情況。

Synchronized鎖一共有四種狀態:無鎖,偏向鎖,輕量級鎖,重量級鎖。其中偏向鎖與輕量級鎖是由Java6提出,以優化Synchronized性能的(具體實現方式,後續可以看一下,有區別的)。

在此之前,我要簡單申明一個定義,首先鎖競爭的資源,我們稱為“臨界資源”(如:Synchronized(this)中指向的this對象)。而競爭鎖的線程,我們稱為鎖的競爭者,獲得鎖的線程,我們稱為鎖的持有者。

無鎖狀態

就是對象不持有任何鎖。其對象頭中的mark word是

含義 identity_hashcode age biased_lock lock
示例 aaa…(25位bit) xxxx(4位bit) 0(1位bit ,具體值:0) 01(2位bit ,具體值:01)

無鎖狀態沒什麼太多說的。

這裏簡單說一下identity_hashcode的含義,25bit位的對象hash標識碼,用於標識這是堆中哪個對象的對象頭。具體會在後面的鎖中應用到。

那麼這個時候一個線程嘗試獲取該對象鎖,會怎樣呢?

偏向鎖狀態

如果一個線程獲得了鎖,即鎖直接成為了鎖的持有者,那麼鎖(其實就是臨界資源對象)就進入了偏向模式,此時Mark Word的結果就會進入之前展示的偏向鎖結構。

那麼當該線程進再次請求該鎖時,無需再做任何同步操作(不需要再像第一次獲得該鎖那樣,進行較為複雜的操作),即獲取鎖的過程只需要檢查Mark Word的鎖標記位位偏向鎖並且當前線程ID等於Mark Word的ThreadID即可,從而節省大量有關鎖申請的操作。

看得有點懵,沒關係,我會好好詳細解釋的。此處有關偏向鎖的內存變化過程就兩個,一個是第一次獲得鎖的過程,一個是後續獲得該鎖的過程。

接下來,我會結合圖片,來詳細闡述這兩個過程的。

當一個線程通過Synchronized鎖,出於需求,對共享資源進行獨佔操作時,就得試圖向別的鎖的競爭者宣誓鎖的所有權。但是,此時由於該鎖是第一次被佔用,也不確定是否後面還有別的線程需要佔有它(大多數情況下,鎖不存在多線程競爭情況,總是由同一線程多次獲得該鎖),所以不會立馬進入資源消耗較大的重量鎖,輕量級鎖,而是選擇資源佔用最少的偏向鎖。為了向後面可能存在的鎖競爭者線程證明該共享資源已被佔用,該臨界資源的Mark Word就會做出相應變化,標記該臨界資源已被佔用。具體Mark Word會變成如下形式:

含義 thread epoll age biased_lock lock
示例 aaa…(23位bit) bb(2位bit) xxxx(4位bit) 1(1位bit ,具體值:1) 01(2位bit ,具體值:01)

這裏我來說明一下其中各個字段的具體含義:

  • thread用於標識當前持有鎖的線程(即在偏向鎖狀態下,表示當前該臨界資源被哪個線程持有)
  • epoll:用於記錄當前對象的mark word變為偏向結果的時間戳(即當前臨界資源被持有的時間戳)
  • age:與無鎖狀態作用相同,無變化
  • biased_lock:值為1,表示當前mark word為偏向鎖結構
  • lock:配合biased_lock共同表示當前mark word為偏向鎖結果(至於為什麼需要兩個字段共同表示,一方面2bit無法表示4種結構,另一方面,最常用的偏向鎖結果,利用1bit表示,既可以快速檢驗,又可以降低檢驗的資源消耗。需要的話,之後細說,或@我)

接下來就是第二個過程:鎖的競爭者線程嘗試獲得鎖,那麼鎖的競爭者線程會檢測臨界資源,或者說鎖對象的mark word。如果是無鎖狀態,參照上一個過程。如果是偏向鎖狀態,就檢測其thread是否為當前線程(鎖的競爭者線程)的線程ID。如果是當前線程的線程ID,就會直接獲得臨界資源,不需要再次進行同步操作(即上一個過程提到的CAS操作)。

還看不懂,再引入一位大佬的:

偏向鎖的加鎖過程:

  1. 訪問Mark Word中偏向鎖的標識是否設置成1,鎖標誌位是否為01,確認為可偏向狀態。

  2. 如果為可偏向狀態,則測試線程ID是否指向當前線程,如果是,進入步驟5,否則進入步驟3。

  3. 如果線程ID並未指向當前線程,則通過CAS操作競爭鎖。如果競爭成功,則將Mark Word中線程ID設置為當前線程ID,然後執行5;如果競爭失敗,執行4。

  4. 如果CAS獲取偏向鎖失敗,則表示有競爭。當到達全局安全點(safepoint)時獲得偏向鎖的線程被掛起,偏向鎖升級為輕量級鎖,然後被阻塞在安全點的線程繼續往下執行同步代碼。(撤銷偏向鎖的時候會導致stop the word)

  5. 執行同步代碼。

PS:safepoint(沒有任何字節碼正在執行的時候):詳見JVM GC相關,其會導致stop the world。

偏向鎖的存在,極大降低了Syncronized在多數情況下的性能消耗。另外,偏向鎖的持有線程運行完同步代碼塊后,不會解除偏向鎖(即鎖對象的Mark Word結構不會發生變化,其threadID也不會發生變化)

那麼,如果偏向鎖狀態的mark word中的thread不是當前線程(鎖的競爭者線程)的線程ID呢?

輕量級鎖

輕量級鎖可能是由偏向鎖升級而來的,也可能是由無鎖狀態直接升級而來(如通過JVM參數關閉了偏向鎖)。

偏向鎖運行在一個線程進入同步塊的情況下,而當第二個線程加入鎖競爭時,偏向鎖就會升級輕量級鎖。

如果JVM關閉了偏向鎖,那麼在一個線程進入同步塊時,鎖對象就會直接變為輕量級鎖(即鎖對象的Mark Word為偏向鎖結構)。

上面的解釋非常簡單,或者說粗糙,實際的判定方式更為複雜。我在查閱資料時,發現網上很多博客根本沒有深入說明偏向鎖升級輕量級鎖的深層邏輯,直到看到一篇寫出了以下的說明:

當線程1訪問代碼塊並獲取鎖對象時,會在java對象頭和棧幀中記錄偏向的鎖的threadID,因為偏向鎖不會主動釋放鎖,因此以後線程1再次獲取鎖的時候,需要比較當前線程的threadID和Java對象頭中的threadID是否一致,如果一致(還是線程1獲取鎖對象),則無需使用CAS來加鎖、解鎖;如果不一致(其他線程,如線程2要競爭鎖對象,而偏向鎖不會主動釋放因此還是存儲的線程1的threadID),那麼需要查看Java對象頭中記錄的線程1是否存活,如果沒有存活,那麼鎖對象被重置為無鎖狀態,其它線程(線程2)可以競爭將其設置為偏向鎖;如果存活,那麼立刻查找該線程(線程1)的棧幀信息,如果還是需要繼續持有這個鎖對象,那麼暫停當前線程1,撤銷偏向鎖,升級為輕量級鎖,如果線程1 不再使用該鎖對象,那麼將鎖對象狀態設為無鎖狀態,重新偏向新的線程。

這段說明的前半截,我已經在偏向鎖部分說過了。我來說明一下其後半截有關鎖升級的部分。

如果當前線程(鎖的競爭者線程)的線程ID與鎖對象的mark word的thread不一致(其他線程,如線程2要競爭鎖對象,而偏向鎖不會主動釋放因此還是存儲的線程1的threadID),那麼需要查看Java對象頭中記錄的線程1是否存活(可以直接根據鎖對象的Mark Word(更準確說是Displaced Mark Word)的thread來判斷線程1是否還存活),如果沒有存活,那麼鎖對象被重置為無鎖狀態,從而其它線程(線程2)可以競爭該鎖,並將其設置為偏向鎖(等於無鎖狀態下,重新偏向鎖的競爭);如果存活,那麼立刻查找該線程(線程1)的棧幀信息,如果線程1還是需要繼續持有這個鎖對象,那麼暫停當前線程1,撤銷偏向鎖,升級為輕量級鎖,如果線程1 不再使用該鎖對象,那麼將鎖對象狀態設為無鎖狀態,重新偏向新的線程。(這個地方其實是比較複雜的,如果有不清楚的,可以@我。)

那麼另一個由無鎖狀態升級為輕量級鎖的內存過程,就是:

首先讓我來說明一下上面提到的“如果線程1還是需要繼續持有這個鎖對象,那麼暫停當前線程1,撤銷偏向鎖,升級為輕量級鎖”涉及的三個問題。

  1. 為什麼需要暫停線程1
  2. 如何撤銷偏向鎖
  3. 如何升級輕量級鎖

第一個問題,如果不暫停線程1,即線程1的虛擬機棧還在運行,那麼就有可能影響到相關的Lock Record,從而導致異常發生。

第二個問題與第三個問題其實是一個問題,就是通過修改Mark Word的鎖標誌位(lock)與偏向鎖標誌(biased_lock)。將Mark Word修改為下面形式:

含義 thread epoll age biased_lock lock
示例 aaa…(23位bit) bb(2位bit) xxxx(4位bit) 1(1位bit ,具體值:1) 01(2位bit ,具體值:01)

在代碼進入同步塊的時候,如果鎖對象的mark word狀態為無鎖狀態,JVM首先將在當前線程的棧幀)中建立一個名為鎖記錄(Lock Record)的空間,用於存儲Displaced Mark Word(即鎖對象目前的Mark Word的拷貝)。

有資料稱:Displaced Mark Word並不等於Mark Word的拷貝,而是Mark Word的前30bit(32位系統),即Hashcode+age+biased_lock,不包含lock位。但是目前我只從網易微專業課聽到這點,而其它我看到的任何博客都沒有提到這點。所以如果有誰有確切資料,希望告知我。謝謝。

鎖的競爭者嘗試獲取鎖時,會先拷貝鎖對象的對象頭中的Mark Word複製到Lock Record,作為Displaced Mark Word。然後就是之前加鎖過程中提到到的,JVM會通過CAS操作將鎖對象的Mark Word更新為指向Lock Record的指針(這與之前提到的修改thread的CAS操作毫無關係,就是修改鎖對象的引用變量Mark Word的指向,直接指向鎖的競爭者線程的Lock Record的Displaced Mark Word)。CAS成功后,將Lock Record中的owner指針指向鎖對象的Mark Word。而這就表示鎖的競爭者嘗試獲得鎖成功,成為鎖的持有者。

而這之後,就是修改鎖的持有者線程的Lock Record的Displaced Mark Word。將Displaced Mark Word的前25bit(原identity_hashcode字段)修改為當前線程(鎖的競爭者線程)的線程ID(即Mark word的偏向鎖結構中的thread)與當前epoll時間戳(即獲得偏向鎖的epoll時間戳),修改偏向鎖標誌位(從0變為1)。

聽得有點暈暈乎乎,來,給你展示之前那位大佬的(另外我還增加了一些註釋):

輕量級鎖的加鎖過程(無鎖升級偏向鎖):

  1. 在代碼進入同步塊的時候,如果同步對象鎖狀態為無鎖狀態(鎖標誌位為“01”狀態,是否為偏向鎖為“0”),虛擬機首先將在當前線程的棧幀(即同步塊進入的地方,這個需要大家理解基於棧的編程的思想)中建立一個名為鎖記錄(Lock Record)的空間,用於存儲 Displaced Mark Word(鎖對象目前的Mark Word的拷貝)。這時候線程堆棧與對象頭的狀態如圖:

    (上圖中的Object就是鎖對象。)

  2. 拷貝對象頭中的Mark Word複製到鎖記錄中,作為Displaced Mark Word;

  3. 拷貝成功后,JVM會通過CAS操作(舊值為Displaced Mark Word,新值為Lock Record Adderss,即當前線程的鎖對象地址)將鎖對象的Mark Word更新為指向Lock Record的指針(就是修改鎖對象的引用變量Mark Word的指向,直接指向鎖的競爭者線程的Lock Record的Displaced Mark Word),並將Lock record里的owner指針指向鎖對象的Mark Word。如果更新成功,則執行步驟4,否則執行步驟5。

  4. 如果這個更新動作成功了,那麼這個線程就擁有了該對象的鎖,並且對象Mark Word的鎖標誌位設置為“00”,即表示此對象處於輕量級鎖定狀態,這時候線程堆棧與對象頭的狀態如圖所示。

    (上圖中的Object就是鎖對象。)

  5. 如果這個更新操作失敗了,虛擬機首先會檢查對象的Mark Word是否指向當前線程的棧幀,如果是就說明當前線程已經擁有了這個對象的鎖,那就可以直接進入同步塊繼續執行(這點是Synchronized為可重入鎖的佐證,起碼說明在輕量級鎖狀態下,Synchronized鎖為可重入鎖。)。否則說明多個線程競爭鎖,輕量級鎖就要膨脹為重量級鎖(其實是CAS自旋失敗一定次數后,才進行鎖升級),鎖標誌的狀態值變為“10”,Mark Word中存儲的就是指向重量級鎖(互斥量)的指針,後面等待鎖的線程也要進入阻塞狀態。 而當前線程便嘗試使用自旋來獲取鎖,自旋就是為了不讓線程阻塞,而採用循環去獲取鎖的過程。

適用的場景為線程交替執行同步塊的場景。

那麼輕量級鎖在什麼情況下會升級為重量級鎖呢?

重量級鎖:

重量級鎖是由輕量級鎖升級而來的。那麼升級的方式有兩個。

第一,線程1與線程2拷貝了鎖對象的Mark Word,然後通過CAS搶鎖,其中一個線程(如線程1)搶鎖成功,另一個線程只有不斷自旋,等待線程1釋放鎖。自旋達到一定次數(即等待時間較長)后,輕量級鎖將會升級為重量級鎖。

第二,如果線程1拷貝了鎖對象的Mark Word,並通過CAS將鎖對象的Mark Word修改為了線程1的Lock Record Adderss。這時候線程2過來后,將無法直接進行Mark Word的拷貝工作,那麼輕量級鎖將會升級為重量級鎖。

無論是同步方法,還是同步代碼塊,無論是ACC_SYNCHRONIZED(類的同步指令,可通過javap反彙編查看)還是monitorenter,monitorexit(這兩個用於實現同步代碼塊)都是基於Monitor實現的

所以,要想繼續在JVM層次學習重量級鎖,我們需要先學習一些概念,如Monitor。

Monitor
  1. 互斥同步時一種常見的併發保障手段。
  2. 同步:確保同一時刻共享數據被一個線程(也可以通過信號量實現多個線程)使用。
  3. 互斥:實現同步的一種手段
  4. 關係:互斥是因,同步是果。互斥是方法,同步是目的
  5. 主要的互斥實現手段有臨界區(Critical Section),互斥量(Mutex),信號量(Semaphore)(信號量又可以分為二進制,整型,記錄型。這裏不再深入)。其中后兩者屬於同步原語。
  6. 在Mutex和Semaphore基礎上,提出更高層次的同步原語Monitor。操作系統不支持Monitor機制,部分語言(如Java)支持Monitor機制。

這裏貼上作者的一頁筆記,幫助大家更好理解(主要圖片展示效果,比文字好)。

(請不要在意字跡問題,以後一定改正)

說白了,Java的Monitor,就是JVM(如Hotspot)為每個對象建立的一個類似對象的實現,用於支持Monitor實現(實現了Monitor同步原語的各種功能)

上面這張圖的下半部分,揭示了JVM(Hotspot)如何實現Monitor的,通過一個objectMonitor.cpp實現的。該cpp具有count,owner,WaitSet,EntryList等參數,還有monitorenter,monitorexit等方法。

看到這裏,大家應該對Monitor不陌生了。一般說的Monitor,指兩樣東西:Monitor同步原語(類似協議,或者接口,規定了這個同步原語是如何實現同步功能的);Monitor實現(類似接口實現,協議落地代碼等,就是具體實現功能的代碼,如objectMonitor.cpp就是Hotspot的Monitor同步原語的落地實現)。兩者的關係就是Java中接口和接口實現

Monitor實現重量級鎖

那麼monitor是如何實現重量級鎖的呢?其實JVM通過Monitor實現Synchronized與JDK通過AQS實現ReentrantLock有異曲同工之妙。只不過JDK為了實現更好的功能擴展,從而搞了一個AQS,使得ReentrantLock看起來非常複雜而已,後續會開一個專門的系列,寫AQS的。這裏繼續Monitor的分析。

從之前的objectMonitor.cpp的圖中,可以看出:

  • objectMonitor有兩個隊列_EntryList和_WaitSet,兩者都是用於保存objectWaiter對象的,其中**_EntryList用於保存等鎖(線程狀態為Block)的對象,而_WaitSet用於保存處於Wait線程狀態(區別於Sleep線程狀態,Wait線程狀態的對象不僅會讓出CPU,還會釋放已佔用的同步鎖資源)的對象**。
  • _owner表示當前持有同步鎖的objectWaiter對象。
  • _count則表示作為可重入鎖的Synchronized的重入次數(否則,如何確定持有鎖的線程是否完成了釋放鎖的操作呢)。
  • monitorenter與monitorexit主要負責加鎖與釋放鎖的操作,不過由於Synchronized的可重入機制,所以需要對_count進行修改,並根據_count的值,判斷是否釋放鎖,是否進行加鎖等流程。

這個部分的代碼邏輯不需要太過深入理解,只需要清楚明白關鍵參數的意義,以及大致流程即可。

有關具體重量級鎖的底層ObjectMonitor源碼解析,我就不再贅述,因為有一位大佬給出(我覺得挺好的,再深入就該去看源碼了)。

如果真的希望清楚了解代碼運行流程,又覺得看源碼太過麻煩。可以查看我之後寫的有關JUC下AQS對ReentrantLock的簡化實現。看懂了那個,你會發現Monitor實現Synchronized的套路也就那樣了(我自己就是這麼過來的)。

Monitor與持有鎖的線程

看完前面一部分的人,可能對如何實現Monitor,Monitor如何實現Synchronized已經很了解了。但是,Monitor如何與持有鎖的線程產生關係呢?或者進一步問,之前提到的objectWaiter是個什麼東西?

來,上圖片。

從圖中,可以清楚地看到,ObjectWaiter * _next與ObjectWaiter * _prev(volatile就不翻譯,文章前面有),說明ObjectWaiter對象是一個雙向鏈表結構。其中通過Thread* _thread來表示當前線程(即競爭鎖的線程),通過TStates TState表示當前線程狀態。這樣一來,每個等待鎖的線程都會被封裝成OjbectWaiter對象,便於管理線程(這樣一看,就和ReentrantLock更像了。ReentrantLock通過AQS的Node來封裝等待鎖的線程)。

補充
  1. 由於新到來鎖競爭線程,會先嘗試成為鎖的持有者。在嘗試失敗后,才會切換線程狀態為Block,並進入_EntryList。這就導致新到來的競爭鎖的線程,可能在_EntryList不為空的情況下,直接持有同步鎖,所以Synchronized為不公平鎖。又由於該部分並沒有提供別的加鎖邏輯,所以Synchronized無法通過設置,改為公平鎖。具體代碼邏輯參照ReentrantLock。
  2. notify()喚醒的是_WaitSet中任意一個線程,而不是根據等待時間確定的。
  3. 對象的notifyAll()或notify()喚醒的對象,不會從_WaitSet移動到_EntryList中,而是直接參与鎖的競爭。競爭鎖失敗就繼續在_WatiSet中等待,競爭鎖成功就從_WaitSet中移除。這是從JVM性能方面考慮的:如元素在兩個隊列中移動的資源消耗,以及notify()喚醒的對象不一定能競爭鎖成功,那麼就需要再移動回_WaitSet。
  4. Monitor中線程狀態的切換是通過什麼實現的呢?首先線程狀態從根源來說,也只是一個參數而已。其次,據我所知,Hotspot的Monitor是通過park()/unpark()實現(我看到的兩份資料都是這麼寫的)。然後,Hostpot的Monitor中的park()/unpark()區別於JDK提供的park()/unpark(),兩者完全不是一個東西。但是落地到操作系統層,可能是同一個東西。最後,這方面我了解得還不是很深入,如果有誰了解,歡迎交流。

鎖的變遷

最後就是,無鎖,偏向鎖,輕量級鎖,重量級鎖之間的轉換了。

啥都別說了,上圖。

這個圖,基本就說了七七八八了。我就不再深入闡述了。

注意一點,輕量級鎖降級,不會降級為偏向鎖,而是直接降級為無鎖狀態

重量級鎖,就不用我說了。要麼上鎖,要麼沒有鎖。

鎖的優化

鎖的優化,包括自旋鎖,自適應自旋鎖,鎖消除,鎖粗化。

自旋鎖(multiple core CPU)

  • 許多情況下,共享數據的鎖定狀態持續時間較短,切換線程不值得(也許切換線程的資源消耗就超過了共享數據的鎖定持續時間帶來的資源消耗)。
  • 通過線程執行忙循環等待鎖的釋放,不讓出CPU。
  • 缺點:若鎖被其它線程長時間佔用,會帶來許多性能上的開銷。
  • 自旋的等待時間是有限制的(其中忙循環的循環次數是存在默認值的)。
  • Hotspot可通過PreBlockSpin參數,修改默認旋轉次數。

自適應自旋鎖

  • 自旋的次數難以把握,難以完美。
  • 自旋的次數不再固定(可能為零)。
  • 由前一次在同一個鎖上的自旋時間及鎖的擁有者的狀態來決定。
  • 舉例:同一個鎖對象上,自旋等待剛剛成功獲取過鎖,並且持有鎖的線程正在運行=》JVM認為該鎖自旋獲得鎖的可能性大。

鎖消除

JIT(Just In Time)編譯時,對運行上下文進行掃描,去除不可能存在競爭的鎖。

JIT(Hotspot Code):

  • 運行頻繁的代碼,將會進行編譯,轉換為機器碼。
  • JIT編譯是以method為單位的。

鎖粗化

通過擴大加鎖的範圍,避免反覆加鎖和解鎖。

總結

刨除代碼,這篇文章在已發表的文章中,應該是我花的時間最長,手打內容最多的文章了。

從開始編寫,到編寫完成,前前後后,橫跨兩個月。當然主要也是因為這段時間太忙了,沒空進行博客的編寫。

在編寫這篇博客的過程中,我自己也收穫很多,將許多原先自己認為自己懂的內容糾正了出來,也將自己對JVM的認識深度,再推進一層。

最後,願與諸君共進步

參考資料

《深入理解Java虛擬機》

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

「每日五分鐘,玩轉 JVM」:GC 概覽

前言

GC(Garbage Collection)是我們在學習 JVM 的過程中不可避免的一道坎,接下來,我們就來系統的學習一下 GC。

做一件事情之前,我們一定要去知道我們為什麼要去做,這裏不僅僅指 GC,更適用我們日常的學習和生活,知其然,知其所以然,方能百戰不殆。

下面我們先去了解為什麼要有 GC,以及 GC 在 JVM 中扮演了一個什麼樣的角色,起到了什麼的作用?

為什麼要有 GC

用過 C++ 的同學可能知道,對象所佔的內存在程序結束運行之前一直被佔用,在明確釋放之前不能分配給其它對象。如果我們不去手動的清除這些無用的對象,內存很快就被佔滿,而在 JVM 中,GC 所起到的作用就是一個清道夫,它可以幫助我們去判定哪些對象是無用對象怎麼進行垃圾收集,以及決定內存分代和內存分配的策略**。

可能有同學會問了,既然我們的 JVM 會給我們做 GC 的工作,我們為什麼還要去學習 GC 呢,一切交給 JVM 不好嗎?當然,在我們的日常情況下,我們一般不會去關心 GC 的一些細節,但是當我們遇到內存泄露,內存溢出,高併發瓶頸的時候,我們就需要去對 GC 開刀,進行更為細緻的監控和調節。

內存泄露:指程序中己動態分配的堆內存由於某種原因程序未釋放或無法釋放,造成系統內存的浪費,導致程序運行速度減慢甚至系統崩潰等嚴重後果。

內存溢出:應用系統中存在無法回收的內存或使用的內存過多,最終使得程序運行要用到的內存大於能提供的最大內存。

那麼現在問題來了,我們要進行垃圾回收,首先我們需要知道垃圾在哪

垃圾在哪

前面我們講了JVM 的運行時內存區域,知道線程可以分為線程獨佔區和線程共享區,其中線程獨佔區(程序計數器,虛擬機棧,本地方法棧)的內存生命周期是和線程保持一致,且這幾個區域分配的內存大小跟類的大小有關,也就是說,當我們的類結構固定之後,這部分的內存就不會再發生更改,且當方法或線程結束的時候,內存自然就跟隨着回收了.

而線程共享區的堆內存和方法區則不一樣,堆內存和方法區所用的內存是在編譯期間無法確定的,因為一個接口的不同實現,一個方法的不同控制條件分支所執行的代碼可能完全相反,我們只有在運行時才知道會創建哪些對象,這部分的內存的分配和回收是動態的,而我們的 GC 關注的就是該部分的內存。

打個比方來說:JVM 如果是一輛車,線程獨佔區的就像是零件,在出廠時這些零件的壽命基本上都是已知的,線程共享區就像是汽油,汽油的消耗跟我們所採用的路線有關,所以我們關注的部分就是這部分會動態變化的,比如如何開車才能更省油~

知道了垃圾在什麼位置會出現,我們下一步就需要去判定在這些區域的有哪些是垃圾~

下節預告

本節內容到這裏先告一段落,下一節我們來學習,怎麼去判定是否為垃圾~

公眾號

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享

讀《阿里工程師的自我修養》我學到這幾點

 

 

一個月之前瀏覽博客園時發現了這本《阿里工程師的自我修養》,被裡面的其中一個叫做時間管理三八理論吸引到。花了兩個星期看完這本書。看完之後覺得有種豁然開朗的感覺,恰逢自己工作兩年,結合自己兩年以來的工作感受認真思索書中所講到的經驗,覺得有一些內容還是值得記錄下來的。於是趁這個機會總結一下書里我覺得精華的內容,對自己的兩年工作做一個回顧總結,同時按照書中所介紹的一些技巧升級自己的技能。

 

結構化思維

結構化思維 = 邏輯 + 套路

邏輯

四種組織思想的邏輯關係:

1. 因果順序: 大前提,小前提,結論。

2. 步驟順序:第一、第二、第三。首先、然後,再者,最後等。

3. 結構順序:前端,後端,數據。中國,美國,瑞士等。化整為零。

4. 程度順序:最重要,次重要,不重要。

實際上,所有的邏輯關係都在這四種順序之內,只要我們的思想和表達在這四種邏輯順序之內,就是有邏輯的,否則就會顯得沒有邏輯性。

套路

套路是指我們解決問題的方法論。使用合適的方法去解決遇到的問題。如
SWOT分析法
麥肯錫7步分析法
金字塔原理
5w2h分析法

結構化思考的方式:

 1. 建立中心目標
 2. 結構化分析
確定完中心之後,我們需要構建一個結構,使用結構化的思維對問題進行分解。分解的策略就是我們上文提到的四種邏輯順序,即演繹順序,時間順序,空間順序和程度順序。

做分解的時要滿足 MECE原則,即相互獨立,完全窮盡。

個人感悟:

想要提高自己的能力,需要懂得一些解決問題的套路。我一直以為套路就是一種經驗,面對難題時先如何,再如何,分幾步走,做到自己心中有數,這就是套路。一個初出茅廬的學生可以做事毫無頭緒,但作為一個工作兩年的老鳥,做事應該有一些最基本的套路,哪怕是一個最簡單的先搞清楚問題是什麼再動手,這也是一個好套路。做一個開發,需要做的是先思考再寫代碼。面對一個新的功能時思考設計的時間不應該低於20%,思考的時間越多bug越少。

 

如何在工作中快速成長,致工程師的10個技巧

時間管理三八理論

每個人每天公平的擁有24個小時,第一個8小時用於睡覺;第二個8小時用於工作;第三個8小時用於自由支配。
人與人的差距主要由第三個8小時決定的。第三個8小時用於消費,交易還是投資有着非常大的人生差別。

個人感悟:

記得有人說過體現一個人差距的是工作之後的時間。利用工作之餘時間繼續學習的人一定能夠成長的更快。目前所在的工作單位是事業單位的編製,朝九晚五的工作時間讓我覺得這不是程序員該有的工作節奏,所以更應該珍惜好業餘的時間多學習東西才能不遊走於技術的海平面以下。”力盡不知熱 但惜夏日長“,這是我的微信簽名,出至於白居易的《觀刈麥》,农民在炎熱的夏天割麥子精疲力竭但不覺酷熱,只是珍惜夏日天長。我又何嘗不是一個IT农民工呢?每天超長待機寫代碼寫到暈沉沉,還怕自己沒有學到東西。夏天真的好快就過去了,秋天轉眼就在眼前。沒有在夏天珍惜時間,秋天註定不會有收穫。

 

貴人相助

很多時候我們會覺得身邊缺少貴人,或者貴人離自己太遠。產生這種認知離不開4個方面:

1. 自己不自信,不相信自己能夠影響他人,導致缺乏主要溝通,長期溝通。

2. 自己心態問題,自己的心態若不夠积極正向,沒有貴人敢進入你的思維空間,因為價值不匹配,很難形成認知共識。

3. 職場原因,很多時候可能你的老闆就是你的貴人,但是因為職場,因為上下級,礙於面子,礙於共組,不敢多交流,多請教。

4. 貴人來了又走了,有貴人幫你改變,但是你自己不努力,抱着過去做事的心態和方法在職場上浪跡天涯,進步不明顯,否定了他作為貴人的價值和意義。

你求助時被人之所以願意幫你,是因為他已經看到了你的價值,這種價值幫助他確定了自己的價值,或者未來你可以幫助他。

 

個人感悟:

是否在生活中遇到過貴人呢?在這一點上我覺得我還是遇到過的,讀大學時遇到一個打球好又有耐心的球友,後來跟着他學習了兩年,球技有明顯的提高。現在想想別人為什麼願意教我打球呢?第一是因為我對打球有熱情有興趣,第二我按照他交給我的技巧打球水平有明顯的提升。後來他覺的自己有教球方面的經驗就去開了一個球館。所以說貴人幫助可能是一個互利的過程。個人覺得遇到貴人不是一件很難的事情,如果你不是一個羞澀的人。在某一方面上能夠幫助到你,對於大部分人來說還是願意幫你的,只要你態度謙虛,沒有什麼原則上問題。

 

如果我是一線主管

主管大部分時間都很忙,這就要求下屬在向上管理的時候尤其要注意高效,有質量的溝通。如果我是一線主管,我更希望團隊和我交流的方式是讓我做選擇題、判斷題,而不是問答題,思考題。
如果我是一線主管,我希望下屬這樣幫助我:

1. 主動承擔團隊面臨的挑戰,給出合理的解決方案

2. 及時向我反饋經過整理的信息和數據,甚至是結論,輔助決策

3. 主動關心同事,組織學習,幫助大家進步成長

 

團隊的人可能也有集中特徵:

1. 能力強,在某領域是專家

2. 能力一般,有潛力,但是非常有积極性

3. 能力一般,主動性一般

重要&緊急的事情只能交給能力強的人去做,意願上如果有問題也要說服對方去做,因人成事,可見能力強有多重要。

重要不緊急的事情就可以借事修人,如果做得好,這個人以後就有信心了,做的不好也有能力強的人給保底,不會造成業務問題。

技術想法也可以交給有积極性的人做,這必然會佔用一些時間,那麼這個人手頭上的無關痛癢的只好交給能力一般,主動性一般的人來做。

個人感悟:

不是每個人在工作中都是領導,但一定做過別的學長,師兄。在面對學弟學妹時是不是會覺得有些學弟辦事又快又好,而有些學弟什麼事情都要吩咐他才會去做,甚至告訴他方法也做不好。學長有時就是一個領導角色,學弟按要求將布置的任務完成,就覺得這個可以了,再有餘力可以主動承擔一些擅長的任務,就覺得用起來順手,下次有事找他,有好處也找他。我覺得最好的狀態是在工作之餘改善工作中一些複雜的操作繁瑣的步驟;給整個團隊帶來一些流程上的簡化,增加開發的效率;開發小工具能有效解決某些問題。工作之餘能夠在為團隊付出一些時間,領導應該能看的到。

 

 另外在本書中還看到了一些很優秀的PPT,不會做PPT的開發不是一個好銷售,當然這句話搞笑的很純粹,但是下面的這些PPT確實讓我覺得眼前一亮,顏色搭配,圖形搭配,樸素之中掩蓋不住的一種大氣之感。不尬吹了,直接上圖~

 

 

 

 

 

 

 

 

 

 

 

 

         

 

 

 

 

 

 

 

 

 

 

 

 

 

  

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

南投搬家前需注意的眉眉角角,別等搬了再說!

Java描述設計模式(19):模板方法模式

本文源碼: ||

一、生活場景

通常一款互聯網應用的開發流程如下:業務需求,規劃產品,程序開發,測試交付。現在基於模板方法模式進行該過程描述。

public class C01_InScene {
    public static void main(String[] args) {
        DevelopApp developApp = new DevelopApp() ;
        developApp.templateMethod() ;
    }
}
/**
 * 軟件開發抽象類
 */
abstract class SoftDevelop {
    public void templateMethod(){
        //調用基本方法
        doBiz ();
        doProduct();
        doDevelop();
        doTest();
    }
    public abstract void doBiz () ;
    public abstract void doProduct () ;
    public abstract void doDevelop () ;
    public abstract void doTest () ;
}
/**
 * APP開發具體類
 */
class DevelopApp extends SoftDevelop {
    @Override
    public void doBiz() {
        System.out.println("整理App業務");
    }
    @Override
    public void doProduct() {
        System.out.println("輸出App產品");
    }
    @Override
    public void doDevelop() {
        System.out.println("進行App開發");
    }
    @Override
    public void doTest() {
        System.out.println("進行App測試");
    }
}

二、模板方法模式

1、基礎概念

模板方法模式是類的行為模式。準備一個抽象類,將部分邏輯以具體方法以及具體構造函數的形式實現,然後聲明一些抽象方法來迫使子類實現剩餘的邏輯。不同的子類可以用不同的方式實現這些抽象方法,從而對剩餘的邏輯有不同的實現。簡單說,模板方法模式定義流程中的核心的框架,而將實際的業務操作延遲到子類中,使得子類可以不改變流程的結構,但可以重定義業務程序。

2、模式圖解

3、核心角色

  • 抽象模板角色

類中實現了模板方法(template),定義流程結構,具體業務需求子類需要去實現。

  • 具體模板角色

實現父類所定義的一個或多個抽象方法,是整個流程的組成方法。抽象模板角色都可以有任意多個具體模板角色與之對應,具體模板角色都可以給出這些抽象方法的不同實現。

4、源碼實現

/**
 * 抽象模板角色
 */
abstract class AbstractTemplate {
    /**
     * 模板方法
     */
    public void templateMethod(){
        //調用基本方法
        abstractMethod();
        hookMethod();
        concreteMethod();
    }
    /**
     * 相關基本方法
     */
    protected abstract void abstractMethod();
    protected void hookMethod(){}
    private final void concreteMethod(){}
}
/**
 * 具體模板角色
 */
class ConcreteTemplate extends AbstractTemplate{
    /**
     * 基本方法的實現
     */
    @Override
    public void abstractMethod() {
    }
    /**
     * 重寫父類的方法
     */
    @Override
    public void hookMethod(){
    }
}

5、不同方法描述

  • 模板方法

定義在抽象類中的,把基本操作方法組合在一起形成一個總流程的方法,可以有任意多個模板方法。

  • 基本方法
  1. 抽象方法:抽象方法由抽象類聲明,由具體子類實現。
  2. 具體方法:具體方法由抽象類聲明並實現,而子類並不實現。
  3. 鈎子方法:鈎子方法由抽象類聲明並實現,而子類可以加以擴展。

三、JavaEE應用

HttpServlet擔任抽象模板角色,模板方法:由service()方法擔任。基本方法:由doPost()、doGet()等方法擔任。service()方法流程,省略了部分判斷邏輯。該方法調用七個do方法中的一個或幾個,完成對客戶端請求的響應。這些do方法需要由HttpServlet的具體子類提供,在JavaEE中使用時,通常會自己實現相關方法。在API的封裝是典型的模板方法模式。

protected void service(HttpServletRequest req, HttpServletResponse resp) 
    throws ServletException, IOException {
    if (method.equals("GET")) {
        this.doGet(req, resp);
    } else if (method.equals("HEAD")) {
        this.doHead(req, resp);
    } else if (method.equals("POST")) {
        this.doPost(req, resp);
    } else if (method.equals("PUT")) {
        this.doPut(req, resp);
    } else if (method.equals("DELETE")) {
        this.doDelete(req, resp);
    } else if (method.equals("OPTIONS")) {
        this.doOptions(req, resp);
    } else if (method.equals("TRACE")) {
        this.doTrace(req, resp);
    } else {
        String errMsg = lStrings.getString("http.method_not_implemented");
        Object[] errArgs = new Object[]{method};
        errMsg = MessageFormat.format(errMsg, errArgs);
        resp.sendError(501, errMsg);
    }
}

四、源代碼地址

GitHub·地址
https://github.com/cicadasmile/model-arithmetic-parent
GitEE·地址
https://gitee.com/cicadasmile/model-arithmetic-parent

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

巴西東北岸海灘 再出現原油污染

摘錄自2019年12月31日中央社報導

巴西海軍今天(31日)表示,東北部塞阿拉州(Ceara)部分海灘發現原油油污,快兩個月前,這個地區也曾被另一波浮油侵襲。

那次的污染是原油大規模外洩的一部分,在9月到11月污染了巴西東北岸數百個海灘,威脅海洋生物、觀光業和漁業,源頭至今仍是個謎。

塞阿拉聯邦大學(Federal University of Ceara)海洋研究人員卡瓦坎特(Rivelino Cavalcante)告訴新聞網站G1,和這次污染相同,於今年稍早出現在海灘上的大量油污仍沈積在海床,因為洋流的關係才跑到岸邊。

巴西政府官員曾表示,檢測顯示外洩原油的源頭是委內瑞拉,但委國否認。

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

湄公河開發不止 洞里薩湖水量大減 柬埔寨淡水漁業恐崩盤

環境資訊中心綜合外電;姜唯 編譯;林大利 審校

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

澳洲熱浪恐惡化野火危機 數千觀光客準備撤離

摘錄自2020年1月2日中央社雪梨報導

澳洲4日恐遭一波熱浪侵襲,預期會引發致命火災,數千名觀光客必須在不到48小時內撤離已被野火肆虐的沿海社區。

法新社報導,在2019年到2020年的跨年夜,災難性野火把澳洲大陸南岸燒出遍地火場,沿著海岸形成一條火帶,造成至少8人死亡,海邊小鎮被火焰包圍,度假遊客陷入困境。

澳洲新南威爾斯省(NSW)鄉村消防局公告「遊客禁停區」,從度假勝地巴特曼灣(Batemans Bay)沿著向來風景優美的東南海岸延伸約200公里,到相鄰的維多利亞省(Victoria),當局也呼籲這裡的人遠離火災威脅的地區。

當局告誡遊客在4日前離開,天氣預報預測這天會有狂風,氣溫將飆升到攝氏40度以上。

新南威爾斯省運輸部長康史坦士(Andrew Constance)接受澳洲廣播公司(ABC)訪問時說,這可能是這個地區「歷來最大規模的人員撤離」,數千人準備疏散,以避開4日可能的新一波惡火。

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

※評比南投搬家公司費用收費行情懶人包大公開

天燈惹禍!德國動物園失火 30多隻動物慘被燒死

摘錄自2020年1月2日聯合報德國報導

德國警方表示,一間動物園的猿猴館在跨年夜發生大火,導致30多隻動物死亡,懷疑是天燈惹的禍。

德新社報導,這場大火燒毀克雷菲爾德動物園(Krefeld Zoo)的猿猴館,約30多隻動物命喪火窟,包括黑猩猩、紅毛猩猩、兩隻年長大猩猩,只有兩隻黑猩猩獲救。火災也導致狐蝠和鳥類死亡。

克雷菲爾德動物園距離杜塞道夫約15公里,1日和2日不對外開放。警方懷疑可能是跨年夜當晚,有人放天燈才導致大火,並在該地區發現一些類似的天燈。

調查人員霍普曼(Gerd Hoppmann)說,天燈相當危險,可能會飛行超過一公里。另外,也呼籲在該地區放天燈的人自首。

德國之聲報導,天燈導致幾起死亡火災後,德國北萊茵-西發利亞邦2009年起禁止放天燈。

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!