圖文可以做網(wǎng)站設(shè)計(jì)嗎電商運(yùn)營(yíng)推廣的方式和渠道有哪些
1. 什么是JVM
?
我們都知道在 Windows 系統(tǒng)上一個(gè)軟件包裝包是 exe 后綴的,而這個(gè)軟件包在蘋果的 Mac OSX 系統(tǒng)上是無(wú)法安裝的。類似地,Mac OSX 系統(tǒng)上軟件安裝包則是 dmg 后綴,同樣無(wú)法在 Windows 系統(tǒng)上安裝。
?
Java 代碼為什么可以在 Windows 系統(tǒng)運(yùn)行,也可以在 Linux 系統(tǒng)運(yùn)行?這就是 jvm 的功勞, Java 虛擬機(jī)可以理解為一個(gè)翻譯官,在 Linux 系統(tǒng)上將 Java 代碼翻譯成 Linux 機(jī)器碼,在 Windows 系統(tǒng)上將 Java 代碼翻譯成 Windows 機(jī)器碼,所以 Java 有了虛擬機(jī)之后,可以讓 Java 代碼運(yùn)行在不同的系統(tǒng)上。
?
2. JVM內(nèi)存結(jié)構(gòu)(運(yùn)行時(shí)數(shù)據(jù)區(qū))
運(yùn)行時(shí)數(shù)據(jù)區(qū)是官方說法,但很多時(shí)候這個(gè)名詞并不是很形象,再加上日積月累的習(xí)慣,很多人都習(xí)慣用 JVM 內(nèi)存結(jié)構(gòu)這個(gè)說法
?
JVM 內(nèi)存結(jié)構(gòu)(運(yùn)行時(shí)數(shù)據(jù)區(qū))主要包括:堆、棧(虛擬機(jī)棧)、本地方法棧、方法區(qū)、程序計(jì)數(shù)器等。
?
線程公有:堆、方法區(qū)
線程私有:棧、本地方法棧、程序計(jì)數(shù)器
?
?
2.1 堆(公有)
堆內(nèi)存被所有線程共享。堆內(nèi)存用于存放由 new 創(chuàng)建的對(duì)象和數(shù)組。
?
Java堆分為年輕代(Young Generation)和老年代(Old Generation);年輕代又分為樂園(Eden)和幸存區(qū)(Survivor區(qū));幸存區(qū)又分為 From 區(qū)(From Survivor 區(qū))和 To 區(qū)(To Survivor 區(qū))。
?
而 JVM 垃圾回收機(jī)制主要收集堆中年輕代和老年代對(duì)象所占用的內(nèi)存空間。
?
?
?
為什么默認(rèn)的虛擬機(jī)配置,Eden:from :to = 8:1:1 ?
?
這是經(jīng)過大量統(tǒng)計(jì)得出的結(jié)果發(fā)現(xiàn) 80% 的對(duì)象存活時(shí)間都很短,于是將 Eden 區(qū)設(shè)置為年輕代的 80%,這樣可以減少內(nèi)存空間的浪費(fèi),提高內(nèi)存空間利用率。
?
年輕代中的 Minor GC
?
1、絕大多數(shù)剛剛被創(chuàng)建的對(duì)象會(huì)存放在樂園(Eden)。
2、在樂園內(nèi)存滿時(shí),執(zhí)行第一次GC(Minor GC)之后,存活的對(duì)象被移動(dòng)到其中一個(gè)幸存區(qū)(Survivor)。
3、此后,每次樂園執(zhí)行GC后,存活的對(duì)象會(huì)被堆積在同一個(gè)幸存區(qū)。
4、當(dāng)一個(gè)幸存區(qū)飽和,還在存活的對(duì)象會(huì)被移動(dòng)到另一個(gè)幸存區(qū)。然后會(huì)清空已經(jīng)飽和的那個(gè)幸存區(qū)。
5、在以上步驟中重復(fù)N次(N = MaxTenuringThreshold(年齡閥值設(shè)定,默認(rèn)15))依然存活的對(duì)象,就會(huì)被移動(dòng)到老年代。
?
從上面的步驟可以發(fā)現(xiàn),兩個(gè)幸存者空間,必須有一個(gè)是保持空的。
?
需要重點(diǎn)記住的是,對(duì)象在剛剛被創(chuàng)建之后,是保存在樂園的(Eden)。那些長(zhǎng)期存活的對(duì)象會(huì)經(jīng)由幸存區(qū)(Survivor)轉(zhuǎn)存到老年代(Old generation)。
?
也有例外出現(xiàn),對(duì)于一些比較大的對(duì)象(需要分配一塊比較大的連續(xù)內(nèi)存空間)則直接進(jìn)入到老年代(內(nèi)存分配擔(dān)保機(jī)制)。
?
注意:當(dāng)年輕代滿時(shí)就會(huì)觸發(fā)Minor GC,這里的年輕代滿指的是Eden滿,Survivor滿不會(huì)引發(fā)Minor GC。由于年輕代中的對(duì)象存活時(shí)間比較短,Minor GC比較頻繁,GC 速度也很快。
?
老年代中的 Full GC
?
老年代空間的構(gòu)成很簡(jiǎn)單,它不像新生代空間那樣劃分為幾個(gè)區(qū)域,它只有一個(gè)區(qū)域,里面存儲(chǔ)的對(duì)象并不像新生代一樣存活時(shí)間很短。這里的對(duì)象幾乎都是從Survivor 區(qū)中熬過來的,它們絕不會(huì)輕易的被回收掉。
?
注意:Full GC 是清理整個(gè)堆空間,包括年輕代和老年代,如果Full GC之后,堆中仍然無(wú)法存儲(chǔ)對(duì)象,就會(huì)拋出OutOfMemoryError異常。由于老年代內(nèi)存不會(huì)輕易的被回收掉,因此 Full GC 發(fā)生的次數(shù)不會(huì)有 Minor GC 那么頻繁。但是老年代內(nèi)存大,做一次 Full GC 的時(shí)間比 Minor GC 要更長(zhǎng)(約10倍)。
?
2.2 方法區(qū)(公有)
方法區(qū)被所有線程共享。方法區(qū)用于存放靜態(tài)變量、常量、類信息(版本、方法、字段等)、常量池??梢钥醋鍪菍㈩?#xff08;Class)的元數(shù)據(jù),保存在方法區(qū)里。
?
Integer常量池
?
都知道數(shù)據(jù)類型 == 比較的是內(nèi)存地址,先看個(gè)下邊的例子。
?
public static void main(String[] args)
{
? ? Integer i1 = 66;
? ? Integer i2 = 66;
? ? Integer i3 = 150;
? ? Integer i4 = 150;
? ? System.out.println(i1 == i2);//true
? ? System.out.println(i3 == i4);//false
}
i1 == i2 結(jié)果為 true,i3 == i4 結(jié)果為 false。由結(jié)果得知 i1 和 i2 的內(nèi)存地址是相同的,而 i3 和 i4 內(nèi)存地址是不同的。
?
產(chǎn)生這樣結(jié)果的原因是 Integer i1 = 66 實(shí)際上有一步裝箱的操作,通過 Integer 的 valueOf 方法將 int 型的 66 裝箱成 Integer。下邊是 Integer 中的 valudOf 方法。
?
public static Integer valueOf(int i) {
? ? if (i >= IntegerCache.low && i <= IntegerCache.high)
? ? ? ? return IntegerCache.cache[i + (-IntegerCache.low)];
? ? return new Integer(i);
}
Integer 的 valueOf 方法很簡(jiǎn)單,它判斷變量是否在 IntegerCache 的最小值(-128)和最大值(127)之間,如果在,則返回常量池中的內(nèi)容,否則 new 一個(gè) Integer 對(duì)象。
?
由于 66 在 -128 ~ 127 之間,所以 66 裝箱時(shí),使用的是常量池中的 66,所以 == 結(jié)果為 true。
而 150 不在范圍內(nèi),在裝箱時(shí)執(zhí)行了 new Integer(150),所以返回的是新創(chuàng)建的對(duì)象,所以 == 結(jié)果為 false。
?
String常量池
?
String 是由 final 修飾的類,是不可以被繼承的。通常有兩種方式來創(chuàng)建對(duì)象。
?
// 1
String str = new String("abc");
// 2
String str = abc;
第一種:使用 new 創(chuàng)建的對(duì)象,存放在堆中,每次 new 出來的內(nèi)存地址都不同。
第二種:先在常量池中找有沒有 “abc”。有,則直接取常量池內(nèi)存地址賦值給 str。沒有,先在常量池創(chuàng)建“abc”,再取內(nèi)存地址賦值給 str。
?
通過代碼驗(yàn)證上面理論。
?
public static void main(String[] args) {
? ? String s1 = new String("abc");
? ? String s2 = new String("abc");
? ? String s3 = "abc";
? ? String s4 = "abc";
? ? System.out.println(s1 == s2);// false
? ? System.out.println(s3 == s4);// true
}
s1 == s2 為 false 原因:str1 和 str2 使用 new 創(chuàng)建對(duì)象,分別在堆上創(chuàng)建了不同的對(duì)象。兩個(gè)引用指向堆中兩個(gè)不同的對(duì)象,所以為 false。
s3 == s4 為 true 原因:首先在棧上存放變量引用 s3,然后去常量池中找是否有 abc,沒有,則將 abc 存儲(chǔ)在常量池中,然后將 s3 指向常量池的 abc。當(dāng) s4 = "abc" 時(shí),去常量池中發(fā)現(xiàn)已經(jīng)有 abc 了,就將 s4 引用指向常量池已有的 abc 。所以s3 == s4,指向同一個(gè)內(nèi)存地址。
String 類中有一個(gè)方法 intern,可以返回池中的字符串,如下代碼
?
public static void main(String[] args) {
? ? String s1 = new String("abc");
? ? String s2 = "abc";
? ? System.out.println(s1 == s2);// false
? ? System.out.println(s1.intern() == s2);// true
}
上邊的結(jié)果可以看下 intern() 方法注釋就知道結(jié)果。當(dāng)調(diào)用 intern 方法時(shí),如果常量池中已經(jīng)該字符串,則返回池中的字符串;否則將此字符串添加到常量池中,并返回字符串的引用。
?
2.3 棧(私有)
棧是后進(jìn)先出的。棧是線程私有的,他的生命周期與線程相同。每個(gè)線程都會(huì)分配一個(gè)棧的空間,一個(gè)線程會(huì)對(duì)應(yīng)一個(gè)棧。
?
棧存儲(chǔ)什么
?
棧中存儲(chǔ)的是棧幀。每個(gè)方法在執(zhí)行時(shí)都會(huì)創(chuàng)建一個(gè)棧幀。棧幀中存儲(chǔ)了局部變量表、操作數(shù)棧、動(dòng)態(tài)連接和方法出口等信息。每個(gè)方法從調(diào)用到運(yùn)行結(jié)束的過程,就對(duì)應(yīng)著一個(gè)棧幀在棧中壓棧到出棧的過程??梢岳斫鉃闂褪蔷€程所執(zhí)行的方法。
?
使用遞歸時(shí),會(huì)導(dǎo)致 StackOverflowError 錯(cuò)誤,就是因?yàn)椴粩嗟脑跅V袆?chuàng)建棧幀,當(dāng)棧幀的數(shù)量超過了棧的大小時(shí),就會(huì)導(dǎo)致報(bào)錯(cuò)。
?
?
2.4 本地方法棧(私有)
本地方法棧是線程私有的,主要為 JVM 使用到的 Native 方法服務(wù)。Native 方法不是以 Java 語(yǔ)言實(shí)現(xiàn)的,而是以本地語(yǔ)言實(shí)現(xiàn)的(比如 C 或 C++)。
?
可以理解為 Native 方法是與操作系統(tǒng)直接交互的,比如通知垃圾收集器進(jìn)行垃圾回收的代碼 System.gc(),獲取常量池中的字符串引用 String.intern(),都是使用 native 修飾的。
?
2.5 程序計(jì)數(shù)器(私有)
程序計(jì)數(shù)器是一個(gè)比較小的內(nèi)存區(qū)域,可能是CPU寄存器或者操作系統(tǒng)內(nèi)存,其主要用于指示當(dāng)前線程所執(zhí)行的字節(jié)碼執(zhí)行到了第幾行,可以理解為是當(dāng)前線程的行號(hào)指示器。
?
字節(jié)碼解釋器在工作時(shí),會(huì)通過改變這個(gè)計(jì)數(shù)器的值來取下一條語(yǔ)句指令。 每個(gè)程序計(jì)數(shù)器只用來記錄一個(gè)線程的行號(hào),所以它是線程私有(一個(gè)線程就有一個(gè)程序計(jì)數(shù)器)的。