3.JVM
1.虛擬機(jī)?
可以執(zhí)行java字節(jié)碼的虛擬機(jī)進(jìn)程,
.java文件經(jīng)過編譯成能被java虛擬機(jī)執(zhí)行的.class文件
能跨平臺(tái)?
java編譯成響應(yīng)的字節(jié)碼,再不同系統(tǒng)的jvm被執(zhí)行,所有跨平臺(tái)的是java程序,而不是JVM
2.jvm組成
作用
1.程序在執(zhí)行之前要把代碼編譯成字節(jié)碼.class文件 2.jvm通過類加載器將字節(jié)碼文件加載到內(nèi)存中, 3.而字節(jié)碼文件時(shí)jvm的指令集,并不能直接交給底層操作系統(tǒng)去執(zhí)行, 4.而交給解析器執(zhí)行調(diào)用本地接口方法實(shí)現(xiàn)執(zhí)行底層系統(tǒng)指令
a 類加載器
在jvm啟動(dòng)或者類運(yùn)行時(shí),將需要class文件加載到JVM中
b 內(nèi)存區(qū)
見3
c 執(zhí)行引擎
執(zhí)行引擎的任務(wù)負(fù)責(zé)執(zhí)行class文件中包含的字節(jié)碼指令,
d 本地方法調(diào)用
3.jvm內(nèi)存區(qū)-運(yùn)行數(shù)據(jù)區(qū)
1)方法區(qū)(元空間):
jdk8已經(jīng)被取消,改成了元數(shù)據(jù),
各個(gè)線程共享的一個(gè)區(qū)域,用于存儲(chǔ)虛擬機(jī)加載的類信息,常量,靜態(tài)變量,即時(shí)編譯器編譯后的代碼等數(shù)據(jù)
jdk1.8后,字符串和常量放到堆內(nèi)存中,新增了MetaSpace
大小沒有限制,根據(jù)內(nèi)存大小,動(dòng)態(tài)改變;也可以通過-XX:MetaspaceSize-初始化大小;-XX:MaxMetaspace-最大值,
2)堆內(nèi)存
各個(gè)線程共享的一個(gè)區(qū)域,垃圾收集器管理的主要區(qū)域。
3)本地方法棧
為jvm提供native方法的服務(wù)
jdk里native修飾的方法(本地),會(huì)調(diào)用本地c語(yǔ)言函數(shù)
4)虛擬機(jī)棧
線程私有,
每個(gè)方法在執(zhí)行的時(shí)候都會(huì)創(chuàng)建一個(gè)棧幀用于存儲(chǔ)局部變量,操作數(shù),方法出口等信息
每個(gè)方法調(diào)用,都意味著虛擬機(jī)棧中入棧和出棧的過程
程序計(jì)算器
線程私有,
如果執(zhí)行的時(shí)候java方法,記錄當(dāng)前線程正在執(zhí)行的虛擬機(jī)字節(jié)碼指令的地址,如果本地方法則為undefined
該內(nèi)存區(qū)域是唯一一個(gè)在java虛擬機(jī)沒有規(guī)定任何OutOfMemoryError情況的區(qū)域。
棧幀:用于支持虛擬機(jī)經(jīng)行方法調(diào)用和方法執(zhí)行的數(shù)據(jù)結(jié)構(gòu),它是虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)的虛擬機(jī)棧的棧元素。 每一個(gè)方法對(duì)應(yīng)一塊棧幀內(nèi)存區(qū)域, 棧幀里由
局部變量,
操作數(shù)棧
動(dòng)態(tài)鏈接,將符號(hào)引用轉(zhuǎn)為直接引用
方法出口
a 淺拷貝和深拷貝
淺拷貝:新增一個(gè)指針,指向原有內(nèi)存地址
深拷貝:新增一個(gè)指針,指向原有內(nèi)存地址的復(fù)制的內(nèi)存地址
b 堆棧區(qū)別
物理地址,堆分配的地址對(duì)象時(shí)不連續(xù)的,因此性能慢,而棧分配的是連續(xù)的,性能高
因?yàn)槎咽遣贿B續(xù)所有內(nèi)存大小是不固定,只有在運(yùn)行期確定,而棧是連續(xù)的,在編譯期就確定了;堆存放的是內(nèi)存對(duì)象,而棧存的是局部變量,操作數(shù)棧,方法出入口,
堆內(nèi)存對(duì)于線程是共享的,棧是線程私有
3.nio直接內(nèi)存?
直接內(nèi)存(Direct Memory),并不是虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)的一部分,也不是 Java 虛擬機(jī)規(guī)范中農(nóng)定義的內(nèi)存區(qū)域。在 JDK1.4 中新加入了 NIO(New Input/Output) 類,引入了一種基于通道(Channel)與緩沖區(qū)(Buffer)的 I/O 方式,它可以使用 native 函數(shù)庫(kù)直接分配堆外內(nèi)存,然后通脫一個(gè)存儲(chǔ)在 Java 堆中的 DirectByteBuffer 對(duì)象作為這塊內(nèi)存的引用進(jìn)行操作。這樣能在一些場(chǎng)景中顯著提高性能,因?yàn)楸苊饬嗽?Java 堆和 Native 堆中來(lái)回復(fù)制數(shù)據(jù)
4.創(chuàng)建對(duì)象的方式
方式 | 注釋 |
---|---|
new關(guān)鍵字 | 調(diào)用構(gòu)造函數(shù) |
Class的newInstance | 調(diào)用構(gòu)造函數(shù) |
Constructor的newInstance | 調(diào)用構(gòu)造函數(shù) |
clone | 未調(diào)用構(gòu)造函數(shù) |
反射 | 未調(diào)用構(gòu)造函數(shù) |
1)對(duì)象創(chuàng)建流程
new 類名
-> 虛擬機(jī)遇到一條new指令,檢查常量池中是否已經(jīng)加載了相應(yīng)的類,
->虛擬機(jī)為對(duì)象分配內(nèi)存
->將分配的內(nèi)存初始化為零值(不包括對(duì)象頭)
->調(diào)用對(duì)象init方法
2)為對(duì)象分配內(nèi)存?
指針碰撞
如果java堆的內(nèi)存是規(guī)整的,所有用過的一邊放一邊,而空閑的放另一邊,分配內(nèi)存時(shí),將位于中間的指針指示器將往空閑空間挪動(dòng)一段與對(duì)象大小相等的距離
空閑列表
如果不規(guī)則,虛擬機(jī)必須維護(hù)一個(gè)列表記錄哪些內(nèi)u才能可用,在分配的時(shí)候從列表中找到一塊足夠大的空間劃分給對(duì)象實(shí)例
3)處理并發(fā)問題?
同步處理,采用cas和失敗重試 更新操作的原子性
本地線程分配緩存
4)對(duì)象的訪問定位?
句柄訪問,在堆內(nèi)存中劃分一塊內(nèi)存作為句柄池,引用中存儲(chǔ)對(duì)象的句柄地址,句柄池存儲(chǔ)對(duì)象實(shí)例指針和對(duì)象類型指針,
直接指針
引用中存儲(chǔ)的是對(duì)象地址,對(duì)象地址內(nèi)存存類型數(shù)據(jù)地址,指向方法區(qū)
5.內(nèi)存泄露異常
內(nèi)存泄漏指的是不在使用的對(duì)象或者變量,一直被占用在內(nèi)存中,沒有被回收,
理論上java有垃圾回收機(jī)制,不再使用的對(duì)象,會(huì)被gc自動(dòng)回收,從內(nèi)存中清楚
出現(xiàn)情況:
長(zhǎng)生命周期的對(duì)象持有短生命周期的對(duì)象的引用就很有可能發(fā)生內(nèi)存泄漏
6.垃圾回收機(jī)制
定義
java中,程序員不需要手動(dòng)地去釋放對(duì)象內(nèi)存,而是有虛擬機(jī)自行執(zhí)行,在JVM中有一個(gè)低優(yōu)先級(jí)的垃圾回收線程,在虛擬機(jī)空閑或者內(nèi)存不足的,才會(huì)觸發(fā),掃描那些沒有任何引用的對(duì)象,并將它們添加到回收的集合中,進(jìn)行回收;
1)引用類型
強(qiáng)引用:發(fā)生gc的時(shí)候不會(huì)被回收,需要弱化從而使gc回收
軟引用:在發(fā)生內(nèi)存溢出時(shí)會(huì)被回收
弱引用:在下次gc的時(shí)會(huì)被回收
虛引用:無(wú)法通過虛引用獲取對(duì)象
2)判斷對(duì)象是否可以被回收
引用計(jì)算器法:
為每個(gè)對(duì)象創(chuàng)建一個(gè)引用計(jì)數(shù),有對(duì)象引用時(shí)+1,引用釋放計(jì)數(shù)-1,當(dāng)計(jì)數(shù)器為0,就可以被回收,
缺點(diǎn):循環(huán)引用解決不了
可達(dá)性分析法:
從GC roots開始向下搜索,搜索所走的路徑叫做引用鏈,當(dāng)一個(gè)對(duì)象到Gc Roots沒有任何引用鏈相連,則證明此對(duì)象可以被回收;
GC roots根節(jié)點(diǎn):線程棧中本地變量,靜態(tài)變量,本地方法棧的變量等等
3)在Java中,對(duì)象什么時(shí)候可以被垃圾回收
當(dāng)對(duì)象對(duì)當(dāng)前使用這個(gè)對(duì)象的應(yīng)用程序變得不可觸及的時(shí)候,這個(gè)對(duì)象就可以被回收了
gc roots向下搜索,沒有任何引用鏈
4)永久代中會(huì)發(fā)生垃圾回收?
不會(huì)發(fā)生在永久代,但是永久代滿了或超過臨界值,會(huì)觸發(fā)垃圾回收機(jī)制,但是永久代也是會(huì)被回收的
5) JVM 有哪些垃圾回收算法
標(biāo)記清除算法
顧名思義,標(biāo)記無(wú)用對(duì)象,然后再進(jìn)行清楚回收,
缺點(diǎn):標(biāo)記清除效率低,無(wú)法清除垃圾碎片,會(huì)產(chǎn)生不連續(xù)內(nèi)存碎片
復(fù)制算法
它把內(nèi)存空間分成兩個(gè)相等的區(qū)域,每次只使用其中一個(gè)區(qū)域,垃圾收集時(shí),遍歷當(dāng)前使用區(qū)域,把存活對(duì)象復(fù)制到另外一個(gè)區(qū)域中,然后將可用的數(shù)據(jù)回收
缺點(diǎn):可用內(nèi)存大小縮小為原來(lái)的一半,對(duì)象存活率高時(shí)會(huì)頻繁經(jīng)行復(fù)制。
標(biāo)記整理法
復(fù)制算法,不太適用于老年代,老年代的存活率較高,這樣會(huì)有較多的復(fù)制操作,導(dǎo)致效率變低,
缺點(diǎn):需要局部調(diào)動(dòng),效率低
分代收集算法
當(dāng)前商業(yè)虛擬機(jī)都采用分代收集算法,將存活周期劃分,年輕代,老年代和永久代
[圖片上傳失敗...(image-f4628-1585402664191)]
6)JVM中有哪些垃圾回收器
serial收集器(復(fù)制算法)
新生代單線程收集器,標(biāo)記清理都是單線程,簡(jiǎn)單高效
ParNew收集器(復(fù)制算法)
serial收集器多線程版本
serial old收集器(標(biāo)記整理)
老年代收集器,單線程
parallel old收集器
老年代收集器,并行
CMS收集器(標(biāo)記)
老年代并行收集器,犧牲系統(tǒng)的吞吐量來(lái)追求收集速度
采用標(biāo)記清除算法,會(huì)產(chǎn)生大量?jī)?nèi)存碎片
場(chǎng)景:使用于延時(shí)要求高的服務(wù),用戶線程不允許長(zhǎng)時(shí)間停頓。
g1(標(biāo)記整理)
garbagr first,面向服務(wù)端應(yīng)用
堆回收器,jdk1.7提供的新收集器,
7)簡(jiǎn)述分代垃圾回收器工作?
分代垃圾分兩個(gè)分區(qū):新生代(1/3)和老生代(2/3),
新生代一般采用復(fù)制算法,新生代分三個(gè)分區(qū):Eden(8),from survivor (1),to survivor(1)
首先新建的對(duì)象一般放在eden和form survivor中,存活的對(duì)象放在to survivor中,
清空from survivor和eden區(qū)
from to互換
每次從from 移動(dòng)到to,年齡+1,當(dāng)超過15時(shí),放入老生代(大對(duì)象直接放入老生代)
當(dāng)老生代空間占用達(dá)到某個(gè)值后,就會(huì)觸發(fā)垃圾回收
如果一批對(duì)象的總大小大域survivor>50%時(shí),直接進(jìn)入老年區(qū),老年區(qū)滿時(shí),會(huì)進(jìn)行full gc
(1) STW
stop the word,停止用戶線程
7.java內(nèi)存分配,回收策略,以及minor gc和major gc
1)內(nèi)存分配
對(duì)象的內(nèi)存分配一般是在堆上,自動(dòng)內(nèi)存分配
對(duì)象優(yōu)先分配再eden區(qū),
大對(duì)象直接分配在老年代(大對(duì)象指需要大量連續(xù)內(nèi)存空間)
長(zhǎng)期存活的對(duì)象放入老年代
2)minor gc和Major gc
當(dāng)新生對(duì)象無(wú)法為其分配內(nèi)存時(shí),執(zhí)行minor gc,發(fā)生在新生代
major gc清理老年代,major gc執(zhí)行前往往會(huì)執(zhí)行一次 minor gc
full gc老年代和新生代,
觸發(fā)條件:
老年代空間不足,方法去空間不足,System.gc,minor gc后進(jìn)入老年代的平均大小大于老年代可用大小
裝載->驗(yàn)證->準(zhǔn)備->解析->初始化->實(shí)例化->垃圾收集->對(duì)象終結(jié)->卸載類型
8.java類加載機(jī)制
虛擬機(jī)將描述的類信息從class文件加載到內(nèi)存,并對(duì)數(shù)據(jù)進(jìn)行檢驗(yàn),解析和初始化,形成被虛擬機(jī)直接使用java類型
1)什么是類加載器
通過類的全限定名,獲取該類的二進(jìn)制字節(jié)流的代碼塊,加載到內(nèi)存,獲取class類對(duì)象
2)有哪些類加載器
啟動(dòng)類加載器
用于加載java核心類庫(kù),無(wú)法被java程序直接引用
擴(kuò)展類加載器
java擴(kuò)展庫(kù),
系統(tǒng)類加載器
根據(jù)java應(yīng)用的類路徑來(lái)加載Java類,一般來(lái)說,Java應(yīng)用的類都是由它來(lái)實(shí)現(xiàn)加載
用戶自定義類加載器
通過繼承java.lang.ClassLoader的方式來(lái)實(shí)現(xiàn)
3)類加載的執(zhí)行過程
加載
根據(jù)路徑查找相應(yīng)的class文件導(dǎo)入
驗(yàn)證
檢查加載class文件的正確性
準(zhǔn)備
給類的靜態(tài)變量分配內(nèi)存空間
解析
將常量池中符號(hào)引用替換成直接引用 (符號(hào)引用只是一個(gè)表示,直接引用指向內(nèi)存地址)
初始化
對(duì)靜態(tài)變量和靜態(tài)代碼塊執(zhí)行初始化工作
4)什么是雙親委派模型?
類加載器 通過全限定名將class文件加載到j(luò)vm中,然后轉(zhuǎn)化成class對(duì)象
定義
當(dāng)一個(gè)類收到類加載請(qǐng)求時(shí),不會(huì)自己先去加載這個(gè)類,而是將其委派給父類,有父類去加載,如果此時(shí)父類不能加載,反饋給子類,由子類去完成加載
9.jvm調(diào)優(yōu)
1)調(diào)優(yōu)工具
jdk bin目錄下,
jconsole和jvisualvm
2)調(diào)優(yōu)參數(shù)配置
-Xms2g:初始化推大小為 2g;
-Xmx2g:堆最大內(nèi)存為 2g;
-Xmn:年輕代內(nèi)存大小
-XX:NewRatio=4:設(shè)置年輕的和老年代的內(nèi)存比例為 1:4;
-XX:SurvivorRatio=8:設(shè)置新生代 Eden 和 Survivor 比例為 8:2;
–XX:+UseParNewGC:指定使用 ParNew + Serial Old 垃圾回收器組合;
-XX:+UseParallelOldGC:指定使用 ParNew + ParNew Old 垃圾回收器組合;
-XX:+UseConcMarkSweepGC:指定使用 CMS + Serial Old 垃圾回收器組合;
-XX:+PrintGC:開啟打印 gc 信息;
-XX:+PrintGCDetails:打印 gc 詳細(xì)信息
3)調(diào)優(yōu)方案
億級(jí)電商系統(tǒng)VM參數(shù)設(shè)置優(yōu)化
6.對(duì)象內(nèi)存布局
對(duì)象頭
第一部分:存儲(chǔ)對(duì)象自身運(yùn)行時(shí)的數(shù)據(jù),如哈希碼 gc分代年齡 鎖狀態(tài)標(biāo)志,線程持有的鎖等等
第二部分:類型指針,對(duì)象指向類元數(shù)據(jù)的指針
實(shí)例數(shù)據(jù)
數(shù)據(jù)
對(duì)齊填充
不是必然存在,只是為了對(duì)齊