澳洲伐林導致棲地減少 數十無尾熊遭安樂死

摘錄自2020年2月3日中央社報導

澳洲無尾熊的棲地遭到砍伐,導致數十隻無尾熊被施以安樂死,約80隻因受傷和挨餓接受治療,澳洲政府將介入調查。

維多利亞州(Victoria)環境廳表示,州保育當局正在鄰近海岸城鎮波特蘭(Portland)的藍膠林場調查導致數十隻無尾熊死亡的「令人痛苦事件」。那些該負責的人,會依據澳洲保護野生動物法處以高額罰款。

環境部表示,週末期間已有約80隻無尾熊被帶出林場接受醫療,其他無尾熊則不得不遭到安樂死。環保組織「地球之友」(Friends of the Earth)表示,這片林地是在去年12月遭砍伐。

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

【其他文章推薦】

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

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

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

香港將試行36輛單層電動巴士

香港特首在最新的施政報告中指出,由於電動車有助環保與能源改革,未來將更加普及,因此香港也將持續增加電動巴士,今年預計試行36輛單層電動巴。

香港媒體報導,香港電動車掛牌行駛數量已由2010年約100量增加至現在的接近4,000輛。呼應北京政府的相關政策,香港政府也將在公共交通方面推動電動車的使用與發展,首先將與專營巴士公司合作,在香港試採行36輛單層電動巴士。

同時,香港政府也會提升公共電動車充電樁的充電效能,並協助物業管理公司增設充電設施。目前全港有超過1,200座公共充電樁,其中包含200座中速充電設備。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

2016中國(重慶)國際新能源汽車/電動車展覽會

http:

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

2016第三屆中國(武漢)國際新能源汽車產業展覽會

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

《新能源汽車推廣應用推薦車型目錄》(第1批)

文章來源:工信部

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

澳洲降雨澆熄最嚴重野火 轉而防水災

摘錄自2020年02月07日中央通訊社澳洲報導

澳洲今(7日)傾盆大雨,為終止前所未見的野火危機帶來希望,雨水澆熄了失控延燒數個月的火勢,但也讓當局擔心隨之而來的水災。

災情最嚴重的新南威爾斯州(New South Wales)本週稍早部分地區開始降雨,並預計會持續至下週,料將澆熄數場剩餘大火,並有助於控制其他火勢。

到今天下午,新南威爾斯州沒有最危險緊急狀況或「注意和須採取行動」等級的野火,是將近半年未曾見過的狀況。

不過,隨著野火危機消退,當局現在準備可能發生的水災。澳洲氣象局(Australian Bureau of Meteorology)發布一系列水災警告,並表示新南威爾斯部分地區將出現4年來最大降雨。

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

【其他文章推薦】

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

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

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

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

全球熱帶森林儲碳能力恐退居第二 森林砍伐是主因

環境資訊中心外電;姜唯 翻譯;林大利 審校;稿源:Carbon Brief

研究發現,全世界熱帶森林從大氣中吸收二氧化碳的能力正在衰退,而位於高緯度寒冷地區的北方森林,吸收二氧化碳的速度正在增加。

這項新研究結合遙測資料和數學模型,詳細揭露1992年至2015年地球上所有生物區系的碳損失和碳吸收。研究顯示,世界上最重要的兩個陸地儲碳生態系統——熱帶雨林和北寒林,正在發生變化。

位於高緯度寒冷地區的北方森林,吸收二氧化碳的速度正在增加。照片來源:IBM(CC BY-NC-ND 2.0)

作者表示,研究探討的這段期間,熱帶森林碳損失的主要驅動力是森林砍伐。受影響特別嚴重的地區包括、印尼和東南亞。

另一位科學家表示,目前還不清楚是什麼因素使北寒林的碳增加。一個可能性是二氧化碳施肥效應,也就是大氣中二氧化碳含量增加促進植物生長。

整體而言,研究結果顯示熱帶森林吸收二氧化碳的能力衰退,相當令人擔憂。人類活動產生的溫室氣體排放約有30%被土地吸收,使其成為重要的「」。當樹木和其他類型的植群進行光合作用時,土地從大氣中吸收二氧化碳。植物利用二氧化碳長出枝條、根和葉。也就是說,只要植物還活著,就是長期的碳匯。

熱帶森林、北寒林合計佔土地儲碳的53% 其中北寒林的貢獻逐漸增加

這項新的發表在《自然生態與演化(Nature Ecology and Evolution)》期刊上,使用多種技術來繪製1992年至2015年全球所有生物區系的碳損失和碳吸收狀況,包括旱地、稀疏植生地、苔原(一種北極環境),以及溫帶林、北寒林和熱帶森林。

(溫帶森林所在地氣候溫和,四季分明,而北寒林則位於較冷的高緯度地區,特徵是常綠的松樹、雲杉和落葉松。)

下圖顯示了研究中所含的各種生物區系分布。溫帶、寒帶和熱帶生態系統進一步細分為「低矮植生地」或「森林」。「低矮植生地」表示未被原始森林覆蓋,地景以草、農田、灌木或莽原為主的區域。

1992-2015年世界陸地生物群系分布,包括稀疏植生地(黃色)、苔原(藍色)、北寒低矮植生地(淺綠色)、北寒林(深綠色)、溫帶低矮植生地(橄欖)、溫帶森林(黑色)、旱地 (橙色)、熱帶低矮植生地(綠松石)、熱帶森林(深藍色)和裸露地(灰色)。 資料來源:Tagesson et al. (2020)

作者結合數學模型和遙測資料分析每個地區的碳儲量。這些資料來自微波偵測「地表生物量」變化的衛星。地表生物量是覆蓋陸地表面的所有活植物的生物量,包含樹枝、樹葉、樹幹和落葉。

呼應過去研究,該研究發現熱帶森林和北寒林是最重要的儲碳生物群落。研究期間範圍內,這兩個生物群系合計佔土地儲碳的一半以上(53%)。

然而,來自瑞典隆德大學的主要作者托本.塔格森(Torben Tagesson)博士說,這兩個地區的碳儲存能力有所差異。「研究結果讓我們掌握二氧化碳吸收量在世界各地的分布情況,並顯示熱帶森林的貢獻正在大大減少。同時北寒林的貢獻正在增加。」

整體而言,土地碳匯在研究期間內有所增加,主要是因為北寒林吸收了更多的二氧化碳。研究發現,從1992年至2015年,陸地儲碳增加了10億噸。

下圖更深入地說明了這種差異。它顯示1992年至2015年期間,北寒林(黑線)和熱帶森林(紅線)對陸地碳匯的貢獻。上色區域表示誤差範圍。作者說,熱帶森林的不確定性較大,因為它們對影響碳損失和獲取的因素更加敏感。

1992-2015年,北寒林(黑色)和熱帶森林(紅色)對陸域碳匯的貢獻。上色區域顯示不確定性範圍。資料來源:Tagesson et al. (2020)

該圖顯示,熱帶森林很可能從陸地碳匯的最大貢獻者變為第二大,輸給了北寒林。塔格森說,下降的主要原因是熱帶地區的森林砍伐。「人為土地利用和土地覆蓋變化對熱帶森林的儲碳能力有很大的影響。」

推測施肥作用增加北寒林吸碳 研究:最快2030年就會失效

值得一提的是,該研究僅探討到2015年,但此後,世界各地熱帶森林砍伐都在加速。去年年底,巴西亞馬遜地區的森林砍伐達到十年來新高,而中非和西非的森林砍伐也達到高點。

另一份發表在《自然永續性(Nature Sustainability)》期刊的發現,從2008年到2014年,巴西亞馬遜流域的次生林損失增加了一倍以上,導致釋放出26億噸碳(「次生林」是指近期重新生長而成的森林)。

研究發現,熱帶地區的碳損失還受到乾旱等氣象因素的影響。會導致樹木死亡並加劇野火的危險。

巴西亞馬遜流域的次生林損失增加了一倍以上,導致釋放出26億噸碳。照片來源:CIAT/NeilPalmer(CC BY-SA 2.0)

塔格森說,北寒林吸收二氧化碳速度變快的原因難以理解。研究者發現,在研究期間內,土地利用變遷和氣象因素在北寒林碳儲量的增加上僅扮演次要角色。但是,他認為二氧化碳施肥效應可能發揮了作用。植物在光合作用中使用二氧化碳,因此,隨著人類釋放出更多的二氧化碳,植物似乎生長得更快,並且儲存更多的碳。

就算在研究期間,二氧化碳施肥作用增強了北方森林吸收二氧化碳的能力,但這種作用可能會減緩甚至逆轉。未參與研究的慕尼黑科技大學地表相互作用學者安賈.拉米格(Anja Rammig)教授說:

「問題在於:這些碳能在森林中保留多久?可能很快就會流失,因為樹木長得越快,就會死得越早。如果樹木提早死亡,可能10或20年後換看到一個完全逆轉的局面。」她評論,這項新研究相當紮實地描繪出陸域碳匯變化的全貌。「因為作者研究的是地表生物量,而不僅僅是林業研究中經常使用的『綠化』效果。」

「綠化」是從上而下測量土地隨著時間的推移而變綠的程度,通常來自高解析度衛星影像。另一方面,地表生物量是覆蓋陸地表面所有活植物的質量。這考慮了所有生物量,而非上到下的估計,因此是測量森林碳匯更完整的方法。

未參與研究的波士頓大學氣候森林動態學者藍格.米尼(Ranga Myneni)教授表示:「這項研究的價值在於釐清主要生物區系對陸地碳匯的貢獻,以及這些貢獻隨時間的變化。」

Tropical forests losing ability to absorb CO2, study says by DAISY DUNNE

The world’s tropical forests are losing their ability to remove CO2 from the atmosphere, while boreal forests are absorbing emissions at an increasingly fast rate, a study finds.

The new analysis uses a combination of remote-sensing data and modelling to create a detailed picture of carbon loss and gain across all of Earth’s biomes from 1992 to 2015.

It shows a diverging picture in the world’s two most important ecosystems for storing carbon on land: tropical rainforests and “boreal” forests, which are found in the cold climate of the high latitudes.

The chief driver of carbon loss in tropical forests over the study period was deforestation. Particularly affected areas are likely to include the Amazon, Indonesia and southeastern Asia, the lead author tells Carbon Brief.

It is not fully clear what is driving carbon gains in boreal forests, another scientist tells Carbon Brief. However, one likely driver is the “CO2 fertilisation effect” – a term describing how increasing CO2 levels in the atmosphere can boost plant growth.

Overall, the findings paint a comprehensive picture of a “worrying” shift in the ability of tropical forests to absorb CO2 emissions, she adds.

Around 30% of the greenhouse gas emissions from human activity are absorbed by the land – making it an important “carbon sink”.

The land takes in CO2 from the atmosphere when trees and other types of vegetation carry out photosynthesis, the process where plants use CO2 to build new materials, such as shoots, roots and leaves. This means that, as long as plants are alive, they can act as long-term “sinks” of CO2.

The new study, published in Nature Ecology and Evolution, uses a host of techniques to create a detailed picture of carbon loss and gain from 1992 to 2015 across all of the world’s biomes, which include drylands, sparse land, tundra (an Arctic environment) and temperate, boreal and tropical regions.

(“Temperate” forests are found in moderate climates and are known for experiencing four seasons, while boreal forests are found in cooler high-latitude regions and are characterised by evergreen pines, spruces and larches.)

The map below shows the distribution of the various biomes included in the study. Temperate, boreal and tropical ecosystems are further broken down into “low” or “forest”. “Low” is used to indicate regions that are not covered by primary forest, but instead made up of grass, croplands, shrubland or savannah.

The distribution of the world’s terrestrial biomes from 1992-2015, including sparse (yellow), tundra (blue), boreal low (light green), boreal forest (dark green), temperate low (olive), temperate forest (black), drylands (orange), tropical low (turquoise), tropical forest (dark blue) and bare (grey). Source: Supplementary information, Tagesson et al. (2020)

To analyse carbon storage in each region, the authors use a combination of modelling and remote-sensing data. This data comes from satellites that use microwaves to detect changes in “above-ground biomass” – a measure of all the living plant matter that covers the land’s surface, including branches, leaves, trunks and fallen foliage.

In line with previous research, the study finds that tropical and boreal forests are the most important biomes for storing carbon. Together, these two biomes accounted for more than half (53%) the carbon held by land over the study period.

However, these two regions are now showing “divergence” in their ability to store carbon, says Dr Torben Tagesson, study lead author and researcher at Lund University in Sweden. He tells Carbon Brief:

“This study gives us an insight in how this CO2 uptake is distributed across the world – and we show that the contribution of the tropical forests is substantially decreasing. At the same time, the contribution of boreal forests is increasing.”

Overall, the land carbon sink increased over the study period – largely as a result of the boreal forests absorbing more CO2, he adds. The study finds that the land sink grew by an additional 1bn tonnes of carbon from 1992-2015.

The chart below gives a more in-depth picture of this divergence. It shows the contribution of boreal (black line) and tropical (red line) forests to the land carbon sink from 1992 to 2015.

The shaded areas show the margins of error. (The uncertainties are larger for tropical forests because they are more sensitive to individual drivers of carbon loss and gain, the authors say.)

The contribution of boreal (black) and tropical (red) forests to the land carbon sink from 1992-2015. The shadow areas show margins of uncertainty. Source: Tagesson et al. (2020)

The chart shows how tropical forests have likely gone from being the largest contributor to the land carbon sink to the second largest behind boreal forests.

The primary reason for this decline is deforestation in tropical regions, says Tagesson:

“We can clearly see that the anthropogenic land use and land cover change have a big impact for the contribution of tropical forests.”

It is worth noting that the study only looks at changes up until 2015 – and since then tropical deforestation has accelerated in many parts of the world, he adds.

Late last year deforestation of the Brazillian Amazon reached its highest level in a decade, while central and west Africa also saw a spike in forest loss.

A second study published today in Nature Sustainability finds that loss of “secondary forest” in the Brazillian Amazon more than doubled from 2008 to 2014 – causing the release of 2.6bn tonnes of carbon. (“Secondary forest” refers to forest that was replanted relatively recently.)

Carbon loss in tropical regions was also affected by “meteorological factors” such as droughts – which cause tree deaths and worsen the risk of wildfires, the study finds.

The reason why boreal forests are absorbing CO2 at an increasingly fast rate is more difficult to tease out, says Tagesson. The study finds that both land-use change and meteorological factors played “minor roles” in the observed increase in boreal carbon storage over the study period.

However, it is likely that the “CO2 fertilisation effect” is playing a role, he adds. Plants use CO2 in photosynthesis and, so, as humans emit more of it, it appears that plants are growing faster – and storing more carbon.

Though the CO2 fertilisation effect has boosted the ability of boreal forests to absorb CO2 over the study period, it is possible that this effect may slow down or even reverse, says Prof Anja Rammig, a researcher of land-surface interactions from the Technical University of Munich, who was not involved in the study. She tells Carbon Brief:

“The question is: How long will this carbon stay in forests? It could be that this carbon gets lost earlier because if trees are growing faster, they could die younger. If trees are dying younger, we could expect to see a completely reversed picture in 10 or 20 years.”

The new study is “very solid” and creates a “comprehensive picture” of how the land carbon sink is changing, she adds. “A real strength is the authors look at above-ground biomass, rather than just ‘greening’, which is often used in forestry studies.”

“Greening” is a top-down measurement of how much more green the land has become over time. It is often derived from satellites that can create high-resolution images.

On the other hand, above-ground biomass is a measure of all the living plant matter that covers the land’s surface. Because it takes into account all biomass, rather than making a top-down estimate, it can be seen as a more complete way of measuring forest carbon, she says.

The high-resolution tools used in the study make it “extremely novel”, agrees Prof Ranga Myneni, a researcher of climate-forest dynamics from Boston University, who was not involved in the research. He tells Carbon Brief:

“I think the value of this study is in being able to tease out contributions of different biomes to the land carbon sink and then look at the temporal dynamics of those contributions, principally in the case of tropical and boreal forests.”

※ 全文及圖片詳見:()

參考資料

  • Tagesson, T., Schurgers, G., Horion, S. et al. Recent divergence in the contributions of tropical and boreal forests to the terrestrial carbon sink.(2020),
  • Wang, Y., Ziv, G., Adami, M. et al. Upturn in secondary forest clearing buffers primary forest loss in the Brazilian Amazon. Nat Sustain (2020).

※ 本文與 行政院農業委員會 林務局  合作刊登

作者

如果有一件事是重要的,如果能為孩子實現一個願望,那就是人類與大自然和諧共存。

於特有生物研究保育中心服務,小鳥和棲地是主要的研究對象。是龜毛的讀者,認為龜毛是探索世界的美德。

延伸閱讀

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

【其他文章推薦】

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

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

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

2016年電動車和插電式混合動力車銷量預計將超過70萬輛

中汽協日前預測,2016年全國電動汽車和插電式混合動力車的銷量預計將超過70萬輛,較2015年的銷量增長一倍。

2015年電動車和插電式混合動力車的合併銷量為331092輛,較2014年增長了340%。其中包括247482輛電動車和83610輛插電式混合動力車,在24萬輛多的電動汽車銷量中,包括146719輛乘用車,另有100763輛為商用車。插電式混合動力車的銷量中,60663輛為乘用車,22947輛為商用車。

根據2015年起草的藍圖,政府計畫到2020年在全國範圍內新建12000個充電站和480枚充電樁。2014年年底,全國共有780個充電站共31000枚充電樁。2015年政府還為27個省市自治區設定了電動車的最低銷量目標。

政府預計,這些措施到位後,自主品牌車企的電動車和插電式混合動力車銷量到2020年可達100萬輛,到2025年可達300萬輛。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

[ASP.NET Core 3框架揭秘] 依賴注入[10]:與第三方依賴注入框架的適配

.NET Core具有一個承載(Hosting)系統,承載需要在後台長時間運行的服務,一個ASP.NET Core應用僅僅是該系統承載的一種服務而已。承載系統總是採用依賴注入的方式來消費它在服務承載過程所需的服務。對於承載系統來說,原始的服務註冊總是體現為一個IServiceCollection集合,最終的依賴注入容器則體現為一個IServiceProvider對象,如果要將第三方依賴注入框架整合進來,就需要利用它們解決從IServiceCollection集合到IServiceProvider對象之間的適配問題。

一、IServiceCollection =>ContainerBuilder=>IServiceProvider

具體來說,我們可以在IServiceCollection集合和IServiceProvider對象之間設置一個針對某個第三方依賴注入框架的ContainerBuilder對象。我們先利用包含原始服務註冊的IServiceCollection集合來創建一個ContainerBuilder對象,再利用該對象來構建作為依賴注入容器的IServiceProvider對象。

二、 IServiceProviderFactory<TContainerBuilder>

如上圖所示的兩種轉換是利用一個IServiceProviderFactory<TContainerBuilder>對象完成的。如下面的代碼片段所示,IServiceProviderFactory<TContainerBuilder>接口定義了兩個方法,其中CreateBuilder方法利用指定的IServiceCollection集合創建出對應的ContainerBuilder對象,而CreateServiceProvider方法則進一步利用這個ContainerBuilder對象創建出作為依賴注入容器的IServiceProvider對象。

public interface IServiceProviderFactory<TContainerBuilder>
{
    TContainerBuilder CreateBuilder(IServiceCollection services);
    IServiceProvider CreateServiceProvider(TContainerBuilder containerBuilder);
}

.NET Core的承載系統總是利用註冊的IServiceProviderFactory<TContainerBuilder>服務來創建最終作為依賴注入容器的IServiceProvider對象。承載系統默認註冊的是如下這個DefaultServiceProviderFactory類型。如下面的代碼片段所示,DefaultServiceProviderFactory對象會直接調用指定IServiceCollection集合的BuildServiceProvider方法創建出對應的IServiceProvider對象。

public class DefaultServiceProviderFactory :  IServiceProviderFactory<IServiceCollection>
{   
    public DefaultServiceProviderFactory()  : this(ServiceProviderOptions.Default){}
    public DefaultServiceProviderFactory(ServiceProviderOptions options) =>_options = options;

    public IServiceCollection CreateBuilder(IServiceCollection services)  => services;

    public IServiceProvider CreateServiceProvider( IServiceCollection containerBuilder) =>  containerBuilder.BuildServiceProvider(_options);
}

三、整合第三方依賴注入框架

為了讓讀者朋友對利用註冊的IServiceProviderFactory<TContainerBuilder>服務整合第三方依賴注入框架具有更加深刻的理解,我們來演示一個具體的實例。我們在《》創建了一個名為Cat的“迷你版”依賴注入框架,接下來我們將提供一個具體IServiceProviderFactory<TContainerBuilder>實現類型完成對它的整合。

我們首先創建一個名為CatBuilder的類型作為對應的ContainerBuilder。由於需要涉及針對服務範圍的創建,我們在CatBuilder類中定了如下兩個內嵌的私有類型,其中表示服務範圍的ServiceScope對象實際上就是對一個IServiceProvider對象的封裝,另一個ServiceScopeFactory類型表示創建該對象的工廠,它是對一個Cat對象的封裝。

public class CatBuilder
{    
    private class ServiceScope : IServiceScope
    {
        public ServiceScope(IServiceProvider serviceProvider) => ServiceProvider = serviceProvider;
        public IServiceProvider ServiceProvider { get; }
        public void Dispose()=> (ServiceProvider as IDisposable)?.Dispose();
}

    private class ServiceScopeFactory : IServiceScopeFactory
    {
        private readonly Cat _cat;
        public ServiceScopeFactory(Cat cat) => _cat = cat;
        public IServiceScope CreateScope() => new ServiceScope(_cat);
    }
}

一個CatBuilder對象是對一個Cat對象的封裝,它的BuildServiceProvider方法會直接返回這個Cat對象,並作為最終提供的依賴注入容器。CatBuilder在初始化過程中添加了針對IServiceScopeFactory接口的服務註冊,具體註冊的是根據作為當前子容器的Cat對象創建的ServiceScopeFactory對象。為了實現程序集範圍內的批量服務註冊,我們為CatBuilder定義了一個Register方法。

public class CatBuilder
{
    private readonly Cat _cat;
    public CatBuilder(Cat cat)
    {
        _cat = cat;
        _cat.Register<IServiceScopeFactory>( c => new ServiceScopeFactory(c.CreateChild()), Lifetime.Transient);
    }
    public IServiceProvider BuildServiceProvider() => _cat;
    public CatBuilder Register(Assembly assembly)
    {
        _cat.Register(assembly);
        return this;
    }
    ...
}

如下所示的CatServiceProviderFactory類型實現了IServiceProviderFactory<CatBuilder>接口。在實現的CreateBuilder方法中,我們創建了一個Cat對象,並將指定IServiceCollection集合包含中的服務註冊(ServiceDescriptor對象)轉換成兼容Cat的服務註冊(ServiceRegistry對象)並應用到創建的Cat對象上。我們最終利用這個Cat對象創建出返回的CatBuilder對象。實現的另一個方法CreateServiceProvider返回的是調用CatBuilder對象的CreateServiceProvider方法得到的IServiceProvider對象。

public class CatServiceProviderFactory : IServiceProviderFactory<CatBuilder>
{
    public CatBuilder CreateBuilder(IServiceCollection services)
    {
        var cat = new Cat();
        foreach (var service in services)
        {
            if (service.ImplementationFactory != null)
            {
                cat.Register(service.ServiceType, provider ) => service.ImplementationFactory(provider),   service.Lifetime.AsCatLifetime());
            }
            else if (service.ImplementationInstance != null)
            {
                cat.Register(service.ServiceType, service.ImplementationInstance);
            }
            else
            {
                cat.Register(service.ServiceType, service.ImplementationType,    service.Lifetime.AsCatLifetime());
            }
        }
        return new CatBuilder(cat);
    }
    public IServiceProvider CreateServiceProvider(CatBuilder containerBuilder) => containerBuilder.BuildServiceProvider();
}

Cat具有.NET Core依賴注入框架一致的服務生命周期表達方式,所以我們在將服務註冊從ServiceDescriptor類型轉化成ServiceRegistry類型時,可以實現直接完成兩種生命周期模式的轉換,具體的轉換實現在如下這個AsCatLifetime擴展方法中。

internal static class Extensions
{
    public static Lifetime AsCatLifetime(this ServiceLifetime lifetime)
    {
        return lifetime switch
        {
            ServiceLifetime.Scoped => Lifetime.Self,
            ServiceLifetime.Singleton => Lifetime.Root,
            _ => Lifetime.Transient,
        };
    }
}

接下來我們演示如何利用CatServiceProviderFactory來創建作為依賴注入容器的IServiceProvider對象。我們定義了如下的接口和對應的實現類型,其中Foo、Bar、Baz和Qux類型分別實現了對應的接口IFoo、IBar、IBaz和IQux,其中Qux類型上標註了一個MapToAttribute特性註冊了與對應接口IQux之間的映射。為了反映Cat對服務實例生命周期的控制,我們讓它們派生於同一個基類Base。Base實現了IDisposable接口,我們在其構造函數和實現的Dispose方法中輸出相應的文本以確定對應的實例何時被創建和釋放。

public interface IFoo {}
public interface IBar {}
public interface IBaz {} 
public interface IQux {}
public interface IFoobar<T1, T2> {}
public class Base : IDisposable
{
    public Base()  => Console.WriteLine($"Instance of {GetType().Name} is created.");
    public void Dispose()  => Console.WriteLine($"Instance of {GetType().Name} is disposed.");
}

public class Foo : Base, IFoo{ }
public class Bar : Base, IBar{ }
public class Baz : Base, IBaz{ } 
[MapTo(typeof(IQux), Lifetime.Root)]
public class Qux : Base, IQux { }
public class Foobar<T1, T2>: IFoobar<T1,T2>
{
    public IFoo Foo { get; }
    public IBar Bar { get; }
    public Foobar(IFoo foo, IBar bar)
    {
        Foo = foo;
        Bar = bar;
    }
}

在如下所示的演示程序中,我們創建了一個ServiceCollection集合,並採用三種不同的生命周期模式分別添加了針對IFoo、IBar和IBaz接口的服務註冊。我們接下來根據這個ServiceCollection集合創建了一個CatServiceProviderFactory對象,並調用其CreateBuilder方法創建出對應的CatBuilder對象。我們隨後調用了CatBuilder對象的Register方法完成了針對當前入口程序集的批量服務註冊,其目的在於添加針對IQux/Qux的服務註冊。

class Program
{
    static void Main()
    {
        var services = new ServiceCollection()
            .AddTransient<IFoo, Foo>()
            .AddScoped<IBar>(_ => new Bar())
            .AddSingleton<IBaz>(new Baz());

        var factory = new CatServiceProviderFactory();
        var builder = factory.CreateBuilder(services)
            .Register(Assembly.GetEntryAssembly());
        var container = factory.CreateServiceProvider(builder);

        GetServices();
        GetServices();
        Console.WriteLine("\nRoot container is disposed.");
        (container as IDisposable)?.Dispose();

        void GetServices()
        {
            using (var scope = container.CreateScope())
            {
                Console.WriteLine("\nService scope is created.");
                var child = scope.ServiceProvider;

                child.GetService<IFoo>();
                child.GetService<IBar>();
                child.GetService<IBaz>();
                child.GetService<IQux>();

                child.GetService<IFoo>();
                child.GetService<IBar>();
                child.GetService<IBaz>();
                child.GetService<IQux>();                     
                Console.WriteLine("\nService scope is disposed.");
            }
        }
    }
}

在調用CatServiceProviderFactory對象的CreateServiceProvider方法創建出作為依賴注入容器的IServiceProvider對象之後,我們先後兩次調用了本地方法GetServices方法。GetServices方法會利用這個IServiceProvider對象創建一個服務範圍,並利用此服務範圍內的IServiceProvider提供兩組服務實例。通過CatServiceProviderFactory創建的IServiceProvider對象在最終通過調用其Dispose方法進行釋放。該程序運行之後會在控制台上輸出如圖4-16所示的結果,輸出結果體現的服務生命周期與演示程序體現的是完全一致的。

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

【其他文章推薦】

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

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

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

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

別翻了,這篇文章絕對讓你深刻理解java類的加載以及ClassLoader源碼分析【JVM篇二】

目錄

@
前言
你是否真的理解java的類加載機制?點進文章的盆友不如先來做一道非常常見的面試題,如果你能做出來,可能你早已掌握並理解了java的類加載機制,若結果出乎你的意料,那就很有必要來了解了解java的類加載機制了。代碼如下

package com.jvm.classloader;

class Father2{
    public static String strFather="HelloJVM_Father";

    static{
        System.out.println("Father靜態代碼塊");
    }
}

class Son2 extends Father2{
    public static String strSon="HelloJVM_Son";

    static{
        System.out.println("Son靜態代碼塊");
    }
}

public class InitativeUseTest2 {
    public static void main(String[] args) {

       System.out.println(Son2.strSon);
    }
}
運行結果:
        Father靜態代碼塊
        Son靜態代碼塊
        HelloJVM_Son

嗯哼?其實上面程序並不是關鍵,可能真的難不倒各位,不妨做下面一道面試題可好?如果下面這道面試題都做對了,那沒錯了,這篇文章你就不用看了,真的。

package com.jvm.classloader;

class YeYe{
    static {
        System.out.println("YeYe靜態代碼塊");
    }
}

class Father extends YeYe{
    public static String strFather="HelloJVM_Father";

    static{
        System.out.println("Father靜態代碼塊");
    }
}

class Son extends Father{
    public static String strSon="HelloJVM_Son";

    static{
        System.out.println("Son靜態代碼塊");
    }
}

public class InitiativeUse {
    public static void main(String[] args) {
        System.out.println(Son.strFather); 
    }
}

各位先用“畢生所學”來猜想一下運行的結果是啥…

注意了…
注意了…
注意了…

運行結果:
    YeYe靜態代碼塊
    Father靜態代碼塊
    HelloJVM_Father

是對是錯已經有個數了吧,我就不拆穿各位的小心思了…

以上的面試題其實就是典型的java類的加載問題,如果你對Java加載機制不理解,那麼你可能就錯了上面兩道題目的。這篇文章將通過對Java類加載機制的講解,讓各位熟練理解java類的加載機制。

其實博主還是想在給出一道題,畢竟各位都已經有了前面兩道題的基礎了,那麼請看代碼:

package com.jvm.classloader;

class YeYe{
    static {
        System.out.println("YeYe靜態代碼塊");
    }
}

class Father extends YeYe{
    public final static String strFather="HelloJVM_Father";

    static{
        System.out.println("Father靜態代碼塊");
    }
}

class Son extends Father{
    public static String strSon="HelloJVM_Son";

    static{
        System.out.println("Son靜態代碼塊");
    }
}

public class InitiativeUse {
    public static void main(String[] args) {
        System.out.println(Son.strFather);
    }
}

注意了
注意了
注意了

運行結果:HelloJVM_Father

衝動的小白童鞋看到了運行結果,果斷的註銷了博客賬戶….

1、什麼是類的加載(類初始化)

JVM重要的一個領域:類加載

當程序主動使用某個類時,如果該類還未被加載到內存中,則JVM會通過加載、連接、初始化3個步驟來對該類進行初始化。如果沒有意外,JVM將會連續完成3個步驟,所以有時也把這個3個步驟統稱為類加載或類初始化。

而類加載必然涉及類加載器,下面我們先來了解一下類的加載。

類的加載(類初始化):

1、在java代碼中,類型加載連接、與初始化過程都是在程序運行期間完成的(類從磁盤加載到內存中經歷的三個階段)【牢牢記在心裏】

 

2、提供了更大的靈活性,增加了更多的可能性

雖然上面的第一句話非常簡短,但是蘊含的知識量卻是巨大的!包含兩個重要的概念:

1、類型

定義的類、接口或者枚舉稱為類型而不涉及對象,在類加載的過程中,是一個創建對象之前的一些信息

2、程序運行期間

程序運行期間完成典型例子就是動態代理,其實很多語言都是在編譯期就完成了加載,也正因為這個特性給Java程序提供了更大的靈活性,增加了更多的可能性

1、1.類加載注意事項

1、類加載器並不需要等到某個類被 “首次主動使用” 時再加載它~關於首次主動使用這個重要概念下文將講解~
2、JVM規範允許類加載器在預料某個類將要被使用時就預先加載它
3、如果在預先加載的過程中遇到了.class文件缺失或存在錯誤,類加載器必須在程序首次主動使用該類時才報告錯誤(LinkageError錯誤)如果這個類一直沒有被程序主動使用,那麼類加載器就不會報告錯誤。

首先給各位打個預防針:可能沒有了解過JVM的童鞋可能看的很蒙,感覺全是理論的感覺,不勉強一字一句的“死看”,只要達到一種概念印象就好!等到有一定理解認識之後再回頭看一遍就好很多了,畢竟學習是一種循進漸進的過程,記住沒有捷徑!

2、類的生命周期

從上圖可知,類從被加載到虛擬機內存開始,到卸載出內存為止,它的整個生命周期包括 7 個階段,而驗證、準備、解析 3 個階段統稱為連接。

加載、驗證、準備、初始化和卸載這 5 個階段的順序是固定確定的,類的加載過程必須按照這種順序開始(注意是“開始”,而不是“進行”),而解析階段則不一定:它在某些情況下可以在初始化后再開始,這是為了支持 Java 語言的運行時綁定【也就是java的動態綁定/晚期綁定】。

2、1.加載

在上面已經提到過,加載階段是類加載的第一個階段!類的加載過程就是從加載階段開始~

加載階段指的是將類的.class文件中的二進制數據讀入到內存中,將其放在運行時數據區的方法區內,然後在堆區創建一個 java.lang.Class對象(JVM規範並未說明Class對象位於哪裡,HotSpot虛擬機將其放在方法區中),用來封裝類在方法區內的數據結構。類的加載的最終產品是位於堆區中的 Class對象, Class對象封裝了類在方法區內的數據結構,並且向Java程序員提供了訪問方法區內的數據結構的接口。

Class對象是存放在堆區的,不是方法區,這點很多人容易犯錯。類的元數據才是存在方法區的。【元數據並不是類的Class對象。Class對象是加載的最終產品,類的方法代碼,變量名,方法名,訪問權限,返回值等等都是在方法區的】

JDK7創建Class實例存在堆中;因為JDK7中JavaObjectsInPerm參數值固定為false。
JDK8移除了永久代,轉而使用元空間來實現方法區,創建的Class實例依舊在java heap(堆)中

編寫一個新的java類時,JVM就會幫我們編譯成class對象,存放在同名的.class文件中。在運行時,當需要生成這個類的對象,JVM就會檢查此類是否已經裝載內存中。若是沒有裝載,則把.class文件裝入到內存中。若是裝載,則根據class文件生成實例對象。

怎麼理解Class對象與new出來的對象之間的關係呢?

new出來的對象以car為例。可以把carClass類看成具體的一個人,而new car則是人物映像,具體的一個人(Class)是唯一的,人物映像(new car)是多個的。鏡子中的每個人物映像都是根據具體的人映造出來的,也就是說每個new出來的對象都是以Class類為模板參照出來的!為啥可以參照捏?因為Class對象提供了訪問方法區內的數據結構的接口哇,上面提及過了喔!

算了參照下面這張圖理解吧,理解是其次,重點是話說這妹砸蠻好看的。

總結:
加載階段簡單來說就是:
.class文件(二進制數據)——>讀取到內存——>數據放進方法區——>堆中創建對應Class對象——>並提供訪問方法區的接口

相對於類加載的其他階段而言,加載階段(準確地說,是加載階段獲取類的二進制字節流的動作)是可控性最強的階段,因為開發人員既可以使用系統提供的類加載器來完成加載,也可以自定義自己的類加載器來完成加載。

加載階段完成后,虛擬機外部的二進制字節流就按照虛擬機所需的格式存儲在方法區之中,而且在Java堆中也創建一個 java.lang.Class類的對象,這樣便可以通過該對象訪問方法區中的這些數據。

加載.calss文件的方式:
類的加載由類加載器完成,類加載器通常由JVM提供,這些類加載器也是前面所有程序運行的基礎,JVM提供的這些類加載器通常被稱為系統類加載器。除此之外,開發者可以通過繼承ClassLoader基類來創建自己的類加載器。通過使用不同的類加載器,可以從不同來源加載類的二進制數據,二進制數據通常有如下幾種來源:

(1)從本地系統中直接加載
(2)通過網絡下載.class文件
(3)從zip,jar等歸檔文件中加載.class文件
(4)從專用數據庫中提取.class文件
(5)將java源文件動態編譯為.class文件

2、2.驗證

驗證:確保被加載的類的正確性。
關於驗證大可不必深入但是了解類加載機制必須要知道有這麼個過程以及知道驗證就是為了驗證確保Class文件的字節流中包含的信息符合當前虛擬機的要求即可。
所以下面關於驗證的內容作為了解即可!

驗證是連接階段的第一階段,這一階段的目的是為了確保Class文件的字節流中包含的信息符合當前虛擬機的要求,並且不會危害虛擬機自身的安全。驗證階段大致會完成4個階段的檢驗動作:

文件格式驗證:驗證字節流是否符合Class文件格式的規範;例如:是否以 0xCAFEBABE開頭、主次版本號是否在當前虛擬機的處理範圍之內、常量池中的常量是否有不被支持的類型。

元數據驗證:對字節碼描述的信息進行語義分析(注意:對比javac編譯階段的語義分析),以保證其描述的信息符合Java語言規範的要求;例如:這個類是否有父類,除了 java.lang.Object之外。

字節碼驗證:通過數據流和控制流分析,確定程序語義是合法的、符合邏輯的。

符號引用驗證:確保解析動作能正確執行。

驗證階段是非常重要的,但不是必須的,它對程序運行期沒有影響,如果所引用的類經過反覆驗證,那麼可以考慮採用 -Xverifynone參數來關閉大部分的類驗證措施,以縮短虛擬機類加載的時間。

2、3.準備【重點】

當完成字節碼文件的校驗之後,JVM 便會開始為類變量分配內存並初始化。準備階段是正式為類變量分配內存並設置類變量初始值的階段,這些內存都將在方法區中分配。

這裏需要注意兩個關鍵點,即內存分配的對象以及初始化的類型

內存分配的對象:要明白首先要知道Java 中的變量有類變量以及類成員變量兩種類型,==類變量指的是被 static 修飾的變量==,而==其他所有類型的變量都屬於類成員變量==。在準備階段,JVM 只會為類變量分配內存,而不會為類成員變量分配內存。類成員變量的內存分配需要等到初始化階段才開始(初始化階段下面會講到)。

舉個例子:例如下面的代碼在準備階段,只會為 LeiBianLiang屬性分配內存,而不會為 ChenYuanBL屬性分配內存。

public static int LeiBianLiang = 666;
public String ChenYuanBL = "jvm";

初始化的類型:在準備階段,JVM 會為類變量分配內存,併為其初始化(JVM 只會為類變量分配內存,而不會為類成員變量分配內存,類成員變量自然這個時候也不能被初始化)。==但是這裏的初始化指的是為變量賦予 Java 語言中該數據類型的默認值,而不是用戶代碼里初始化的值。==

例如下面的代碼在準備階段之後,LeiBianLiang 的值將是 0,而不是 666。

public static int LeiBianLiang = 666;

注意了!!!
注意了!!!
注意了!!!

但如果一個變量是常量(被 static final 修飾)的話,那麼在準備階段,屬性便會被賦予用戶希望的值。例如下面的代碼在準備階段之後,ChangLiang的值將是 666,而不再會是 0。

public static final int ChangLiang = 666;

之所以 static final 會直接被複制,而 static 變量會被賦予java語言類型的默認值。其實我們稍微思考一下就能想明白了。

兩個語句的區別是一個有 final 關鍵字修飾,另外一個沒有。而 final 關鍵字在 Java 中代表不可改變的意思,意思就是說 ChangLiang的值一旦賦值就不會在改變了。既然一旦賦值就不會再改變,那麼就必須一開始就給其賦予用戶想要的值,因此被 final 修飾的類變量在準備階段就會被賦予想要的值。而沒有被 final 修飾的類變量,其可能在初始化階段或者運行階段發生變化,所以就沒有必要在準備階段對它賦予用戶想要的值。

如果還不是很清晰理解final和static關鍵字的話建議參閱下面博主整理好的文章,希望對你有所幫助!

2、4.解析

當通過準備階段之後,進入解析階段。解析階段是虛擬機將常量池內的符號引用替換為直接引用的過程,解析動作主要針對類或接口、字段、類方法、接口方法、方法類型、方法句柄和調用點限定符7類符號引用進行。符號引用就是一組符號來描述目標,可以是任何字面量。

直接引用就是直接指向目標的指針、相對偏移量或一個間接定位到目標的句柄。

==其實這個階段對於我們來說也是幾乎透明的,了解一下就好==。

2、5.初始化【重點】

到了初始化階段,用戶定義的 Java 程序代碼才真正開始執行。

Java程序對類的使用方式可分為兩種:主動使用被動使用。一般來說只有當對類的==首次主動使用==的時候才會導致類的初始化,所以主動使用又叫做類加載過程中“初始化”開始的時機。那啥是主動使用呢?類的主動使用包括以下六種【超級重點】

1、 創建類的實例,也就是new的方式

 

2、 訪問某個類或接口的靜態變量,或者對該靜態變量賦值(凡是被final修飾不不不其實更準確的說是在編譯器把結果放入常量池的靜態字段除外)

 

3、 調用類的靜態方法

 

4、 反射(如 Class.forName(“com.gx.yichun”))

 

5、 初始化某個類的子類,則其父類也會被初始化

 

6、 Java虛擬機啟動時被標明為啟動類的類( JavaTest ),還有就是Main方法的類會
首先被初始化

 

最後注意一點對於靜態字段,只有直接定義這個字段的類才會被初始化(執行靜態代碼塊),這句話在繼承、多態中最為明顯!為了方便理解下文會陸續通過例子講解

2、6.使用

當 JVM 完成初始化階段之後,JVM 便開始從入口方法開始執行用戶的程序代碼。這個使用階段也只是了解一下就可以了。

2、7.卸載

當用戶程序代碼執行完畢后,JVM 便開始銷毀創建的 Class 對象,最後負責運行的 JVM 也退出內存。這個卸載階段也只是了解一下就可以了。

2、8.結束生命周期

在如下幾種情況下,Java虛擬機將結束生命周期

1、 執行了 System.exit()方法

2、 程序正常執行結束

3、 程序在執行過程中遇到了異常或錯誤而異常終止

4、 由於操作系統出現錯誤而導致Java虛擬機進程終止

3、接口的加載過程

接口加載過程與類加載過程稍有不同。

==當一個類在初始化時,要求其父類全部都已經初始化過了,但是一個接口在初始化時,並不要求其父接口全部都完成了初始化,當真正用到父接口的時候才會初始化。==

4、解開開篇的面試題

package com.jvm.classloader;

class Father2{
    public static String strFather="HelloJVM_Father";

    static{
        System.out.println("Father靜態代碼塊");
    }
}

class Son2 extends Father2{
    public static String strSon="HelloJVM_Son";

    static{
        System.out.println("Son靜態代碼塊");
    }
}

public class InitativeUseTest2 {
    public static void main(String[] args) {

       System.out.println(Son2.strSon);
    }
}

運行結果:
        Father靜態代碼塊
        Son靜態代碼塊
        HelloJVM_Son

再回頭看這個題,這也太簡單了吧,由於Son2.strSon是調用了Son類自己的靜態方法屬於主動使用,所以會初始化Son類,又由於繼承關係,類繼承原則是初始化一個子類,會先去初始化其父類,所以會先去初始化父類!

再看開篇的第二個題

package com.jvm.classloader;

class YeYe{
    static {
        System.out.println("YeYe靜態代碼塊");
    }
}

class Father extends YeYe{
    public static String strFather="HelloJVM_Father";

    static{
        System.out.println("Father靜態代碼塊");
    }
}

class Son extends Father{
    public static String strSon="HelloJVM_Son";

    static{
        System.out.println("Son靜態代碼塊");
    }
}

public class InitiativeUse {
    public static void main(String[] args) {
        System.out.println(Son.strFather); 
    }
}

運行結果:
    YeYe靜態代碼塊
    Father靜態代碼塊
    HelloJVM_Father

這個題就稍微要注意一下,不過要是你看懂這篇文章,這個題也很簡單。這個題要注意什麼呢?要注意子類Son類沒有被初始化,也就是Son的靜態代碼塊沒有執行!發現了咩?那我們來分析分析…

首先看到Son.strFather,你會發現是子類Son訪問父類Father的靜態變量strFather,這個時候就千萬要記住我在歸納主動使用概念時特別提到過的一個注意點了:對於靜態字段,只有直接定義這個字段的類才會被初始化(執行靜態代碼塊),這句話在繼承、多態中最為明顯!

嗯哼,對吧,Son.strFather中的靜態字段是屬於父類Father的對吧,也就是說直接定義這個字段的類是父類Father,所以在執行 System.out.println(Son.strFather); 這句代碼的時候會去初始化Father類而不是子類Son!是不是一下子明白了?如果明白了就支持一下博主點個讚唄,謝謝~

再看開篇的第三個題

package com.jvm.classloader;

class YeYe{
    static {
        System.out.println("YeYe靜態代碼塊");
    }
}

class Father extends YeYe{
    public final static String strFather="HelloJVM_Father";

    static{
        System.out.println("Father靜態代碼塊");
    }
}

class Son extends Father{
    public static String strSon="HelloJVM_Son";

    static{
        System.out.println("Son靜態代碼塊");
    }
}

public class InitiativeUse {
    public static void main(String[] args) {
        System.out.println(Son.strFather);
    }
}

運行結果:HelloJVM_Father

這個題唯一的特點就在於final static !是的Son.strFather所對應的變量便是final static修飾的,依舊是在本篇文章中歸納的類的主動使用範疇第二點當中:訪問某個類或接口的靜態變量,或者對該靜態變量賦值(凡是被final修飾不不不其實更準確的說是在編譯器把結果放入常量池的靜態字段除外)

所以,這個題並不會初始化任何類,當然除了Main方法所在的類!於是僅僅執行了System.out.println(Son.strFather);所以僅僅打印了Son.strFather的字段結果HelloJVM_Father,嗯哼,是不是又突然明白了?如果明白了就再支持一下博主點個讚唄,謝謝~

實際上上面的題目並不能完全說明本篇文章中歸納的類的主動使用範疇第二點!這話怎麼說呢?怎麼理解呢?再來一個程序各位就更加明了了

package com.jvm.classloader;

import sun.applet.Main;

import java.util.Random;
import java.util.UUID;

class Test{
    static {
        System.out.println("static 靜態代碼塊");
    }

//    public static final String str= UUID.randomUUID().toString();
    public static final double str=Math.random();  //編譯期不確定
}


public class FinalUUidTest {
    public static void main(String[] args) {
        System.out.println(Test.str);
    }
}

請試想一下結果,會不會執行靜態代碼塊里的內容呢?

重點來了
重點來了
重點來了
重點來了

運行結果

static 靜態代碼塊
0.7338688977344875

上面這個程序完全說明本篇文章中歸納的類的主動使用範疇第二點當中的這句話:凡是被final修飾不不不其實更準確的說是在編譯器把結果放入常量池的靜態字段除外!

分析:==其實final不是重點,重點是編譯器把結果放入常量池!當一個常量的值並非編譯期可以確定的,那麼這個值就不會被放到調用類的常量池中,這時在程序運行時,會導致主動使用這個常量所在的類,所以這個類會被初始化==

到這裏,能理解完上面三個題已經很不錯了,但是要想更加好好的學習java,博主不得不給各位再來一頓燒腦盛宴,野心不大,只是單純的想巔覆各位對java代碼的認知,當然還望大佬輕拍哈哈哈,直接上代碼:

package com.jvm.classloader;

public class ClassAndObjectLnitialize {

        public static void main(String[] args) {
            System.out.println("輸出的打印語句");
        }

      public ClassAndObjectLnitialize(){

            System.out.println("構造方法");
            System.out.println("我是熊孩子我的智商=" + ZhiShang +",情商=" + QingShang);
        }

        {
            System.out.println("普通代碼塊");
        }

        int ZhiShang = 250;
        static int QingShang = 666;
        
        static
        {
            System.out.println("靜態代碼塊");
        }     

}

建議這個題不要花太多時間思考,否則看了結果你會發現自己想太多了,導致最後可能你看到結果想砸電腦哈哈哈

隔離運行結果專業跑龍套…

隔離運行結果專業跑龍套…

隔離運行結果專業跑龍套…

隔離運行結果專業跑龍套…

隔離運行結果專業跑龍套…

運行結果
        靜態代碼塊
        輸出的打印語句

怎麼樣,是不是沒有你想的那麼複雜呢?

下面我們來簡單分析一下,首先根據上面說到的觸發初始化的(主動使用)的第六點:Java虛擬機啟動時被標明為啟動類的類( JavaTest ),還有就是Main方法的類會首先被初始化

嗯哼?小白童鞋就有疑問了:不是說好有Main方法的類會被初始化的么?那怎麼好多東西都沒有執行捏?

那麼類的初始化順序到底是怎麼樣的呢?在我們代碼中,我們只知道有一個構造方法,但實際上Java代碼編譯成字節碼之後,最開始是沒有構造方法的概念的,只有==類初始化方法== 和 ==對象初始化方法== 。

這個時候我們就不得不深入理解了!那麼這兩個方法是怎麼來的呢?

類初始化方法:編譯器會按照其出現順序,收集:類變量(static變量)的賦值語句靜態代碼塊,最終組成類初始化方法。==類初始化方法一般在類初始化的時候執行。==

所以,上面的這個例子,類初始化方法就會執行下面這段代碼了:

 static int QingShang = 666;  //類變量(static變量)的賦值語句

  static   //靜態代碼塊
   {
       System.out.println("靜態代碼塊");
   }

而不會執行普通賦值語句以及普通代碼塊了

對象初始化方法:編譯器會按照其出現順序,收集:成員變量的賦值語句普通代碼塊,最後收集構造函數的代碼,最終組成對象初始化方法值得特別注意的是,如果沒有監測或者收集到構造函數的代碼,則將不會執行對象初始化方法。==對象初始化方法一般在實例化類對象的時候執行。==

以上面這個例子,其對象初始化方法就是下面這段代碼了:

    {                        
       System.out.println("普通代碼塊");    //普通代碼塊
    }
 
    int ZhiShang = 250;   //成員變量的賦值語句
    
    System.out.println("構造方法");  //最後收集構造函數的代碼
    System.out.println("我是熊孩子我的智商=" + ZhiShang +",情商=" + QingShang);

明白了類初始化方法 和 對象初始化方法 之後,我們再來看這個上面例子!是的!正如上面提到的:如果沒有監測或者收集到構造函數的代碼,則將不會執行對象初始化方法。上面的這個例子確實沒有執行對象初始化方法。忘了嗎?我們根本就沒有對類ClassAndObjectLnitialize 進行實例化!只是單純的寫了一個輸出語句。

如果我們給其實例化,驗證一下,代碼如下:

package com.jvm.classloader;

public class ClassAndObjectLnitialize {

        public static void main(String[] args) {
            new ClassAndObjectLnitialize();
            System.out.println("輸出的打印語句");
        }

      public ClassAndObjectLnitialize(){

            System.out.println("構造方法");
             System.out.println("我是熊孩子我的智商=" + ZhiShang +",情商=" + QingShang);
        }

        {
            System.out.println("普通代碼塊");
        }

        int ZhiShang = 250;
        static int QingShang = 666;
        
        static
        {
            System.out.println("靜態代碼塊");
        }      
}

運行結果:
        靜態代碼塊
        普通代碼塊
        構造方法
        我是熊孩子我的智商=250,情商=666
        輸出的打印語句     

到這裏博主必須要聲明一點了!我為什麼要用這些面試題作為這篇文章的一部分?因為關於學習有一定的方法,你可以設想一下,如果博主不涉及並分析這幾個面試題,你還有耐心看到這裏嗎?小白杠精童鞋說有。。。好的,就算有,大篇大篇的理論各位扣心自問,能掌握所有知識嗎?小白杠精童鞋說說能。。。額,就算能,那你能保證光記理論一個月不遺忘嗎?小白杠精童鞋說可以。。。我特么一老北京布鞋過去頭給你打歪(我這暴脾氣我天)。所以呢學習要帶着興趣、“目的”、“野心”!希望我這段話能對你有所幫助,哪怕是一點點…

5、理解首次主動使用

我在上面提到過Java程序對類的使用方式可分為兩種:主動使用與被動使用。一般來說只有當對類的首次主動使用的時候才會導致類的初始化,其中首次關鍵字很重要,因此特地用一小結將其講解!

怎麼理解呢?老規矩看個題:

package com.jvm.classloader;

class Father6{
    public static int a = 1;
    static {
        System.out.println("父類粑粑靜態代碼塊");
    }
}
class Son6{
    public static int b = 2;
    static {
        System.out.println("子類熊孩子靜態代碼塊");
    }
}

public class OverallTest {
    static {
        System.out.println("Main方法靜態代碼塊");
    }

    public static void main(String[] args) {
        Father6 father6;
        System.out.println("======");

         father6=new Father6();
        System.out.println("======");

        System.out.println(Father6.a);
        System.out.println("======");

        System.out.println(Son6.b);

    }
}

請試想一下運行結果

運行結果:
        Main方法靜態代碼塊
        ======
        父類粑粑靜態代碼塊
        ======
        1
        ======
        子類熊孩子靜態代碼塊
        2

分析:
首先根據主動使用概括的第六點:Main方法的類會首先被初始化。 所以最先執行Main方法靜態代碼塊,而 Father6 father6;只是聲明了一個引用不會執行什麼,當運行到father6=new Father6();的時候,看到關鍵字new並且將引用father6指向了Father6對象,說明主動使用了,所以父類Father6將被初始化,因此打印了:父類粑粑靜態代碼塊 ,之後執行 System.out.println(Father6.a);屬於訪問靜態變量所以也是主動使用,這個時候注意了,因為在上面執行father6=new Father6();的時候父類已經主動使用並且初始化過一次了,這次不再是首次主動使用了,所以Father6不會在被初始化,自然它的靜態代碼塊就不再執行了,所以直接打印靜態變量值1,而後面的System.out.println(Son6.b);同樣,也是只初始化自己,不會去初始化父類,只因為父類Father6以及不再是首次主動使用了!明白了沒?如果有疑問歡迎留言,絕對第一時間回復!

6、類加載器

喔o,終於到類加載器內容了!我們之前講的類加載都是給類加載器做的一個伏筆,在這之前講的所有類被加載都是由類加載器來完成的,可見類加載器是多麼重要。由於上面的面試題並不涉及類加載器的相關知識,所以到這裏再涉及涉及類加載器的知識!

類加載器負責加載所有的類,其為所有被載入內存中的類生成一個java.lang.Class實例對象。一旦一個類被加載入JVM中,同一個類就不會被再次載入了。正如一個對象有一個唯一的標識一樣,一個載入JVM的類也有一個唯一的標識。

關於唯一標識符:

在Java中,一個類用其全限定類名(包括包名和類名)作為標識;

 


但在JVM中,一個類用其全限定類名和其類加載器作為其唯一標識。

類加載器的任務是根據一個類的全限定名來讀取此類的二進制字節流到JVM中,然後轉換為一個與目標類對應的java.lang.Class對象實例,在虛擬機提供了3種類加載器,啟動(Bootstrap)類加載器、擴展(Extension)類加載器、系統(System)類加載器(也稱應用類加載器),如下:

站在Java開發人員的角度來看,類加載器可以大致劃分為以下三類:

啟動類加載器BootstrapClassLoader,啟動類加載器主要加載的是JVM自身需要的類,這個類加載使用C++語言實現的,是虛擬機自身的一部分,負責加載存放在 JDK\jre\lib(JDK代表JDK的安裝目錄,下同)下,或被 -Xbootclasspath參數指定的路徑中的,並且能被虛擬機識別的類庫(如rt.jar,所有的java.開頭的類均被 BootstrapClassLoader加載)。啟動類加載器是無法被Java程序直接引用的。==總結一句話:啟動類加載器加載java運行過程中的核心類庫JRE\lib\rt.jar, sunrsasign.jar, charsets.jar, jce.jar, jsse.jar, plugin.jar 以及存放在JRE\classes里的類,也就是JDK提供的類等常見的比如:Object、Stirng、List…==

擴展類加載器: ExtensionClassLoader,該加載器由 sun.misc.Launcher$ExtClassLoader實現,它負責加載 JDK\jre\lib\ext目錄中,或者由 java.ext.dirs系統變量指定的路徑中的所有類庫(如javax.開頭的類),開發者可以直接使用擴展類加載器。

應用程序類加載器: ApplicationClassLoader,該類加載器由 sun.misc.Launcher$AppClassLoader來實現,它負責加載用戶類路徑(ClassPath)所指定的類,開發者可以直接使用該類加載器,如果應用程序中沒有自定義過自己的類加載器,一般情況下這個就是程序中默認的類加載器。==總結一句話:應用程序類加載器加載CLASSPATH變量指定路徑下的類 即指你自已在項目工程中編寫的類==

線程上下文類加載器:除了以上列舉的三種類加載器,還有一種比較特殊的類型就是線程上下文類加載器。類似Thread.currentThread().getContextClassLoader()獲取線程上下文類加載器

在Java的日常應用程序開發中,類的加載幾乎是由上述3種類加載器相互配合執行的,在必要時,我們還可以自定義類加載器,因為JVM自帶的類加載器(ClassLoader)只是懂得從本地文件系統加載標準的java class文件,因此如果編寫了自己的ClassLoader,便可以做到如下幾點:

1、在執行非置信代碼之前,自動驗證数字簽名。

2、動態地創建符合用戶特定需要的定製化構建類。

3、從特定的場所取得java class,例如數據庫中和網絡中。

需要注意的是,Java虛擬機對class文件採用的是按需加載的方式,也就是說當需要使用該類時才會將它的class文件加載到內存生成class對象,而且加載某個類的class文件時,Java虛擬機默認採用的是雙親委派模式即把請求交由父類處理,它一種任務委派模式,下面將會詳細講到!

下面我們看一個程序:

package com.jvm.classloaderQi;

public class ClassloaderTest {
    public static void main(String[] args) {
        //獲取ClassloaderTest類的加載器
        ClassLoader classLoader= ClassloaderTest.class.getClassLoader(); 
        
        System.out.println(classLoader);
        System.out.println(classLoader.getParent()); //獲取ClassloaderTest類的父類加載器
        System.out.println(classLoader.getParent().getParent());
    }
}

運行結果:

sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@1b6d3586
null

從上面的結果可以看出,並沒有獲取到ExtClassLoader的父Loader,原因是Bootstrap Loader(啟動類加載器)是用C++語言實現的(這裏僅限於Hotspot,也就是JDK1.5之後默認的虛擬機,有很多其他的虛擬機是用Java語言實現的),找不到一個確定的返回父Loader的方式,於是就返回null。至於$符號就是內部類的含義。

7、關於命名空間

我覺得講類加載器,還是很有必要知道命名空間這個概念!實際上類加載器的一個必不可少的前提就是命名空間!

命名空間概念:

每個類加載器都有自己的命名空間,命名空間由該加載器及所有父加載器所加載的類組成。

 

特別注意:

在同一個命名空間中,不會出現類的完整名字(包括類的包名)相同的兩個類。

在不同的命名空間中,有可能會出現類的完整名字(包括類的包名)相同的兩個類。
由子加載器加載的類能看見父加載器的類,由父親加載器加載的類不能看見子加載器加載的類

我們已經知道每個類只能被加載一次,其實這樣說是不夠準確的,怎樣才算是準確的呢?那就涉及到命名空間的概念了!只有在相同的命名空間中,每個類才只能被加載一次,反過來說就是一個類在不同的命名空間中是可以被加載多次的,而被加載多次的Class對象是互相獨立的!

7.1、如何理解?

當然,直接把命名空間的概念直接拋給大家,如果沒有接觸過,100%是看不懂其中的含義的,我敢打包票,假一賠100萬。。。那麼博主就舉出寫例子讓各位深刻體會一下!當然這些例子涉及自定義加載器的一些知識,建議先對自定義加載器有一定了解在看!

例子必知前提:
1、 自己在idea或者eclipse中創建的工程項目中只要編譯之後都會有對應的class文件成在classPath目錄中
2、 而這些目錄是由ApplicationClassLoader應用加載器加載
3、 我之後會將class文件放到系統桌面地址上,而這些系統地址由自定義加載器指定,所以由自定義加載器加載

7.2、準備

事先編譯好,然後將項目工程中的兩個字節碼class文件【File1和File2】拷貝到系統桌面路徑上,編譯main方法就會出現在項目工程(ClassPath)下,注意以下例子情況中系統桌面路徑的class文件一直都存在!

Main方法情況:

  • 1、創建一個自定義加載器classloader2,並聲明桌面class文件路徑,接着加載File1

  • 2、打印File1的加載器
  • 3、newInstanceFile1的實例

File1類的方法情況:

  • 1、File1的構造方法中存在一行代碼:new File2new實例代碼

File2類的方法情況:

  • 1、打印File2的加載器

7.3、測試代碼情景一

刪除File1File2項目工程中的class文件,工程項目的兩個class文件都刪除(只存在系統桌面路徑下的class文件)

結果:File1File2的加載器都是自定義加載器

7.4、測試代碼情景二

只刪除File1項目工程中的class文件

結果:File1的加載器是自定義加載器,而執行到File2實例的加載器是App應用加載器

7.5、測試代碼情景三

只刪除File2項目工程中的class文件

結果:File1的加載器都是APP應用加載器,而執行到File2實例的時候報NoClassDefFoundError異常

得出結論:加載一個類(File1)的時候,這個類裏面調用了其他的類(File2)或者其他類方法的初始化代碼,那麼這裏面的類也會試着從這個類的加載器開始向上委託加載,如果全都加載不了加載不了就報NoClassDefFoundError異常

當然這樣理解命名空間和類加載機制還是遠遠不夠的!

File2類中發生改變情況如下:

  • 1、File1的構造方法中存在一行new File2的實例這沒變
  • 2、在File2的構造方法中,打印(訪問)File1的class文件

7.6、測試代碼情景四

只刪除項目工程中File1的class文件

結果:File1的加載器都是自定義加載器,而執行到File2實例的加載器是App應用加載器,當運行到File2構造方法中的打印(訪問)File1的class文件的時候報NoClassDefFoundError異常

得出結論:父親加載器加載的類(File2)不能看見子加載器加載的類(File1

File1方法發生改變情況如下:
1、Main方法中newInstanceFile1的實例,File1的構造方法中存在一行new File2的實例這都沒變
2、在File1的構造方法中,打印File2的class文件

7.7、測試代碼情景五

只刪除File1項目工程中的class文件

結果:File1的加載器都是自定義加載器,而執行到File2實例的加載器是App應用加載器,當運行到File1構造方法中的打印File2的class文件的時候沒問題

得出結論:由子加載器加載的類(File1)能看見父加載器的類(File2)

當然還要注意知道的一點的是:如果兩個加載器之間沒有直接或間接的父子關係,那麼它們各自加載類相互不可見。

當然整對上面的情況還是相當比較抽象,畢竟沒上代碼,如果有任何疑問,歡迎留言,宜春絕對第一時間回復!

8、JVM類加載機制

JVM的類加載機制主要有如下3種。

全盤負責:當一個類加載器負責加載某個Class時,該Class所依賴的和引用的其他Class也將由該類加載器負責載入,除非显示使用另外一個類加載器來載入

父類委託:先讓父類加載器試圖加載該類,只有在父類加載器無法加載該類時才嘗試從自己的類路徑中加載該類,通俗講就是兒子們都他么是懶豬,自己不管能不能做,就算能加載也先不幹,先給自己的父親做,一個一個往上拋,直到拋到啟動類加載器也就是最頂級父類,只有父親做不了的時候再沒辦法由下一個子類做,直到能某一個子類能做才做,之後的子類就直接返回,實力坑爹!

緩存機制:緩存機制將會保證所有加載過的Class都會被緩存,當程序中需要使用某個Class時,類加載器先從緩存區尋找該Class,只有緩存區不存在,系統才會讀取該類對應的二進制數據,並將其轉換成Class對象,存入緩存區。這就是為什麼修改了Class后,必須重啟JVM,程序的修改才會生效

9、雙親委派模型

雙親委派模型的工作流程是:如果一個類加載器收到了類加載的請求,它首先不會自己去嘗試加載這個類,而是把請求委託給父加載器去完成,依次向上,因此,所有的類加載請求最終都應該被傳遞到頂層的啟動類加載器中,只有當父加載器在它的搜索範圍中沒有找到所需的類時,即無法完成該加載,子加載器才會嘗試自己去加載該類。也就是實力坑爹!

雙親委派機制:

1、當AppClassLoader加載一個class時,它首先不會自己去嘗試加載這個類,而是把類加載請求委派給父類加載器ExtClassLoader去完成。

 

2、當 ExtClassLoader加載一個class時,它首先也不會自己去嘗試加載這個類,而是把類加載請求委派給BootStrapClassLoader去完成。

 

3、如果 BootStrapClassLoader加載失敗(例如在 $JAVA_HOME/jre/lib里未查找到該class),會使用

ExtClassLoader來嘗試加載;

 

4、若ExtClassLoader也加載失敗,則會使用 AppClassLoader來加載,如果

AppClassLoader也加載失敗,則會報出異常 ClassNotFoundException。

從代碼層面了解幾個Java中定義的類加載器及其雙親委派模式的實現,它們類圖關係如下:

從圖可以看出頂層的類加載器是
抽象類ClassLoader類,其後
所有的類加載器都繼承自ClassLoader(不包括啟動類加載器),為了更好理解雙親委派模型,ClassLoader源碼中的loadClass(String)方法該方法加載指定名稱(包括包名)的二進制類型,該方法在JDK1.2之後不再建議用戶重寫但用戶可以直接調用該方法,
loadClass()方法是ClassLoader類自己實現的,該方法中的邏輯就是雙親委派模式的實現,loadClass(String name, boolean resolve)是一個重載方法,resolve參數代表是否生成class對象的同時進行解析相關操作。源碼分析如下::

 public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }

protected Class<?> loadClass(String name, boolean resolve)
      throws ClassNotFoundException
  {
      synchronized (getClassLoadingLock(name)) {
          // 先從緩存查找該class對象,找到就不用重新加載
          Class<?> c = findLoadedClass(name);
          if (c == null) {
              long t0 = System.nanoTime();
              try {
                  if (parent != null) {
                      //如果找不到,則委託給父類加載器去加載
                      c = parent.loadClass(name, false);
                  } else {
                  //如果沒有父類,則委託給啟動加載器去加載
                      c = findBootstrapClassOrNull(name);
                  }
              } catch (ClassNotFoundException e) {
                  // ClassNotFoundException thrown if class not found
                  // from the non-null parent class loader
              }

              if (c == null) {
                  // If still not found, then invoke findClass in order
                  // 如果都沒有找到,則通過自定義實現的findClass去查找並加載
                  c = findClass(name);

                  // this is the defining class loader; record the stats
                  sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                  sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                  sun.misc.PerfCounter.getFindClasses().increment();
              }
          }
          if (resolve) {//是否需要在加載時進行解析
              resolveClass(c);
          }
          return c;
      }
  }

既然存在這個雙親委派模型,那麼就一定有着存在的意義,其意義主要是:Java類隨着它的類加載器一起具備了一種帶有優先級的層次關係,通過這種層級關可以避免類的重複加載,當父親已經加載了該類時,就沒有必要子ClassLoader再加載一次。其次是考慮到安全因素,java核心api中定義類型不會被隨意替換,假設通過網絡傳遞一個名為java.lang.Integer的類,通過雙親委託模式傳遞到啟動類加載器,而啟動類加載器在核心Java API發現這個名字的類,發現該類已被加載,並不會重新加載網絡傳遞的過來的java.lang.Integer,而直接返回已加載過的Integer.class,這樣便可以防止核心API庫被隨意篡改。

雙親委派模型意義總結來講就是:
1、系統類防止內存中出現多份同樣的字節碼
2、保證Java程序安全穩定運行

10、ClassLoader源碼分析

ClassLoader類是一個抽象類,所有的類加載器都繼承自ClassLoader(不包括啟動類加載器),因此它顯得格外重要,分析ClassLoader抽象類也是非常重要的!

簡單小結一下ClassLoader抽象類中一些概念:

二進制概念(Binary name):格式如下

把二進制名字轉換成文件名字,然後在文件系統中磁盤上讀取其二進制文件(class文件),每一個class對象都包含了定義了這個類的classload對象,class類都是由類加載器加載的只有數組類型是有JVM根據需要動態生成。

特別注意數組類型

1、 數組類的類對象不是由類加載器創建的,而是根據Java運行時的需要自動創建的。
2、 數組類的類加載器getClassLoader()與它的元素類型的類加載器相同;如果元素類型是基本類型,則數組類沒有類加載器也就是null,而這個null不同於根類加載器返回的null,它是單純的null。

到這裏,下面就主要分析ClassLoader抽象類中幾個比較重要的方法。

10、1.loadClass

該方法加載指定名稱(包括包名)的二進制類型,該方法在JDK1.2之後不再建議用戶重寫但用戶可以直接調用該方法,loadClass()方法是ClassLoader類自己實現的,該方法中的邏輯就是雙親委派模式的實現,其源碼如下,loadClass(String name, boolean resolve)是一個重載方法,resolve參數代表是否生成class對象的同時進行解析相關操作:

 public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }

protected Class<?> loadClass(String name, boolean resolve)
      throws ClassNotFoundException
  {
      synchronized (getClassLoadingLock(name)) {
          // 先從緩存查找該class對象,找到就不用重新加載
          Class<?> c = findLoadedClass(name);
          if (c == null) {
              long t0 = System.nanoTime();
              try {
                  if (parent != null) {
                      //如果找不到,則委託給父類加載器去加載
                      c = parent.loadClass(name, false);
                  } else {
                  //如果沒有父類,則委託給啟動加載器去加載
                      c = findBootstrapClassOrNull(name);
                  }
              } catch (ClassNotFoundException e) {
                  // ClassNotFoundException thrown if class not found
                  // from the non-null parent class loader
              }

              if (c == null) {
                  // If still not found, then invoke findClass in order
                  // 如果都沒有找到,則通過自定義實現的findClass去查找並加載
                  c = findClass(name);

                  // this is the defining class loader; record the stats
                  sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                  sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                  sun.misc.PerfCounter.getFindClasses().increment();
              }
          }
          if (resolve) {//是否需要在加載時進行解析
              resolveClass(c);
          }
          return c;
      }
  }

正如loadClass方法所展示的,當類加載請求到來時,先從緩存中查找該類對象,如果存在直接返回,如果不存在則交給該類加載去的父加載器去加載,倘若沒有父加載則交給頂級啟動類加載器去加載,最後倘若仍沒有找到,則使用findClass()方法去加載(關於findClass()稍後會進一步介紹)。從loadClass實現也可以知道如果不想重新定義加載類的規則,也沒有複雜的邏輯,只想在運行時加載自己指定的類,那麼我們可以直接使用this.getClass().getClassLoder.loadClass(“className”),這樣就可以直接調用ClassLoader的loadClass方法獲取到class對象。

10、2.findClass

在JDK1.2之前,在自定義類加載時,總會去繼承ClassLoader類並重寫loadClass方法,從而實現自定義的類加載類,但是在JDK1.2之後已不再建議用戶去覆蓋loadClass()方法,而是建議把自定義的類加載邏輯寫在findClass()方法中,從前面的分析可知,findClass()方法是在loadClass()方法中被調用的,當loadClass()方法中父加載器加載失敗后,則會調用自己的findClass()方法來完成類加載,這樣就可以保證自定義的類加載器也符合雙親委託模式。需要注意的是ClassLoader類中並沒有實現findClass()方法的具體代碼邏輯,取而代之的是拋出ClassNotFoundException異常,同時應該知道的是findClass方法通常是和defineClass方法一起使用的(稍後會分析),ClassLoader類中findClass()方法源碼如下:

//直接拋出異常
protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
}

10、3.defineClass(byte[] b, int off, int len)

defineClass()方法是用來將byte字節流解析成JVM能夠識別的Class對象(ClassLoader中已實現該方法邏輯),通過這個方法不僅能夠通過class文件實例化class對象,也可以通過其他方式實例化class對象,如通過網絡接收一個類的字節碼,然後轉換為byte字節流創建對應的Class對象,defineClass()方法通常與findClass()方法一起使用,一般情況下,在自定義類加載器時,會直接覆蓋ClassLoader的findClass()方法並編寫加載規則,取得要加載類的字節碼後轉換成流,然後調用defineClass()方法生成類的Class對象,簡單例子如下:

protected Class<?> findClass(String name) throws ClassNotFoundException {
      // 獲取類的字節數組
      byte[] classData = getClassData(name);  
      if (classData == null) {
          throw new ClassNotFoundException();
      } else {
          //使用defineClass生成class對象
          return defineClass(name, classData, 0, classData.length);
      }
  }

需要注意的是,如果直接調用defineClass()方法生成類的Class對象,這個類的Class對象並沒有解析(也可以理解為鏈接階段,畢竟解析是鏈接的最後一步),其解析操作需要等待初始化階段進行。

10、4.resolveClass (Class<?>c)

使用該方法可以使用類的Class對象創建完成也同時被解析。前面我們說鏈接階段主要是對字節碼進行驗證,為類變量分配內存並設置初始值同時將字節碼文件中的符號引用轉換為直接引用。

10、5.ClassLoader小結

以上上述4個方法是ClassLoader類中的比較重要的方法,也是我們可能會經常用到的方法。接看SercureClassLoader擴展了 ClassLoader,新增了幾個與使用相關的代碼源(對代碼源的位置及其證書的驗證)和權限定義類驗證(主要指對class源碼的訪問權限)的方法,一般我們不會直接跟這個類打交道,更多是與它的子類URLClassLoader有所關聯,前面說過,ClassLoader是一個抽象類,很多方法是空的沒有實現,比如 findClass()、findResource()等。而URLClassLoader這個實現類為這些方法提供了具體的實現,並新增了URLClassPath類協助取得Class字節碼流等功能,在編寫自定義類加載器時,如果沒有太過於複雜的需求,可以直接繼承URLClassLoader類,這樣就可以避免自己去編寫findClass()方法及其獲取字節碼流的方式,使自定義類加載器編寫更加簡潔。

檢查完父類加載器之後loadClass會去默認調用findClass方法,父類(ClassLoader)中的findClass方法主要是拋出一個異常。

findClass根據二進制名字找到對應的class文件,返回值為Class對象Class<?>

defineClass這個方法主要是將一個字節數組轉換成Class實例,會拋三個異常,但只是threws一個,因為其他兩個是運行時異常。

loadClass方法是一個加載一個指定名字的class文件,調用findLoadedClass (String)檢查類是否已經加載…如果已經加裝就不再加載而是直接返回第一次加載結果 所以一個類只會加載一次

11、自定義類加載器

自定義核心目的是擴展java虛擬機的動態加載類的機制,JVM默認情況是使用雙親委託機制,雖然雙親委託機制很安全極高但是有些情況我們需要自己的一種方式加載,==比如應用是通過網絡來傳輸 Java類的字節碼,為保證安全性,這些字節碼經過了加密處理,這時系統類加載器就無法對其進行加載,這樣則需要自定義類加載器來實現==。因此自定義類加載器也是很有必要的。

自定義類加載器一般都是繼承自 ClassLoader類,從上面對 loadClass方法來分析來看,我們只需要重寫 findClass 方法即可。自定義加載器中點:重寫findClass,下面直接看自定義類加載器代碼的流程:

package com.yichun.classloader;
import java.io.*;

public class MyClassLoader extends ClassLoader {
    private String root;

    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] classData = loadClassData(name);
        if (classData == null) {
            throw new ClassNotFoundException();
        } else {
            return defineClass(name, classData, 0, classData.length);
        }
    }

    private byte[] loadClassData(String className) {
        String fileName = root + File.separatorChar
                + className.replace('.', File.separatorChar) + ".class";
        try {
            InputStream ins = new FileInputStream(fileName);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int bufferSize = 1024;
            byte[] buffer = new byte[bufferSize];
            int length = 0;
            while ((length = ins.read(buffer)) != -1) {
                baos.write(buffer, 0, length);
            }
            return baos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    public String getRoot() {
        return root;
    }

    public void setRoot(String root) {
        this.root = root;
    }

    public static void main(String[] args)  {

        MyClassLoader classLoader = new MyClassLoader();
        classLoader.setRoot("D:\\dirtemp");

        Class<?> testClass = null;
        try {
            testClass = classLoader.loadClass("com.yichun.classloader.Demo1");
            Object object = testClass.newInstance();
            System.out.println(object.getClass().getClassLoader());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

自定義類加載器的核心在於對字節碼文件的獲取,如果是加密的字節碼則需要在該類中對文件進行解密。上面代碼程序只是簡單Demo,並未對class文件進行加密,因此省略了解密的過程。這裡有幾點需要注意:

1、這裏傳遞的文件名需要是類的全限定性名稱,即com.yichun.test.classloading.Test格式的,因為
defineClass 方法是按這種格式進行處理的。

 

2、最好不要重寫loadClass方法,因為這樣容易破壞雙親委託模式。

 

3、這類Test 類本身可以被 AppClassLoader類加載,因此我們不能把com/yichun/test/classloading/Test.class放在類路徑下。否則,由於雙親委託機制的存在,會直接導致該類由AppClassLoader加載,而不會通過我們自定義類加載器來加載。

12、加載類的三種方式

到這裏,相信大家已經對類的加載以及加載器有一定的了解了,那麼你知道嗎,其實加載類常見的有三種方式,如下:

1、靜態加載,也就是通過new關鍵字來創建實例對象。

 

2、動態加載,也就是通過Class.forName()方法動態加載(反射加載類型),然後調用類的newInstance()方法實例化對象。

 

3、動態加載,通過類加載器loadClass()方法來加載類,然後調用類的newInstance()方法實例化對象

12、1.三種方式的區別:

1、第一種和第二種方式使用的類加載器是相同的,都是當前類加載器。(this.getClass.getClassLoader)。而3由用戶指定類加載器。

2、如果需要在當前類路徑以外尋找類,則只能採用第3種方式。第3種方式加載的類與當前類分屬不同的命名空間

3、第一種是靜態加載,而第二、三種是動態加載。

12、2.兩種異常(exception)

1、靜態加載的時候如果在運行環境中找不到要初始化的類,拋出的是NoClassDefFoundError,它在JAVA的異常體系中是一個Error

2、動態態加載的時候如果在運行環境中找不到要初始化的類,拋出的是ClassNotFoundException,它在JAVA的異常體系中是一個checked異常

12、3.理解Class.forName

Class.forName()是一種獲取Class對象的方法,而且是靜態方法。

Class.forName()是一個靜態方法,同樣可以用來加載類,Class.forName()返回與給定的字符串名稱相關聯類或接口的Class對象。注意這是一種獲取Class對象的方法

官方給出的API文檔如下

publicstatic Class<?> forName(String className)

Returns the Class object associated withthe class or interface with the given string name. Invokingthis method is equivalent to:

Class.forName(className,true, currentLoader)

where currentLoader denotes the definingclass loader of the current class.

For example, thefollowing code fragment returns the runtime Class descriptor for theclass named java.lang.Thread:

Class t =Class.forName("java.lang.Thread")

A call to forName("X") causes theclass named X to beinitialized.

Parameters:

className - the fully qualifiedname of the desired class.

Returns:

the Class object for the classwith the specified name.

可以看出,Class.forName(className)實際上是調用Class.forName(className,true, this.getClass().getClassLoader())。第二個參數,是指Classloading后是不是必須被初始化。可以看出,使用Class.forName(className)加載類時則已初始化。所以Class.forName()方法可以簡單的理解為:獲得字符串參數中指定的類,並初始化該類。

12、4.Class.forName與ClassLoader.loadClass區別

首先,我們必須先明確類加載機制的三個過程主要是:加載 –> 連接 –> 初始化。

  • Class.forName():將類的.class文件加載到jvm中之外,還會對類進行解釋,執行類中的static塊;

  • ClassLoader.loadClass():只干一件事情,就是將.class文件加載到jvm中,不會執行static中的內容,只有在newInstance才會去執行static塊。

  • Class.forName(name, initialize, loader):帶參函數也可控制是否加載static塊。並且只有調用了newInstance()方法採用調用構造函數,創建類的對象 。

這個時候,我們再來看一個程序:

package com.jvm.classloader;

class Demo{
    static {
        System.out.println("static 靜態代碼塊");
    }
}

public class ClassLoaderDemo {
    public static void main(String[] args) throws ClassNotFoundException {
        ClassLoader classLoader=ClassLoaderDemo.class.getClassLoader();
        //1、使用ClassLoader.loadClass()來加載類,不會執行初始化塊
        classLoader.loadClass("com.jvm.classloader.Demo");
        
        //2、使用Class.forName()來加載類,默認會執行初始化塊
        Class.forName("com.jvm.classloader.Demo");
        
        //3、使用Class.forName()來加載類,並指定ClassLoader,初始化時不執行靜態塊 
        Class.forName("com.jvm.classloader.Demo",false,classLoader);
    }
}

記得一個一個測試!我上面的程序是一次寫了三個的並且已經標明了標號1、2、3!!!各位再自個電腦上跑一遍,思路就很會清晰了!

13、總結

類的加載、連接與初始化:

1、加載:查找並加載類的二進制數據到java虛擬機中

 

2、 連接:

驗證: 確保被加載的類的正確性
準備:為類的靜態變量分配內存,並將其初始化為默認值,但是到達初始化之前類變量都沒有初始化為真正的初始值(如果是被 final 修飾的類變量,則直接會被初始成用戶想要的值。)
解析:把類中的符號引用轉換為直接引用,就是在類型的常量池中尋找類、接口、字段和方法的符號引用,把這些符號引用替換成直接引用的過程

3、 初始化:為類的靜態變量賦予正確的初始值

 

類從磁盤上加載到內存中要經歷五個階段:加載、連接、初始化、使用、卸載

Java程序對類的使用方式可分為兩種

(1)主動使用
(2)被動使用

所有的Java虛擬機實現必須在每個類或接口被Java程序“首次主動使用”時才能初始化他們
主動使用
(1)創建類的實例
(2)訪問某個類或接口的靜態變量 getstatic(助記符),或者對該靜態變量賦值 putstatic
(3)調用類的靜態方法 invokestatic
(4)反射(Class.forName(“com.test.Test”))
(5)初始化一個類的子類
(6)Java虛擬機啟動時被標明啟動類的類以及包含Main方法的類
(7)JDK1.7開始提供的動態語言支持(了解)

被動使用
除了上面七種情況外,其他使用java類的方式都被看做是對類的被動使用,都不會導致類的初始化

14、特別注意

初始化入口方法。當進入類加載的初始化階段后,JVM 會尋找整個 main 方法入口,從而初始化 main 方法所在的整個類。當需要對一個類進行初始化時,會首先初始化類構造器(),之後初始化對象構造器()。

初始化類構造器:JVM 會按順序收集類變量的賦值語句、靜態代碼塊,最終組成類構造器由 JVM 執行。
初始化對象構造器:JVM 會按照收集成員變量的賦值語句、普通代碼塊,最後收集構造方法,將它們組成對象構造器,最終由 JVM 執行。值得特別注意的是,如果沒有監測或者收集到構造函數的代碼,則將不會執行對象初始化方法。對象初始化方法一般在實例化類對象的時候執行。

如果在初始化 main 方法所在類的時候遇到了其他類的初始化,那麼就先加載對應的類,加載完成之後返回。如此反覆循環,最終返回 main 方法所在類。

如果本文對你有一點點幫助,那麼請點個讚唄,謝謝~

最後,若有不足或者不正之處,歡迎指正批評,感激不盡!如果有疑問歡迎留言,絕對第一時間回復!

歡迎各位關注我的公眾號,一起探討技術,嚮往技術,追求技術,說好了來了就是盆友喔…

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

【其他文章推薦】

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

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

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

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