日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

黑马程序员JVM完整教程

發(fā)布時間:2023/12/10 编程问答 65 豆豆
生活随笔 收集整理的這篇文章主要介紹了 黑马程序员JVM完整教程 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

黑馬程序員JVM完整教程

  • 前言
  • 引言
  • 1. 內(nèi)存結(jié)構(gòu)
    • 1. 1 程序計數(shù)器
    • 1.2 虛擬機(jī)棧
      • 1.2.1 定義與作用
      • 1.2.2 棧內(nèi)存溢出
      • 1.2.3 線程運(yùn)行診斷
    • 1.3 本地方法棧
    • 1.4 堆
      • 1.4.1 定義
      • 1.4.2 堆內(nèi)存溢出
      • 1.4.3 堆內(nèi)存診斷
    • 1.5 方法區(qū)
      • 1.5.1 定義
      • 1.5.2 組成
      • 1.5.3 方法區(qū)內(nèi)存溢出
      • 1.5.4 運(yùn)行時常量池
        • 反編譯字節(jié)碼獲取
      • 1.5.5 StringTable
        • StringTable特性
        • StringTable位置
        • StringTable垃圾回收機(jī)制
        • StringTable調(diào)優(yōu)
    • 1.6 直接內(nèi)存
      • 1.6.1 定義
      • 1.6.2 使用
      • 1.6.3 分配和回收原理
  • 2. 垃圾回收
    • 2.1 如何判斷對象可以回收
      • 2.1.1 引用計數(shù)
      • 2.1.2 可達(dá)性分析算法
      • 2.1.3 五種引用
        • 強(qiáng)引用
        • 軟引用(SoftReference)
        • 弱引用(WeakReference)
        • 虛引用(PhantomReference)
        • 終結(jié)器引用(FinalReference)
        • 引用隊列
    • 2.2 垃圾回收算法
      • 2.2.1 標(biāo)記清除(Mark Sweep)
      • 2.2.2 標(biāo)記整理(Mark Compact)
      • 2.2.3 復(fù)制(Copy)
    • 2.3 分代垃圾回收
    • 2.4 垃圾回收器
      • 2.4.1 串行垃圾回收器
      • 2.4.2 吞吐量優(yōu)先垃圾回收器
      • 2.4.3 響應(yīng)時間優(yōu)先垃圾回收器
        • CMS 收集器
      • 2.4.4 G1垃圾回收器
        • G1垃圾回收階段
        • Full GC
        • Young Collection 跨代引用
        • Remake重新標(biāo)記
        • G1的部分優(yōu)化
          • JDK 8u20 字符串去重
          • JDK 8u40 并發(fā)標(biāo)記類卸載
          • JDK 8u60 回收巨型對象
          • JDK 9 并發(fā)標(biāo)記起始時間的調(diào)整
          • JDK 9 更高效的回收
    • 2.5 垃圾回收調(diào)優(yōu)
      • 2.5.1 調(diào)優(yōu)領(lǐng)域
      • 2.5.2 確定目標(biāo)
      • 2.5.3 最快的 GC是不發(fā)生 GC
      • 2.5.4 新生代調(diào)優(yōu)
      • 2.5.5 老年代調(diào)優(yōu)
  • 3. 類加載與字節(jié)碼技術(shù)
    • 3.1 類文件結(jié)構(gòu)
      • 3.1.1 魔數(shù)
      • 3.1.2 版本
      • 3.1.3 常量池
      • 3.1.4 訪問標(biāo)識與繼承信息
      • 3.1.5 Field 信息
      • 3.1.6 Method 信息
      • 3.1.7 附加屬性
    • 3.2 字節(jié)碼指令
      • 3.2.1 入門
      • 3.2.2 javap工具獲取反編譯字節(jié)碼
      • 3.2.3 圖解方法執(zhí)行流程
        • ① 原始 java 代碼
        • ② 編譯后的字節(jié)碼文件
        • ③ 常量池載入運(yùn)行時常量池
        • ④ 方法字節(jié)碼載入方法區(qū)
        • ⑤ main 線程開始運(yùn)行,分配棧幀內(nèi)存
        • ⑥ 執(zhí)行引擎開始執(zhí)行字節(jié)碼
      • 3.2.4 練習(xí) - 分析 i++
      • 3.2.5 條件判斷指令
      • 3.2.6 循環(huán)控制指令
      • 3.2.7 練習(xí) - 判斷結(jié)果
      • 3.2.8 構(gòu)造方法
        • < cinit >()V
        • < init >()V
      • 3.2.9 方法調(diào)用
      • 3.2.10 多態(tài)的原理
      • 3.2.11 異常處理
        • try-catch
        • 多個 single-catch 塊的情況
        • multi-catch 的情況
        • finally
      • 3.2.12 練習(xí) - finally 面試題
        • finally 出現(xiàn)了 return
        • finally(無return)對返回值影響
      • 3.2.13 synchronized
    • 3.3 編譯期處理(語法糖)
      • 3.3.1 默認(rèn)構(gòu)造器
      • 3.3.2 自動拆裝箱
      • 3.3.3 泛型集合取值
      • 3.3.4 可變參數(shù)
      • 3.3.5 foreach 循環(huán)
      • 3.3.7 switch 枚舉
      • 3.3.8 枚舉類
      • 3.3.9 try-with-resources
      • 3.3.10 方法重寫時的橋接方法
      • 3.3.11 匿名內(nèi)部類
    • 3.4 類加載階段
      • 3.4.1 加載
      • 3.4.2 鏈接
        • 驗證
        • 準(zhǔn)備
        • 解析
      • 3.4.3 初始化
    • 3.5 類加載器
      • 3.5.1 啟動類加載器 Bootstrap ClassLoader
      • 3.5.2 擴(kuò)展類加載器 Extension ClassLoader
      • 3.5.3 雙親委派模式
      • 3.5.4 線程上下文類加載器(雙親委派破壞者)
      • 3.5.5 自定義類加載器
    • 3.6 運(yùn)行期優(yōu)化
      • 3.6.1 即時編譯JIT
      • 3.6.2 常見JIT優(yōu)化
        • 逃逸分析
        • 方法內(nèi)聯(lián)
        • 反射優(yōu)化(字段優(yōu)化)
  • 4. 內(nèi)存模型
    • 4.1 java內(nèi)存模型
      • 4.1.1 原子性
      • 4.1.2 可見性
      • 4.1.3 有序性
      • 4.1.4 happens-before
    • 4.2 CAS 與 原子類
      • 4.2.1 CAS(Compare and Swap)
      • 4.2.2 樂觀鎖與悲觀鎖
      • 4.2.3 原子操作類
    • 4.3 synchronized 優(yōu)化
      • 4.3.1 輕量級鎖
      • 4.3.2 鎖膨脹
      • 4.3.3 重量鎖
      • 4.3.4 偏向鎖
      • 4.3.5 其它優(yōu)化


前言

黑馬程序員JVM完整教程
b站鏈接:https://www.bilibili.com/video/BV1yE411Z7AP?spm_id_from=333.337.search-card.all.click&vd_source=a1565026d150073a042e4cd0cc263851
配套資料:https://pan.baidu.com/s/167mob33EfeD-on6dWrEWGQ 提取碼:bqob


引言

  • 什么是JVM?
    Java Virtual Machine - java 程序的運(yùn)行環(huán)境(java 二進(jìn)制字節(jié)碼的運(yùn)行環(huán)境)。

  • 好處:
    一次編寫,到處運(yùn)行;
    自動內(nèi)存管理,垃圾回收功能;
    數(shù)組下標(biāo)越界檢查;
    多態(tài)。

  • 比較:JVM JRE JDK

    JVM:java虛擬機(jī), 可以屏蔽java代碼與底層虛擬機(jī)之間的關(guān)系
    JRE:java的運(yùn)行時環(huán)境,在JVM的基礎(chǔ)上結(jié)合一些基礎(chǔ)類庫
    JDK:java開發(fā)工具包,在JRE的基礎(chǔ)上增加編譯工具

  • 常見的JVM:

  • JVM的整體架構(gòu):

1. 內(nèi)存結(jié)構(gòu)

1. 1 程序計數(shù)器

  • Program Counter Register 程序計數(shù)器(寄存器)
  • 作用:記住下一條jvm指令的執(zhí)行地址
  • 特點(diǎn):
    • 是線程私有的
      • CPU會為每個線程分配時間片,當(dāng)前線程的時間片使用完以后,CPU就會去執(zhí)行另一個線程中的代碼
      • 程序計數(shù)器是每個線程所私有的,當(dāng)另一個線程的時間片用完,又返回來執(zhí)行當(dāng)前線程的代碼時,通過程序計數(shù)器可以知道應(yīng)該執(zhí)行哪一句指令
    • 不會存在內(nèi)存溢出

1.2 虛擬機(jī)棧

1.2.1 定義與作用

  • Java Virtual Machine Stacks(Java虛擬機(jī)棧)
    • 每個線程運(yùn)行時所需要的內(nèi)存,稱為虛擬機(jī)棧
    • 每個棧由多個棧幀(Frame)組成,對應(yīng)著每次方法調(diào)用時所占用的內(nèi)存
    • 每個線程只能有一個活動棧幀,對應(yīng)著當(dāng)前正在執(zhí)行的那個方法

  • 問題辨析:
  • 垃圾回收是否涉及棧內(nèi)存?
    不需要。棧內(nèi)存是一次次方法調(diào)用所產(chǎn)生的棧幀內(nèi)存,棧幀內(nèi)存在每一次方法調(diào)用后都會彈出棧,也就是說會被自動的回收掉,因此不需要垃圾回收來進(jìn)行管理。
  • 棧內(nèi)存分配越大越好嗎?
    不是。物理內(nèi)存大小一定,棧內(nèi)存分配的越大,所能劃分的線程越少。 棧內(nèi)存分配的大僅僅只能使得能夠進(jìn)行更多次的遞歸調(diào)用。
  • 方法內(nèi)的局部變量是否線程安全?
    如果方法內(nèi)局部變量沒有逃離方法的作用范圍,它是線程安全的。
    如果是局部變量引用了對象,并逃離方法的作用范圍,需要考慮線程安全。如靜態(tài)變量、方法參數(shù)、方法返回值等。

1.2.2 棧內(nèi)存溢出

  • 棧內(nèi)存溢出:java.lang.StackOverflowError
    • 棧幀過多導(dǎo)致棧內(nèi)存溢出:如沒有終止條件的方法遞歸調(diào)用等

    • 棧幀過大導(dǎo)致棧內(nèi)存溢出

    • 可以通過參數(shù)-Xss來控制程序棧的大小

1.2.3 線程運(yùn)行診斷

  • 線程診斷:
    • CPU占用高
      • 用top定位哪個進(jìn)程對cpu的占用過高
      • ps H -eo pid,tid,%cpu | grep 進(jìn)程id :用ps命令進(jìn)一步定位是哪個線程引起的cpu占用過高(可以得到十進(jìn)制的線程id)
      • jstack 進(jìn)程id
        可以根據(jù)線程id(此時的線程id為十六進(jìn)制,在匹配時需要將上一步得到的十進(jìn)制的換算為十六進(jìn)制) 找到有問題的線程,進(jìn)一步定位到問題代碼的源碼行號
    • 遲遲得不到運(yùn)行結(jié)果
      使用jstack 進(jìn)程id,在最后會有一些提示信息

1.3 本地方法棧

  • 在java虛擬機(jī)調(diào)用一些本地方法時需要給本地方法提供的內(nèi)存空間
    • 本地方法:由于java有限制,不可以直接與操作系統(tǒng)底層交互,所以需要一些用c/c++編寫的本地方法與操作系統(tǒng)底層的API交互,java可以間接的通過本地方法來調(diào)用底層功能
  • 舉例:Object的clone()、hashCode()、notify()、notifyAll()、wait()等

1.4 堆

1.4.1 定義

  • Heap堆:通過new關(guān)鍵字創(chuàng)建對象都會使用堆內(nèi)存
  • 特點(diǎn)
    • 它是線程共享的,堆中對象都需要考慮線程安全的問題
    • 有垃圾回收機(jī)制

1.4.2 堆內(nèi)存溢出

  • 堆內(nèi)存溢出:java.lang.OutofMemoryError :java heap space.
    • 可以通過參數(shù)-Xmx來控制程序堆的大小

1.4.3 堆內(nèi)存診斷

  • jps工具:查看當(dāng)前系統(tǒng)中有哪些java進(jìn)程

  • jmap工具:查看某一時刻堆內(nèi)存占用情況


  • jconsole工具:圖形界面的,多功能的監(jiān)測工具,可以連續(xù)監(jiān)測

  • jvisualvm工具

    可以查看堆內(nèi)存的具體信息:占用200多M內(nèi)存

    查看占用內(nèi)存最大的對象:ArrayList

    ArrayList中的元素Student對象中包含的big屬性占用1M內(nèi)存,共有200個元素,占用200M內(nèi)存

    對應(yīng)源碼:

  • 1.5 方法區(qū)

    1.5.1 定義

    • 所有java虛擬機(jī)線程的共享區(qū)域
    • 存儲類的結(jié)構(gòu)的相關(guān)信息,如運(yùn)行時常量池、成員變量、方法數(shù)據(jù)、成員方法和構(gòu)造器的代碼等
    • 方法區(qū)在虛擬機(jī)啟動時創(chuàng)建,其邏輯上是堆的一個組成部分,但在實(shí)現(xiàn)時不同的JVM廠商可能會有不同的實(shí)現(xiàn)

    1.5.2 組成

    • 組成:以O(shè)racle的HotSpot為例
      • jdk1.6:永久代,占用JVM內(nèi)存空間
      • jdk1.8:元空間,移出JVM內(nèi)存(除StringTable),放入系統(tǒng)內(nèi)存

    1.5.3 方法區(qū)內(nèi)存溢出

    • 1.8以前會導(dǎo)致永久代內(nèi)存溢出**java.lang.OutOfMemoryError: PernGen space**
    • 1.8之后會導(dǎo)致元空間內(nèi)存溢出:java.lang.OutOfMemoryError: Metaspace

    1.5.4 運(yùn)行時常量池

    • 常量池:常量池,就是一張,存儲在*.class字節(jié)碼文件中。虛擬機(jī)指令根據(jù)這張常量表找到要執(zhí)行的類名、方法名、參數(shù)類型、字面量等信息

    反編譯字節(jié)碼獲取

    • 方法一:
      編譯: javac -encoding utf-8 Demo1_22.java,生成字節(jié)碼文件
      反編譯字節(jié)碼文件:javap -v Demo1_22.class,在控制臺打印出字節(jié)碼文件的內(nèi)容

    • 方法二:
      直接在IDEA中進(jìn)行配置,可直接右鍵查看反編譯好的字節(jié)碼文件,方法見文章:Intellij IDEA 查看字節(jié)碼

    • 運(yùn)行時常量池:常量池是*.class文件中的,當(dāng)該類被加載,它的常量池信息就會放入運(yùn)行時常量池,并把里面的符號地址變?yōu)檎鎸?shí)地址

    1.5.5 StringTable

    StringTable特性

    • 常量池中的字符串僅是符號,第一次用到時才變?yōu)閷ο蟆?/p>

    • 利用串池的機(jī)制,來避免重復(fù)創(chuàng)建字符串對象

    • 字符串拼接

      package cn.itcast.jvm.t1.stringtable;// StringTable [ "a", "b" ,"ab" ] hashtable 結(jié)構(gòu),不能擴(kuò)容 public class Demo1_22 {// 常量池中的信息,都會被加載到運(yùn)行時常量池中, 這時 a b ab 都是常量池中的符號,還沒有變?yōu)?java 字符串對象// ldc #2 會把 a 符號變?yōu)?"a" 字符串對象// ldc #3 會把 b 符號變?yōu)?"b" 字符串對象// ldc #4 會把 ab 符號變?yōu)?"ab" 字符串對象public static void main(String[] args) {String s1 = "a"; // 懶惰的, 運(yùn)行到此行才會創(chuàng)建字符串對象放入串池String s2 = "b";String s3 = "ab";String s4 = s1 + s2; // new StringBuilder().append("a").append("b").toString() new String("ab")String s5 = "a" + "b"; // javac 在編譯期間的優(yōu)化,結(jié)果已經(jīng)在編譯期確定為abSystem.out.println(s3 == s4);//falseSystem.out.println(s3 == s5);//true} }

      右鍵查看反編譯后的字節(jié)碼文件如下:

      Classfile /E:/00_learning/JAVA/資料-解密JVM/代碼/jvm/out/production/jvm/cn/itcast/jvm/t1/stringtable/Demo1_22.classLast modified 2022年7月17日; size 1039 bytesMD5 checksum 8815b8f391f7f5387dc065e4053bf5d1Compiled from "Demo1_22.java" public class cn.itcast.jvm.t1.stringtable.Demo1_22minor version: 0major version: 52flags: (0x0021) ACC_PUBLIC, ACC_SUPERthis_class: #11 // cn/itcast/jvm/t1/stringtable/Demo1_22super_class: #12 // java/lang/Objectinterfaces: 0, fields: 0, methods: 2, attributes: 1 Constant pool:#1 = Methodref #12.#36 // java/lang/Object."<init>":()V#2 = String #37 // a#3 = String #38 // b#4 = String #39 // ab#5 = Class #40 // java/lang/StringBuilder#6 = Methodref #5.#36 // java/lang/StringBuilder."<init>":()V#7 = Methodref #5.#41 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;#8 = Methodref #5.#42 // java/lang/StringBuilder.toString:()Ljava/lang/String;#9 = Fieldref #43.#44 // java/lang/System.out:Ljava/io/PrintStream;#10 = Methodref #45.#46 // java/io/PrintStream.println:(Z)V#11 = Class #47 // cn/itcast/jvm/t1/stringtable/Demo1_22#12 = Class #48 // java/lang/Object#13 = Utf8 <init>#14 = Utf8 ()V#15 = Utf8 Code#16 = Utf8 LineNumberTable#17 = Utf8 LocalVariableTable#18 = Utf8 this#19 = Utf8 Lcn/itcast/jvm/t1/stringtable/Demo1_22;#20 = Utf8 main#21 = Utf8 ([Ljava/lang/String;)V#22 = Utf8 args#23 = Utf8 [Ljava/lang/String;#24 = Utf8 s1#25 = Utf8 Ljava/lang/String;#26 = Utf8 s2#27 = Utf8 s3#28 = Utf8 s4#29 = Utf8 s5#30 = Utf8 StackMapTable#31 = Class #23 // "[Ljava/lang/String;"#32 = Class #49 // java/lang/String#33 = Class #50 // java/io/PrintStream#34 = Utf8 SourceFile#35 = Utf8 Demo1_22.java#36 = NameAndType #13:#14 // "<init>":()V#37 = Utf8 a#38 = Utf8 b#39 = Utf8 ab#40 = Utf8 java/lang/StringBuilder#41 = NameAndType #51:#52 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;#42 = NameAndType #53:#54 // toString:()Ljava/lang/String;#43 = Class #55 // java/lang/System#44 = NameAndType #56:#57 // out:Ljava/io/PrintStream;#45 = Class #50 // java/io/PrintStream#46 = NameAndType #58:#59 // println:(Z)V#47 = Utf8 cn/itcast/jvm/t1/stringtable/Demo1_22#48 = Utf8 java/lang/Object#49 = Utf8 java/lang/String#50 = Utf8 java/io/PrintStream#51 = Utf8 append#52 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;#53 = Utf8 toString#54 = Utf8 ()Ljava/lang/String;#55 = Utf8 java/lang/System#56 = Utf8 out#57 = Utf8 Ljava/io/PrintStream;#58 = Utf8 println#59 = Utf8 (Z)V {public cn.itcast.jvm.t1.stringtable.Demo1_22();descriptor: ()Vflags: (0x0001) ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: invokespecial #1 // Method java/lang/Object."<init>":()V4: returnLineNumberTable:line 4: 0LocalVariableTable:Start Length Slot Name Signature0 5 0 this Lcn/itcast/jvm/t1/stringtable/Demo1_22;public static void main(java.lang.String[]);descriptor: ([Ljava/lang/String;)Vflags: (0x0009) ACC_PUBLIC, ACC_STATICCode:stack=3, locals=6, args_size=10: ldc #2 // String a2: astore_13: ldc #3 // String b5: astore_26: ldc #4 // String ab8: astore_39: new #5 // class java/lang/StringBuilder12: dup13: invokespecial #6 // Method java/lang/StringBuilder."<init>":()V16: aload_117: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;20: aload_221: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;24: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;27: astore 429: ldc #4 // String ab31: astore 533: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;36: aload_337: aload 439: if_acmpne 4642: iconst_143: goto 4746: iconst_047: invokevirtual #10 // Method java/io/PrintStream.println:(Z)V50: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;53: aload_354: aload 556: if_acmpne 6359: iconst_160: goto 6463: iconst_064: invokevirtual #10 // Method java/io/PrintStream.println:(Z)V67: returnLineNumberTable:line 11: 0line 12: 3line 13: 6line 14: 9line 15: 29line 17: 33line 18: 50line 19: 67LocalVariableTable:Start Length Slot Name Signature0 68 0 args [Ljava/lang/String;3 65 1 s1 Ljava/lang/String;6 62 2 s2 Ljava/lang/String;9 59 3 s3 Ljava/lang/String;29 39 4 s4 Ljava/lang/String;33 35 5 s5 Ljava/lang/String;StackMapTable: number_of_entries = 4frame_type = 255 /* full_frame */offset_delta = 46locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String ]stack = [ class java/io/PrintStream ]frame_type = 255 /* full_frame */offset_delta = 0locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String ]stack = [ class java/io/PrintStream, int ]frame_type = 79 /* same_locals_1_stack_item */stack = [ class java/io/PrintStream ]frame_type = 255 /* full_frame */offset_delta = 0locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String ]stack = [ class java/io/PrintStream, int ] } SourceFile: "Demo1_22.java"
      • 對于字符串變量拼接String s4 = s1 + s2;:原理是 StringBuilder (1.8)

        其過程為:new StringBuilder().append("a").append("b").toString() ,即`new String(“ab”),其應(yīng)當(dāng)存儲在堆中,而不是常量池

      • 對于字符常量拼接String s5 = "a" + "b"; :原理是編譯期優(yōu)化

        javac 在編譯期間的優(yōu)化,結(jié)果已經(jīng)在編譯期確定為"ab",因此會直接找到常量池中的值

    • 字符串延遲加載:在執(zhí)行時是執(zhí)行一行創(chuàng)建一個對象放入串池

      package cn.itcast.jvm.t1.stringtable;/*** 演示字符串字面量也是【延遲】成為對象的*/ public class TestString {public static void main(String[] args) {int x = args.length;System.out.println(); // 字符串個數(shù) 2177System.out.print("1"); // 字符串個數(shù) 2178System.out.print("2");System.out.print("3");System.out.print("4");System.out.print("5");System.out.print("6");System.out.print("7");System.out.print("8");System.out.print("9");System.out.print("0"); // 字符串個數(shù) 2187System.out.print("1"); // 字符串個數(shù) 2187System.out.print("2");System.out.print("3");System.out.print("4");System.out.print("5");System.out.print("6");System.out.print("7");System.out.print("8");System.out.print("9");System.out.print("0");System.out.print(x); // 字符串個數(shù)} }

      可以使用IDEA的Debug工具進(jìn)行內(nèi)存查看:

    • 可以使用 intern 方法,主動將串池中還沒有的字符串對象放入串池

      • 1.8 將這個字符串對象嘗試放入串池,如果有則并不會放入,如果沒有則放入串池, 會把串池中的對象返回

        • 串池中有此字符串對象,不會放入package cn.itcast.jvm.t1.stringtable;public class Demo1_23 {// ["ab", "a", "b"]public static void main(String[] args) {String x = "ab";String s = new String("a") + new String("b");// 堆 new String("a") new String("b") new String("ab")String s2 = s.intern(); // 將這個字符串對象嘗試放入串池,如果有則并不會放入,如果沒有則放入串池, 會把串池中的對象返回System.out.println( s2 == x);//trueSystem.out.println( s == x);//false} }
        • 串池中沒有此字符串對象,放入串池, 并把串池中的對象返回package cn.itcast.jvm.t1.stringtable;public class Demo1_23 {// ["ab", "a", "b"]public static void main(String[] args) {String s = new String("a") + new String("b");// 堆 new String("a") new String("b") new String("ab")String s2 = s.intern(); // 將這個字符串對象嘗試放入串池,如果有則并不會放入,如果沒有則放入串池, 會把串池中的對象返回String x = "ab";System.out.println( s2 == x);//trueSystem.out.println( s == x);//true} }
      • 1.6 將這個字符串對象嘗試放入串池,如果有則并不會放入,如果沒有會把此對象復(fù)制一份,放入串池, 會把串池中的對象返回

        • 串池中有此字符串對象,不會放入package cn.itcast.jvm.t1.stringtable;public class Demo1_23 {// ["ab", "a", "b"]public static void main(String[] args) {String x = "ab";String s = new String("a") + new String("b");// 堆 new String("a") new String("b") new String("ab")String s2 = s.intern(); // 將這個字符串對象嘗試放入串池,如果有則并不會放入,如果沒有則放入串池, 會把串池中的對象返回System.out.println( s2 == x);//trueSystem.out.println( s == x);//false} }
        • 串池中沒有此字符串對象,放入串池, 并把串池中的對象返回package cn.itcast.jvm.t1.stringtable;public class Demo1_23 {// ["ab", "a", "b"]public static void main(String[] args) {String s = new String("a") + new String("b");// 堆 new String("a") new String("b") new String("ab")String s2 = s.intern(); // 將這個字符串對象嘗試放入串池,如果有則并不會放入,如果沒有則放入串池, 會把串池中的對象返回//s 拷貝一份,放入串池String x = "ab";System.out.println( s2 == x);//trueSystem.out.println( s == x);//false} }
    • 面試題:

      package cn.itcast.jvm.t1.stringtable;public class Demo1_21 {public static void main(String[] args) {String s1 = "a";String s2 = "b";String s3 = "a" + "b"; // abString s4 = s1 + s2; // new String("ab")String s5 = "ab";String s6 = s4.intern();// 問System.out.println(s3 == s4); // falseSystem.out.println(s3 == s5); // trueSystem.out.println(s3 == s6); // trueString x2 = new String("c") + new String("d"); // new String("cd")String x1 = "cd";x2.intern();// 問,如果調(diào)換了【最后兩行代碼】的位置呢,如果是jdk1.6呢//調(diào)換位置后:true//jdk1.6:falseSystem.out.println(x1 == x2);//false} }

    StringTable位置

    • jdk1.6時,StringTable是常量池的一部分,隨常量池存儲在永久代中;jdk1.7/jdk1.8時StringTable從永久代轉(zhuǎn)移到了堆中。
    • 原因:
      永久代內(nèi)存回收效率很低,只有Full GC時才會觸發(fā)永久代的垃圾回收,而Full GC只有在整個老年代的空間不足時才會觸發(fā),時機(jī)較晚。而StringTable使用頻繁,其中存儲著大量字符串常量,若其回收效率不高就會占用大量內(nèi)存,導(dǎo)致永久代內(nèi)存不足。因此jdk1.7 / jdk1.8開始將StringTable從永久代轉(zhuǎn)移到了堆中,只需要Minor GC就會對用不到的字符串進(jìn)行堆垃圾回收,減輕了字符串對內(nèi)存的占用。

    StringTable垃圾回收機(jī)制

    • StringTable在內(nèi)存緊張時,會進(jìn)行垃圾回收。

    StringTable調(diào)優(yōu)

    • 調(diào)整 -XX:StringTableSize=桶個數(shù)
      StringTable是由HashTable實(shí)現(xiàn)的,所以可以適當(dāng)增加HashTable桶的個數(shù),來減少字符串放入串池所需要的時間
    • 考慮將字符串對象是否入池
      若字符串對象很多且大量重復(fù),可以考慮使用intern方法將字符串入池,減少重復(fù)對象

    1.6 直接內(nèi)存

    1.6.1 定義

    • 直接內(nèi)存Direct Memory:
      • 常見于NIO操作時,用于數(shù)據(jù)緩沖區(qū)
      • 分配回收成本較高,但讀寫性能高
      • 不受JVM內(nèi)存回收管理

    1.6.2 使用

    • 文件讀寫流程:

      系統(tǒng)緩沖區(qū)的數(shù)據(jù)java不能直接使用,會再將其讀入到j(luò)ava緩沖區(qū),造成了不必要的數(shù)據(jù)的復(fù)制。
    • 使用直接緩沖區(qū):ByteBuffer.allocateDirect(內(nèi)存大小)

      操作系統(tǒng)會劃分一段內(nèi)存,此部分內(nèi)存是系統(tǒng)與java共享的,里邊的數(shù)據(jù)系統(tǒng)與java都可以訪問,少了一次緩沖區(qū)的復(fù)制操作,因此速度得到了成倍的提升。

    1.6.3 分配和回收原理

    • 直接內(nèi)存的回收不是通過JVM的垃圾回收來釋放的,而是使用了 Unsafe 對象完成直接內(nèi)存的分配回收,并且回收需要主動調(diào)用 freeMemory 方法

    • ByteBuffer 的實(shí)現(xiàn)類內(nèi)部,使用了 Cleaner (虛引用)監(jiān)測 ByteBuffer 對象一旦ByteBuffer 對象被垃圾回收,那么就會由 ReferenceHandler 線程(監(jiān)測虛引用對象)通過 Cleaner 的 clean 方法自動調(diào)用 freeMemory 來釋放直接內(nèi)存

    • 具體過程:

    • 使用ByteBuffer.allocateDirect(內(nèi)存大小)創(chuàng)建直接內(nèi)存

    • 創(chuàng)建DirectByteBuffer對象

    • cleaner的回調(diào)任務(wù)對象Deallocator中包含主動垃圾回收方法unsafe.freeMemory

    • 當(dāng)ReferenceHandler 線程監(jiān)測到cleaner關(guān)聯(lián)的對象被回收后(this對象,即ByteBuffer),會自動觸發(fā)cleaner對象的clean方法

    • clean方法會執(zhí)行回調(diào)任務(wù)對象Deallocator的run方法,即3中的unsafe.freeMemory進(jìn)而釋放直接內(nèi)存

    • 當(dāng)使用-XX:+DisableExplicitGC 參數(shù)禁用顯式的垃圾回收System.gc()時,ByteBuffer無法被回收進(jìn)而導(dǎo)致直接內(nèi)存無法釋放,此時可以通過直接使用unsafe.freeMemory進(jìn)行主動內(nèi)存回收

      主動調(diào)用unsafe.freeMemory:

    2. 垃圾回收

    2.1 如何判斷對象可以回收

    2.1.1 引用計數(shù)

    • 記錄當(dāng)前對象的引用次數(shù),當(dāng)引用次數(shù)為0時則進(jìn)行回收。
    • 缺點(diǎn):當(dāng)兩個對象互相引用但并沒有其他對象再引用它們時,其引用次數(shù)都為1,無法對其進(jìn)行回收釋放。
    • java不采用引用計數(shù)法,而是采用可達(dá)性分析算法

    2.1.2 可達(dá)性分析算法

    • JVM中的垃圾回收器通過可達(dá)性分析來探索所有存活的對象
    • 掃描堆中的對象,看能否沿著GC Root對象為起點(diǎn)的引用鏈找到該對象,如果找不到,則表示可以回收
    • 哪些對象可以作為GC Root?
      • System Class:系統(tǒng)類 / 核心類
      • Native Stack:本地方法調(diào)用的java對象
      • Thread:活動線程中使用的對象,即棧幀內(nèi)的變量等引用的對象
      • Busy Monitor:正在加鎖的對象

    2.1.3 五種引用


    實(shí)現(xiàn)部分為強(qiáng)引用,虛線為其他引用。

    強(qiáng)引用

    • 只有GC Root都不通過強(qiáng)引用引用該對象時,該對象才能被垃圾回收。
      • 如上圖B、C對象都不引用A1對象時,A1對象才會被回收

    軟引用(SoftReference)

    • 沒有被直接的強(qiáng)引用所引用
    • 僅有軟引用引用該對象時,在垃圾回收后,仍內(nèi)存不足時,會回收軟引用所引用的對象
      • 如上圖如果B對象不再引用A2對象且內(nèi)存不足時,軟引用本身不會被清理,軟引用所引用的A2對象會被回收
      • 例子:對于4M的byte數(shù)組,在垃圾回收后,仍內(nèi)存不足時進(jìn)行回收
    • 若要清理軟引用與弱引用本身,需要借助引用隊列實(shí)現(xiàn)。

    弱引用(WeakReference)

    • 沒有被直接的強(qiáng)引用所引用
    • 僅有弱引用引用該對象時,在垃圾回收時,無論內(nèi)存是否充足,都會回收弱引用所引用的對象
      • 如上圖如果B對象不再引用A3對象,則A3對象會被回收
      • 例子:
    • 若要清理軟引用與弱引用本身,需要借助引用隊列實(shí)現(xiàn)。

    虛引用(PhantomReference)

    • 必須配合引用隊列使用。主要配合ByteBuffer使用被引用對象回收時,會將虛引用入隊,由ReferenceHandler線程調(diào)用虛引用相關(guān)方法釋放直接內(nèi)存。
    • 當(dāng)虛引用對象所引用的對象被回收以后,虛引用對象就會被放入引用隊列中,從而間接的用一個線程調(diào)用虛引用對象的方法
    • 舉例:直接內(nèi)存的回收——Cleaner (虛引用)
      ByteBuffer 的實(shí)現(xiàn)類內(nèi)部,使用了 Cleaner (虛引用)監(jiān)測 ByteBuffer 對象一旦ByteBuffer 對象被垃圾回收,Cleaner 就回進(jìn)入引用隊列,ReferenceHandler 線程會查看引用隊列,若其中有Cleaner,就會由 ReferenceHandler 線程(監(jiān)測虛引用對象)通過 Cleaner 的 clean 方法自動調(diào)用 freeMemory 來釋放直接內(nèi)存。

    終結(jié)器引用(FinalReference)

    • 所有的類都繼承自O(shè)bject類,Object類有一個finalize方法。當(dāng)某個對象重寫了finalize()后,此對象不再被其他的對象所強(qiáng)引用時,垃圾回收時會先將終結(jié)器引用對象放入引用隊列中,然后Finalizer 線程會查看引用隊列,若其中有終結(jié)器引用,則會通過終結(jié)器引用找到它所引用的對象并調(diào)用該對象的finalize方法。調(diào)用以后,再進(jìn)行一次GC,該對象才可以被垃圾回收。
    • 不推薦使用finalize()方法進(jìn)行內(nèi)存釋放,其條件過于苛刻。

    引用隊列

    2.2 垃圾回收算法

    三種垃圾回收算法協(xié)同工作,并不是單純的采用某一種。

    2.2.1 標(biāo)記清除(Mark Sweep)

    • 定義:在虛擬機(jī)執(zhí)行垃圾回收的過程中,先采用標(biāo)記算法確定可回收對象,然后垃圾收集器根據(jù)標(biāo)識清除相應(yīng)的內(nèi)容,給堆內(nèi)存騰出相應(yīng)的空間
      • 這里的騰出內(nèi)存空間并不是將內(nèi)存空間的字節(jié)清0,而是記錄下這段內(nèi)存的起始結(jié)束地址,下次分配內(nèi)存的時候,會直接覆蓋這段內(nèi)存
    • 優(yōu)點(diǎn):速度快,只需要記錄起始結(jié)束地址
    • 缺點(diǎn):不會對內(nèi)存進(jìn)行整理,容易產(chǎn)生大量的內(nèi)存碎片,可能無法滿足大對象的內(nèi)存分配,一旦導(dǎo)致無法分配對象,那就會導(dǎo)致jvm啟動gc,一旦啟動gc,應(yīng)用程序就會暫停,導(dǎo)致應(yīng)用的響應(yīng)速度變慢

    2.2.2 標(biāo)記整理(Mark Compact)

    • 定義:先采用標(biāo)記算法確定可回收對象,然后將不被GC Root引用的對象回收,清除其占用的內(nèi)存空間,然后整理剩余對象的內(nèi)存空間。
    • 優(yōu)點(diǎn):減少內(nèi)存碎片。
    • 缺點(diǎn):整理需要消耗一定的時間,速度較慢。

    2.2.3 復(fù)制(Copy)

    • 步驟:
    • 標(biāo)記可回收對象
    • 將不可回收的對象從FROM轉(zhuǎn)移到TO中,并把FROM清空
    • 交換FROM和TO
    • 優(yōu)點(diǎn):減少內(nèi)存碎片。
    • 缺點(diǎn):占用雙倍的內(nèi)存空間。

    2.3 分代垃圾回收

    • 新生代采用復(fù)制算法,老年代使用標(biāo)記整理算法

    • 過程:

      • 對象首先分配在伊甸園區(qū)域;
      • 新生代空間不足時,觸發(fā)minor gc,伊甸園和fom存活的對象使用copy復(fù)制到to中,存活的對象年齡加1并且交換from to;
      • minor gc會引發(fā)stop the world,暫停其它用戶的線程,等垃圾回收結(jié)束,用戶線程才恢復(fù)運(yùn)行;
      • 當(dāng)對象壽命超過閾值時,會晉升至老年代,最大壽命是15(4bit);
      • 當(dāng)老年代空間不足,會先嘗試觸發(fā)minor gc,如果之后空間仍不足,那么觸發(fā)full gc,full gc的STW時間更長;
      • full gc后內(nèi)存仍不足時則會OutOfMemory2。
    • 相關(guān)VM參數(shù)

    2.4 垃圾回收器

    2.4.1 串行垃圾回收器

    • 單線程
    • 堆內(nèi)存較小,適合個人電腦
    • 開啟:-XX:+UseSerialGC= Serial + SerialOld
      • 串行垃圾回收器分為Serial和SerialOld兩部分:
        • Serial工作在新生代,采用復(fù)制算法
        • SerialOld工作在老年代,采用標(biāo)記整理算法
      • 在進(jìn)行垃圾回收時,先將所有的線程在安全點(diǎn)暫停,防止垃圾回收過程中內(nèi)存地址的修改導(dǎo)致程序出錯。然后開始垃圾回收線程,其他線程阻塞。等垃圾回收線程結(jié)束后,其他用戶線程再開始運(yùn)行。

    2.4.2 吞吐量優(yōu)先垃圾回收器

    • 多線程
    • 堆內(nèi)存較大,多核 cpu
    • 讓單位時間內(nèi),STW 的時間最短(0.2 + 0.2 = 0.4),垃圾回收時間占比最低,這樣就稱吞吐量高
    • 開啟:
      • -XX:+UseParallelGC ~ -XX:+UseParalleloldGC
        • UseParallelGC 新生代的并行垃圾回收器,采用采用復(fù)制算法
        • UseParalleloldG 老年代的并行垃圾回收器,采用標(biāo)記整理算法
        • 兩個關(guān)聯(lián),開啟一個時另一個會自動開啟
      • -XX:+UseAdaptiveSizePolicy:采用自適應(yīng)的調(diào)整策略,調(diào)整新生代的大小、新生代內(nèi)部各部分的比例和壽命閾值等
      • -XX:GCTimeRatio=ratio:根據(jù)設(shè)定的吞吐量目標(biāo)(1/(1+ratio))嘗試調(diào)整堆的大小
      • -XX:MaxGCPauseMillis=ms:最大暫停毫秒數(shù),默認(rèn)值200ms
      • -XX:ParallelGCThreads=n:指定并行垃圾回收線程數(shù),一般與CPU核數(shù)相同

    2.4.3 響應(yīng)時間優(yōu)先垃圾回收器

    • 多線程
    • 堆內(nèi)存較大,多核 cpu
    • 盡可能讓單次 STW 的時間最短(0.1 + 0.1 + 0.1 + 0.1 + 0.1 = 0.5)
    • 開啟:
    • -XX:+UseConcMarkSweepGC~-XX:+UseParNewGC~SerialOld
      • ConcMarkSweepGC,并發(fā)的使用標(biāo)記清除算法的垃圾回收器。并發(fā)是指在進(jìn)行垃圾回收線程的某些階段,其他用戶線程可以同時執(zhí)行,進(jìn)一步減少了STW的時間(具體見上圖)。
        其是工作在老年代的垃圾回收器。
      • UseParNewGC是工作在新生代的垃圾回收器。
      • SerialOld是當(dāng)ConcMarkSweepGC并發(fā)失敗時采取的補(bǔ)救措施,其會讓老年代的CMS垃圾回收器退化到單線程的垃圾回收器SerialOld。
    • -XX:ParallelGCThreads=n~-XX:ConcGCThreads=threads
      • ParallelGCThreads=n:并行的垃圾回收線程數(shù),一般與CPU核數(shù)相同
      • ConcGCThreads=threads:并發(fā)的垃圾回收線程數(shù),建議設(shè)置為并行垃圾回收線程數(shù)的1/4
    • -XX:CMSInitiatingOccupancyFraction=percent:執(zhí)行CMS垃圾回收的內(nèi)存占比,控制CMS垃圾(主要是垃圾回收過程中產(chǎn)生的浮動垃圾)回收的時機(jī)
    • -XX:+CMSScavengeBeforeRemark:在重新標(biāo)記前對新生代進(jìn)行一次垃圾回收(UseParNewGC),減少時間浪費(fèi)
      在重現(xiàn)標(biāo)記的階段會掃描整個堆,從新生代查找其引用老年代的對象做可達(dá)性分析,但新生代的對象非常多且其中包含很多無用垃圾,這就造成浪費(fèi)大量的時間查找無用的引用。

    CMS 收集器

    • Concurrent Mark Sweep,并發(fā)的使用標(biāo)記清除算法的垃圾回收器,是一種以獲取最短回收停頓時間為目標(biāo)的老年代收集器
    • 特點(diǎn):基于標(biāo)記-清除算法實(shí)現(xiàn)。并發(fā)收集、低停頓,但是會產(chǎn)生內(nèi)存碎片
    • 應(yīng)用場景:適用于注重服務(wù)的響應(yīng)速度,希望系統(tǒng)停頓時間最短,給用戶帶來更好的體驗等場景下。如web程序、b/s服務(wù)
    • CMS收集器的運(yùn)行過程分為下列4步:
      • 初始標(biāo)記:標(biāo)記GC Roots能直接到的對象。速度很快但是仍存在Stop The World問題
      • 并發(fā)標(biāo)記:進(jìn)行GC Roots Tracing 的過程,找出存活對象且用戶線程可并發(fā)執(zhí)行
      • 重新標(biāo)記:為了修正并發(fā)標(biāo)記期間因用戶程序繼續(xù)運(yùn)行而導(dǎo)致標(biāo)記產(chǎn)生變動的那一部分對象的標(biāo)記記錄。仍然存在Stop The World問題
      • 并發(fā)清除:對標(biāo)記的對象進(jìn)行清除回收
    • CMS收集器的內(nèi)存回收過程是與用戶線程一起并發(fā)執(zhí)行的
    • 由于CMS采取標(biāo)記清除算法進(jìn)行垃圾回收,會產(chǎn)生較多的內(nèi)存碎片,當(dāng)內(nèi)存碎片足夠多時會造成并發(fā)失敗,垃圾回收器會退化為SerialOld進(jìn)行內(nèi)存空間整理,此時垃圾回收時間會突然增長,進(jìn)而導(dǎo)致響應(yīng)時間優(yōu)先的垃圾回收器反而耗時很長,帶來不好的用戶體驗。

    2.4.4 G1垃圾回收器

    • 定義:Garbage First
      • 2004 論文發(fā)布
      • 2009 JDK 6u14 體驗
      • 2012 JDK 7u4 官方支持
      • 2017 JDK 9 默認(rèn),替代CMS 收集器
    • 適用場景
      • 同時注重吞吐量(Throughput)和低延遲(Low latency),默認(rèn)的暫停目標(biāo)是 200 ms
      • 超大堆內(nèi)存,會將堆劃分為多個大小相等的 Region
      • 整體上是 標(biāo)記+整理 算法,兩個區(qū)域之間復(fù)制算法
    • 相關(guān) JVM 參數(shù)
      • -XX:+UseG1GC
      • -XX:G1HeapRegionSize=size
      • -XX:MaxGCPauseMillis=time

    G1垃圾回收階段

    • Young Collection:新生代垃圾收集

      • 對象首先存在伊甸園中,當(dāng)伊甸園的空間將要占滿,會觸發(fā)Young Collection,將伊甸園的幸存對象以復(fù)制算法放入幸存區(qū)。當(dāng)幸存區(qū)的空間快要占滿或幸存區(qū)對象年齡達(dá)到一定閾值,會再次觸發(fā)Young Collection,幸存區(qū)的部分對象會晉升至老年代,年齡不夠的會復(fù)制到另一塊幸存區(qū)。
      • 會 STW
    • Young Collection + CM:新生代垃圾收集+并發(fā)標(biāo)記

      • 在 Young GC 時會進(jìn)行 GC Root 的初始標(biāo)記
      • 老年代占用堆空間比例達(dá)到閾值時,進(jìn)行并發(fā)標(biāo)記(不會 STW),由下面的 JVM 參數(shù)決定
        -XX:InitiatingHeapOccupancyPercent=percent (默認(rèn)45%)
    • Mixed Collection:混合收集

      • 會對E、S、O進(jìn)行全面垃圾回收
      • 最終標(biāo)記(Remark)會STW
      • 拷貝存活(Evacuation)會STW
        -XX:MaxGCPauseMillis=ms:最大暫停時間,使得在有限的時間里優(yōu)先回收老年代中回收價值較高的(垃圾多的)
    • 三個階段是循環(huán)的過程,開始是新生代垃圾收集,經(jīng)過一段時間,老年代的內(nèi)存超過閾值了,會進(jìn)行新生代垃圾收集+并發(fā)標(biāo)記,此階段完成后會進(jìn)行混合收集,對新生代和老年代進(jìn)行一次規(guī)模較大的垃圾收集。當(dāng)內(nèi)存逐漸釋放,又會進(jìn)入新生代垃圾收集……

    Full GC

    • SerialGC
      • 新生代內(nèi)存不足發(fā)生的垃圾收集 - minor gc
      • 老年代內(nèi)存不足發(fā)生的垃圾收集 - full gc
    • ParallelGC
      • 新生代內(nèi)存不足發(fā)生的垃圾收集 - minor gc
      • 老年代內(nèi)存不足發(fā)生的垃圾收集 - full gc
    • CMS
      • 新生代內(nèi)存不足發(fā)生的垃圾收集 - minor gc
      • 老年代內(nèi)存不足 - 當(dāng)內(nèi)存碎片足夠多時會造成并發(fā)失敗,并發(fā)收集失敗時退化為串行收集,此時才會進(jìn)行full gc
    • G1
      • 新生代內(nèi)存不足發(fā)生的垃圾收集 - minor gc
      • 老年代內(nèi)存不足 - 老年代占用堆空間比例達(dá)到閾值(默認(rèn)45%)時,進(jìn)行并發(fā)標(biāo)記(不會 STW)與混合收集,當(dāng)回收速度快于垃圾產(chǎn)生速度時會進(jìn)行并發(fā)收集,只有當(dāng)垃圾產(chǎn)生速度快于回收速度時并發(fā)收集失敗,退化為串行收集,此時才會進(jìn)行full gc

    Young Collection 跨代引用

    • 新生代回收的跨代引用(老年代引用新生代)問題
      在進(jìn)行Young Collection時,首先要找到GC Root根對象,根據(jù)其進(jìn)行可達(dá)性分析找到存活的對象復(fù)制入幸存區(qū),但根對象一部分是來自老年代的,且老年代的存活對象較多,對老年代進(jìn)行遍歷查找效率很低,因此采用卡表技術(shù)對老年代進(jìn)行分區(qū),每個card大小約為512k。若此card中有對象引用了新生代的對象,則將其標(biāo)記為臟卡。因此在Young Collection查找根對象時只需要關(guān)注臟卡,提高效率。
    • Remembered Set:新生代對象記錄外部的引用,即對應(yīng)的臟卡是哪些
    • 在引用變更時通過 post-write barrier + dirty card queue
    • concurrent refinement threads 更新 Remembered Set

    Remake重新標(biāo)記


    黑色:已被處理,需要保留的
    灰色:正在處理中的
    白色:還未處理的

    • 在并發(fā)標(biāo)記的過程中其他用戶線程在同時進(jìn)行,當(dāng)還沒處理到C前B與C的引用斷掉,此時處理C就是一個需要回收的對象。

      但還有可能處理完C之后其他并發(fā)執(zhí)行的線程又對C進(jìn)行了引用,如下圖的A,此時C的標(biāo)記已經(jīng)結(jié)束,在回收時會被回收,會導(dǎo)致程序異常,因此需要重新標(biāo)記。
    • 重新標(biāo)記remark

    • pre-write barrier + satb_mark_queue
      如果對象的引用發(fā)生改變,JVM就回給其加入寫屏障pre-write barrier,并執(zhí)行寫屏障指令,將C加入隊列中 satb_mark_queue,并把C標(biāo)記為灰色。并發(fā)標(biāo)記結(jié)束后,進(jìn)入remark階段,此時會STW,并將satb_mark_queue中的對象一個個取出來進(jìn)行檢查,如此對象有強(qiáng)引用引用時,將其修改為黑色,進(jìn)行對象保留。

    G1的部分優(yōu)化

    JDK 8u20 字符串去重
    • 開啟:-XX:+UseStringDeduplication(默認(rèn)打開)
      • 將所有新分配的字符串放入一個隊列
      • 當(dāng)新生代回收時,G1并發(fā)檢查是否有字符串重復(fù)
      • 如果它們值一樣,讓它們引用同一個 char[]
    • 注意:與 String.intern() 不一樣
      • String.intern() 關(guān)注的是字符串對象
      • 而字符串去重關(guān)注的是 char[]
      • 在 JVM 內(nèi)部,使用了不同的字符串表
    • 優(yōu)點(diǎn):節(jié)省大量內(nèi)存
    • 缺點(diǎn):略微多占用了 cpu 時間,新生代回收時間略微增加
    JDK 8u40 并發(fā)標(biāo)記類卸載
    • 所有對象都經(jīng)過并發(fā)標(biāo)記后,就能知道哪些類不再被使用,當(dāng)一個類加載器(自定義)的所有類都不再使用,則卸載它所加載的所有類
    • 開啟:-XX:+ClassUnloadingWithConcurrentMark 默認(rèn)啟用
    JDK 8u60 回收巨型對象
    • 一個對象大于 region 的一半時,稱之為巨型對象
    • G1 不會對巨型對象進(jìn)行拷貝
    • G1 回收時被優(yōu)先考慮
    • G1 會跟蹤老年代所有 incoming 引用,這樣老年代 incoming 引用為0 的巨型對象就可以在新生代垃圾回收時處理掉
    JDK 9 并發(fā)標(biāo)記起始時間的調(diào)整
    • 并發(fā)標(biāo)記必須在堆空間占滿前完成,否則會退化為 FullGC
    • JDK 9 之前需要使用 -XX:InitiatingHeapOccupancyPercent對老年代閾值進(jìn)行調(diào)整
    • JDK 9 可以動態(tài)調(diào)整
      • -XX:InitiatingHeapOccupancyPercent用來設(shè)置初始值
      • 進(jìn)行數(shù)據(jù)采樣并動態(tài)調(diào)整
      • 總會添加一個安全的空檔空間
    JDK 9 更高效的回收
    • 250+增強(qiáng)
    • 180+bug修復(fù)
    • https://docs.oracle.com/en/java/javase/12/gctuning

    2.5 垃圾回收調(diào)優(yōu)

    • 預(yù)備知識
      • 掌握 GC 相關(guān)的 VM 參數(shù),會基本的空間調(diào)整
        查看虛擬機(jī)運(yùn)行參數(shù):"java_path" -XX:+PrintFlagsFinal -version | findstr "GC"
      • 掌握相關(guān)工具
      • 明白一點(diǎn):調(diào)優(yōu)跟應(yīng)用、環(huán)境有關(guān),沒有放之四海而皆準(zhǔn)的法則

    2.5.1 調(diào)優(yōu)領(lǐng)域

    • 內(nèi)存
    • 鎖競爭
    • cpu 占用
    • io

    2.5.2 確定目標(biāo)

    • 【低延遲】還是【高吞吐量】,選擇合適的回收器
    • 低延遲:CMS,G1,ZGC
    • 高吞吐量:ParallelGC
    • 其他虛擬機(jī):Zing

    2.5.3 最快的 GC是不發(fā)生 GC

    查看 FullGC 前后的內(nèi)存占用,考慮下面幾個問題:

    • 數(shù)據(jù)是不是太多?
      resultSet = statement.executeQuery("select * from 大表 limit n")
    • 數(shù)據(jù)表示是否太臃腫?
      • 對象圖
      • 對象大小:16 ,Integer 24 ,int4
    • 是否存在內(nèi)存泄漏?
      • static Map map =
      • 軟引用
      • 弱引用
      • 第三方緩存實(shí)現(xiàn)

    2.5.4 新生代調(diào)優(yōu)

    • 新生代的特點(diǎn)

      • 所有的 new 操作的內(nèi)存分配非常廉價
        • TLAB thread-local allocation buffer:線程分布局部緩沖區(qū),作用是讓每個線程用自己私有的內(nèi)存進(jìn)行對象分配,因此多個線程同時創(chuàng)建對象時不會產(chǎn)生內(nèi)存沖突
      • 死亡對象的回收代價是零
      • 大部分對象用過即死
      • Minor GC 的時間遠(yuǎn)遠(yuǎn)低于 Full GC
    • 新生代內(nèi)存越大越好嗎?
      -Xmn:設(shè)置新生代的初始和最大值

      Sets the initial and maximum size (in bytes) of the heap for the young generation (nursery). GC is performed in this region more often than in other regions. If the size for the young generation is too small, then a lot of minor garbage collections are performed. If the size is too large, then only full garbage collections are performed, which can take a long time to complete. Oracle recommends that you keep the size for the young generation greater than 25% and less than 50% of the overall heap size.

      • 新生代內(nèi)存過小:會頻繁觸發(fā)Minor GC,造成頻繁STW,降低效率
      • 新生代內(nèi)存過大:導(dǎo)致老年代內(nèi)存不足,老年代內(nèi)存緊張會觸發(fā)Full GC,STW時間更長
      • 建議新生代占堆內(nèi)存大小的25%-50%,盡可能的大
    • 新生代內(nèi)存應(yīng)該盡可能大,估算內(nèi)存大小:【并發(fā)量 * (請求-響應(yīng))
      新生代能容納所有【并發(fā)量 * (請求-響應(yīng))】的數(shù)據(jù),即 每個請求相應(yīng)占用的內(nèi)存量*并發(fā)數(shù)

    • 幸存區(qū)設(shè)計:

      • 幸存區(qū)大到能保留【當(dāng)前活躍對象+需要晉升對象】
        幸存區(qū)太小會導(dǎo)致一部分存活時間較短的對象晉升至老年代,老年代對象full gc才會回收,進(jìn)而造成內(nèi)存浪費(fèi)
      • 晉升閾值配置得當(dāng),讓長時間存活對象盡快晉升,否則其會在幸存區(qū)反復(fù)被復(fù)制,增大GC負(fù)擔(dān),降低GC效率
        -XX:MaxTenuringThreshold=threshold:調(diào)整最大晉升閾值
        -XX:+PrintTenuringDistribution :打印晉升區(qū)存活對象的詳情,如下(對象年齡:占用空間,累計占用空間):Desired survivor size 48286924 bytes, new threshold 10 (max 10) - age 1: 28992024 bytes, 28992024 total - age 2: 1366864 bytes, 30358888 total - age 3: 1425912 bytes, 31784800 total ...

    2.5.5 老年代調(diào)優(yōu)

    • 以 CMS 為例
      • CMS 的老年代內(nèi)存越大越好,避免浮動垃圾引起的并發(fā)失敗,退化為SerialOld導(dǎo)致更長的STW
      • 先嘗試不做調(diào)優(yōu),如果沒有 Full GC 那么老年代內(nèi)存已經(jīng)足夠,否則先嘗試調(diào)優(yōu)新生代
      • 如調(diào)優(yōu)新生代還是不行,再觀察發(fā)生 Full GC 時老年代內(nèi)存占用,將老年代內(nèi)存在原有基礎(chǔ)上調(diào)大 1/4 ~ 1/3
        -XX:CMSInitiatingOccupancyFraction=percent:控制老年代的空間占用在到老年代的多少百分比時使用CMS進(jìn)行垃圾回收

    3. 類加載與字節(jié)碼技術(shù)

    3.1 類文件結(jié)構(gòu)

    • 一個簡單的 HelloWorld.javapackage cn.itcast.jvm.t5; // HelloWorld 示例 public class HelloWorld { public static void main(String[] args) { System.out.println("hello world"); } } 執(zhí)行 javac -parameters -d . HellowWorld.java
      編譯為 HelloWorld.class 后是這個樣子的:[root@localhost ~]# od -t xC HelloWorld.class 0000000 ca fe ba be 00 00 00 34 00 23 0a 00 06 00 15 09 0000020 00 16 00 17 08 00 18 0a 00 19 00 1a 07 00 1b 07 0000040 00 1c 01 00 06 3c 69 6e 69 74 3e 01 00 03 28 29 0000060 56 01 00 04 43 6f 64 65 01 00 0f 4c 69 6e 65 4e 0000100 75 6d 62 65 72 54 61 62 6c 65 01 00 12 4c 6f 63 0000120 61 6c 56 61 72 69 61 62 6c 65 54 61 62 6c 65 01 0000140 00 04 74 68 69 73 01 00 1d 4c 63 6e 2f 69 74 63 0000160 61 73 74 2f 6a 76 6d 2f 74 35 2f 48 65 6c 6c 6f 0000200 57 6f 72 6c 64 3b 01 00 04 6d 61 69 6e 01 00 16 0000220 28 5b 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 0000240 69 6e 67 3b 29 56 01 00 04 61 72 67 73 01 00 13 0000260 5b 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 0000300 6e 67 3b 01 00 10 4d 65 74 68 6f 64 50 61 72 61 0000320 6d 65 74 65 72 73 01 00 0a 53 6f 75 72 63 65 46 0000340 69 6c 65 01 00 0f 48 65 6c 6c 6f 57 6f 72 6c 64 0000360 2e 6a 61 76 61 0c 00 07 00 08 07 00 1d 0c 00 1e 0000400 00 1f 01 00 0b 68 65 6c 6c 6f 20 77 6f 72 6c 64 0000420 07 00 20 0c 00 21 00 22 01 00 1b 63 6e 2f 69 74 0000440 63 61 73 74 2f 6a 76 6d 2f 74 35 2f 48 65 6c 6c 0000460 6f 57 6f 72 6c 64 01 00 10 6a 61 76 61 2f 6c 61 0000500 6e 67 2f 4f 62 6a 65 63 74 01 00 10 6a 61 76 61 0000520 2f 6c 61 6e 67 2f 53 79 73 74 65 6d 01 00 03 6f 0000540 75 74 01 00 15 4c 6a 61 76 61 2f 69 6f 2f 50 72 0000560 69 6e 74 53 74 72 65 61 6d 3b 01 00 13 6a 61 76 0000600 61 2f 69 6f 2f 50 72 69 6e 74 53 74 72 65 61 6d 0000620 01 00 07 70 72 69 6e 74 6c 6e 01 00 15 28 4c 6a 0000640 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b 0000660 29 56 00 21 00 05 00 06 00 00 00 00 00 02 00 01 0000700 00 07 00 08 00 01 00 09 00 00 00 2f 00 01 00 01 0000720 00 00 00 05 2a b7 00 01 b1 00 00 00 02 00 0a 00 0000740 00 00 06 00 01 00 00 00 04 00 0b 00 00 00 0c 00 0000760 01 00 00 00 05 00 0c 00 0d 00 00 00 09 00 0e 00 0001000 0f 00 02 00 09 00 00 00 37 00 02 00 01 00 00 00 0001020 09 b2 00 02 12 03 b6 00 04 b1 00 00 00 02 00 0a 0001040 00 00 00 0a 00 02 00 00 00 06 00 08 00 07 00 0b 0001060 00 00 00 0c 00 01 00 00 00 09 00 10 00 11 00 00 0001100 00 12 00 00 00 05 01 00 10 00 00 00 01 00 13 00 0001120 00 00 02 00 14
    • 根據(jù)JVM規(guī)范,類文件結(jié)構(gòu)如下:

      參考鏈接:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html

    3.1.1 魔數(shù)

    0~3 字節(jié),表示它是否是【class】類型的文件:
    0000000 ca fe ba be 00 00 00 34 00 23 0a 00 06 00 15 09

    3.1.2 版本

    4~7 字節(jié),表示類的版本 00 34(十六進(jìn)制,轉(zhuǎn)換為十進(jìn)制為52) 表示是 Java 8
    0000000 ca fe ba be 00 00 00 34 00 23 0a 00 06 00 15 09

    3.1.3 常量池

    • 8 ~ 9 字節(jié),表示常量池長度,00 23 (35) 表示常量池有 #1 ~ #34項,注意 #0 項不計入,也沒有值
      0000000 ca fe ba be 00 00 00 34 00 23 0a 00 06 00 15 09
      • 第#1項 0a 表示一個 Method 信息(查上邊的表,0a對應(yīng)十進(jìn)制為10,則其對應(yīng)的類型為方法引用),00 0600 15(21) 表示它引用了常量池中 #6#21 項來獲得這個方法的【所屬類】和【方法名
        0000000 ca fe ba be 00 00 00 34 00 23 0a 00 06 00 15 09
      • 第#2項 09 表示一個 Field 信息,00 16(22)和 00 17(23) 表示它引用了常量池中 #22 和 # 23 項來獲得這個成員變量的【所屬類】和【成員變量名】
        0000000 ca fe ba be 00 00 00 34 00 23 0a 00 06 00 15 09
        0000020 00 16 00 17 08 00 18 0a 00 19 00 1a 07 00 1b 07
      • 第#3項 08 表示一個字符串常量名稱,00 18(24)表示它引用了常量池中 #24 項
        0000020 00 16 00 17 08 00 18 0a 00 19 00 1a 07 00 1b 07
      • 第#4項 0a 表示一個 Method 信息,00 19(25) 和 00 1a(26) 表示它引用了常量池中 #25 和 #26項來獲得這個方法的【所屬類】和【方法名】
        0000020 00 16 00 17 08 00 18 0a 00 19 00 1a 07 00 1b 07
      • 第#5項 07 表示一個 Class 信息,00 1b(27) 表示它引用了常量池中 #27 項
        0000020 00 16 00 17 08 00 18 0a 00 19 00 1a 07 00 1b 07
      • 第#6項 07 表示一個 Class 信息(查表),00 1c(28) 表示它引用了常量池中 #28 項
        0000020 00 16 00 17 08 00 18 0a 00 19 00 1a 07 00 1b 07
        0000040 00 1c 01 00 06 3c 69 6e 69 74 3e 01 00 03 28 29
      • 第#7項 01 表示一個 utf8 串,00 06 表示長度,3c 69 6e 69 74 3e 是【 <init>
        0000040 00 1c 01 00 06 3c 69 6e 69 74 3e 01 00 03 28 29
      • 第#8項 01 表示一個 utf8 串,00 03 表示長度,28 29 56 是【()V】其實(shí)就是表示無參、無返回值
        0000040 00 1c 01 00 06 3c 69 6e 69 74 3e 01 00 03 28 29
        0000060 56 01 00 04 43 6f 64 65 01 00 0f 4c 69 6e 65 4e
      • 第#9項 01 表示一個 utf8 串,00 04 表示長度,43 6f 64 65 是【Code】
        0000060 56 01 00 04 43 6f 64 65 01 00 0f 4c 69 6e 65 4e
      • 第#10項 01 表示一個 utf8 串,00 0f(15) 表示長度,4c 69 6e 65 4e 75 6d 62 65 72 54 61 62 6c 65是【LineNumberTable】
        0000060 56 01 00 04 43 6f 64 65 01 00 0f 4c 69 6e 65 4e
        0000100 75 6d 62 65 72 54 61 62 6c 65 01 00 12 4c 6f 63
      • 第#11項 01 表示一個 utf8 串,00 12(18) 表示長度,4c 6f 63 61 6c 56 61 72 69 61 62 6c 65 54 61
        62 6c 65是【LocalVariableTable】
        0000100 75 6d 62 65 72 54 61 62 6c 65 01 00 12 4c 6f 63
        0000120 61 6c 56 61 72 69 61 62 6c 65 54 61 62 6c 65 01
      • 第#12項 01 表示一個 utf8 串,00 04 表示長度,74 68 69 73 是【this】
        0000120 61 6c 56 61 72 69 61 62 6c 65 54 61 62 6c 65 01
        0000140 00 04 74 68 69 73 01 00 1d 4c 63 6e 2f 69 74 63
      • 第#13項 01 表示一個 utf8 串,00 1d(29) 表示長度,是【Lcn/itcast/jvm/t5/HelloWorld;】
        0000140 00 04 74 68 69 73 01 00 1d 4c 63 6e 2f 69 74 63
        0000160 61 73 74 2f 6a 76 6d 2f 74 35 2f 48 65 6c 6c 6f
        0000200 57 6f 72 6c 64 3b 01 00 04 6d 61 69 6e 01 00 16
      • 第#14項 01 表示一個 utf8 串,00 04 表示長度,74 68 69 73 是【main】
        0000200 57 6f 72 6c 64 3b 01 00 04 6d 61 69 6e 01 00 16
      • 第#15項 01 表示一個 utf8 串,00 16(22) 表示長度,是【([Ljava/lang/String;)V】其實(shí)就是參數(shù)為
        字符串?dāng)?shù)組,無返回值
        0000200 57 6f 72 6c 64 3b 01 00 04 6d 61 69 6e 01 00 16
        0000220 28 5b 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72
        0000240 69 6e 67 3b 29 56 01 00 04 61 72 67 73 01 00 13
      • 第#16項 01 表示一個 utf8 串,00 04 表示長度,是【args】
        0000240 69 6e 67 3b 29 56 01 00 04 61 72 67 73 01 00 13
      • 第#17項 01 表示一個 utf8 串,00 13(19) 表示長度,是【[Ljava/lang/String;】
        0000240 69 6e 67 3b 29 56 01 00 04 61 72 67 73 01 00 13
        0000260 5b 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69
        0000300 6e 67 3b 01 00 10 4d 65 74 68 6f 64 50 61 72 61
      • 第#18項 01 表示一個 utf8 串,00 10(16) 表示長度,是【MethodParameters】
        0000300 6e 67 3b 01 00 10 4d 65 74 68 6f 64 50 61 72 61
        0000320 6d 65 74 65 72 73 01 00 0a 53 6f 75 72 63 65 46
      • 第#19項 01 表示一個 utf8 串,00 0a(10) 表示長度,是【SourceFile】
        0000320 6d 65 74 65 72 73 01 00 0a 53 6f 75 72 63 65 46
        0000340 69 6c 65 01 00 0f 48 65 6c 6c 6f 57 6f 72 6c 64
      • 第#20項 01 表示一個 utf8 串,00 0f(15) 表示長度,是【HelloWorld.java】
        0000340 69 6c 65 01 00 0f 48 65 6c 6c 6f 57 6f 72 6c 64
        0000360 2e 6a 61 76 61 0c 00 07 00 08 07 00 1d 0c 00 1e
      • 第#21項 0c 表示一個 【名+類型】,00 07 00 08 引用了常量池中 #7 #8 兩項
        0000360 2e 6a 61 76 61 0c 00 07 00 08 07 00 1d 0c 00 1e
      • 第#22項 07 表示一個 Class 信息,00 1d(29) 引用了常量池中 #29 項
        0000360 2e 6a 61 76 61 0c 00 07 00 08 07 00 1d 0c 00 1e
      • 第#23項 0c 表示一個 【名+類型】,00 1e(30) 00 1f (31)引用了常量池中 #30 #31 兩項
        0000360 2e 6a 61 76 61 0c 00 07 00 08 07 00 1d 0c 00 1e
        0000400 00 1f 01 00 0b 68 65 6c 6c 6f 20 77 6f 72 6c 64
      • 第#24項 01 表示一個 utf8 串,00 0f(15) 表示長度,是【hello world】
        0000400 00 1f 01 00 0b 68 65 6c 6c 6f 20 77 6f 72 6c 64
      • 第#25項 07 表示一個 Class 信息,00 20(32) 引用了常量池中 #32 項
        0000420 07 00 20 0c 00 21 00 22 01 00 1b 63 6e 2f 69 74
      • 第#26項 0c 表示一個 【名+類型】,00 21(33) 00 22(34)引用了常量池中 #33 #34 兩項
        0000420 07 00 20 0c 00 21 00 22 01 00 1b 63 6e 2f 69 74
      • 第#27項 01 表示一個 utf8 串,00 1b(27) 表示長度,是【cn/itcast/jvm/t5/HelloWorld】
        0000420 07 00 20 0c 00 21 00 22 01 00 1b 63 6e 2f 69 74
        0000440 63 61 73 74 2f 6a 76 6d 2f 74 35 2f 48 65 6c 6c
        0000460 6f 57 6f 72 6c 64 01 00 10 6a 61 76 61 2f 6c 61
      • 第#28項 01 表示一個 utf8 串,00 10(16) 表示長度,是【java/lang/Object】
        0000460 6f 57 6f 72 6c 64 01 00 10 6a 61 76 61 2f 6c 61
        0000500 6e 67 2f 4f 62 6a 65 63 74 01 00 10 6a 61 76 61
      • 第#29項 01 表示一個 utf8 串,00 10(16) 表示長度,是【java/lang/System】
        0000500 6e 67 2f 4f 62 6a 65 63 74 01 00 10 6a 61 76 61
        0000520 2f 6c 61 6e 67 2f 53 79 73 74 65 6d 01 00 03 6f
      • 第#30項 01 表示一個 utf8 串,00 03 表示長度,是【out】
        0000520 2f 6c 61 6e 67 2f 53 79 73 74 65 6d 01 00 03 6f
        0000540 75 74 01 00 15 4c 6a 61 76 61 2f 69 6f 2f 50 72
      • 第#31項 01 表示一個 utf8 串,00 15(21) 表示長度,是【Ljava/io/PrintStream;】
        0000540 75 74 01 00 15 4c 6a 61 76 61 2f 69 6f 2f 50 72
        0000560 69 6e 74 53 74 72 65 61 6d 3b 01 00 13 6a 61 76
      • 第#32項 01 表示一個 utf8 串,00 13(19) 表示長度,是【java/io/PrintStream】
        0000560 69 6e 74 53 74 72 65 61 6d 3b 01 00 13 6a 61 76
        0000600 61 2f 69 6f 2f 50 72 69 6e 74 53 74 72 65 61 6d
      • 第#33項 01 表示一個 utf8 串,00 07 表示長度,是【println】
        0000620 01 00 07 70 72 69 6e 74 6c 6e 01 00 15 28 4c 6a
      • 第#34項 01 表示一個 utf8 串,00 15(21) 表示長度,是【(Ljava/lang/String;)V】
        0000620 01 00 07 70 72 69 6e 74 6c 6e 01 00 15 28 4c 6a
        0000640 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b
        0000660 29 56 00 21 00 05 00 06 00 00 00 00 00 02 00 01

    3.1.4 訪問標(biāo)識與繼承信息

    • 00 21(0x0020+0x0001)表示該 class 是一個類,公共的
      0000660 29 56 00 21 00 05 00 06 00 00 00 00 00 02 00 01
    • 00 05 表示根據(jù)常量池中 #5 找到本類全限定名
      0000660 29 56 00 21 00 05 00 06 00 00 00 00 00 02 00 01
    • 00 06 表示根據(jù)常量池中 #6 找到父類全限定名
      0000660 29 56 00 21 00 05 00 06 00 00 00 00 00 02 00 01
    • 00 00表示接口的數(shù)量,本類為 0
      0000660 29 56 00 21 00 05 00 06 00 00 00 00 00 02 00 01

    3.1.5 Field 信息

    • 00 00表示成員變量數(shù)量,本類為 0
      0000660 29 56 00 21 00 05 00 06 00 00 00 00 00 02 00 01

    3.1.6 Method 信息

    • 00 02表示方法數(shù)量,本類為 2
      0000660 29 56 00 21 00 05 00 06 00 00 00 00 00 02 00 01
    • 一個方法由訪問修飾符,名稱,參數(shù)描述,方法屬性數(shù)量,方法屬性組成
    • 例1:method-init
      • 紅色代表訪問修飾符(本類中是 public)
      • 藍(lán)色代表引用了常量池 #07 項作為方法名稱
      • 綠色代表引用了常量池 #08 項作為方法參數(shù)描述
      • 黃色代表方法屬性數(shù)量,本方法是 1
      • 紅色代表方法屬性
        • 00 09 表示引用了常量池 #09 項,發(fā)現(xiàn)是【Code】屬性
        • 00 00 00 2f 表示此屬性的長度是 47
        • 00 01 表示【操作數(shù)棧】最大深度
        • 00 01 表示【局部變量表】最大槽(slot)數(shù)(長度)
        • 00 00 00 05 表示字節(jié)碼長度,本例是 5
        • 2a b7 00 01 b1 是字節(jié)碼指令
        • 00 00 00 02 表示方法細(xì)節(jié)屬性數(shù)量,本例是 2
        • 00 0a 表示引用了常量池 #10 項,發(fā)現(xiàn)是【LineNumberTable】屬性
          • 00 00 00 06 表示此屬性的總長度,本例是 6
          • 00 01 表示【LineNumberTable】長度
          • 00 00 表示【字節(jié)碼】行號 00 04 表示【java 源碼】行號
        • 00 0b 表示引用了常量池 #11 項,發(fā)現(xiàn)是【LocalVariableTable】屬性
          • 00 00 00 0c 表示此屬性的總長度,本例是 12
          • 00 01 表示【LocalVariableTable】長度
          • 00 00 表示局部變量生命周期開始,相對于字節(jié)碼的偏移量
          • 00 05 表示局部變量覆蓋的范圍長度
          • 00 0c 表示局部變量名稱,本例引用了常量池 #12 項,是【this】
          • 00 0d 表示局部變量的類型,本例引用了常量池 #13 項,是【Lcn/itcast/jvm/t5/HelloWorld;】
          • 00 00 表示局部變量占有的槽位(slot)編號,本例是 0
    • 例2:method-main
      • 紅色代表訪問修飾符(本類中是 public static)

      • 藍(lán)色代表引用了常量池 #14 項作為方法名稱

      • 綠色代表引用了常量池 #15 項作為方法參數(shù)描述

      • 黃色代表方法屬性數(shù)量,本方法是 2

      • 紅色代表方法屬性(屬性1)

        • 00 09 表示引用了常量池 #09 項,發(fā)現(xiàn)是【Code】屬性
        • 00 00 00 37 表示此屬性的長度是 55
        • 00 02 表示【操作數(shù)棧】最大深度
        • 00 01 表示【局部變量表】最大槽(slot)數(shù)
        • 00 00 00 05 表示字節(jié)碼長度,本例是 9
        • b2 00 02 12 03 b6 00 04 b1 是字節(jié)碼指令
        • 00 00 00 02 表示方法細(xì)節(jié)屬性數(shù)量,本例是 2
        • 00 0a 表示引用了常量池 #10 項,發(fā)現(xiàn)是【LineNumberTable】屬性
          • 00 00 00 0a 表示此屬性的總長度,本例是 10
          • 00 02 表示【LineNumberTable】長度
          • 00 00 表示【字節(jié)碼】行號 00 06 表示【java 源碼】行號
          • 00 08 表示【字節(jié)碼】行號 00 07 表示【java 源碼】行號
        • 00 0b 表示引用了常量池 #11 項,發(fā)現(xiàn)是【LocalVariableTable】屬性
          • 00 00 00 0c 表示此屬性的總長度,本例是 12
          • 00 01 表示【LocalVariableTable】長度
          • 00 00 表示局部變量生命周期開始,相對于字節(jié)碼的偏移量
          • 00 09 表示局部變量覆蓋的范圍長度
          • 00 10 表示局部變量名稱,本例引用了常量池 #16 項,是【args】
          • 00 11 表示局部變量的類型,本例引用了常量池 #17 項,是【[Ljava/lang/String;】
          • 00 00 表示局部變量占有的槽位(slot)編號,本例是 0

        • 紅色代表方法屬性(屬性2)
          • 00 12 表示引用了常量池 #18 項,發(fā)現(xiàn)是【MethodParameters】屬性
            • 00 00 00 05 表示此屬性的總長度,本例是 5
            • 01 參數(shù)數(shù)量
            • 00 10 表示引用了常量池 #16 項,是【args】
            • 00 00 訪問修飾符

    3.1.7 附加屬性

    0001100 00 12 00 00 00 05 01 00 10 00 00 00 01 00 13 00
    0001120 0 00 02 00 14

    • 00 01 表示附加屬性數(shù)量
    • 00 13 表示引用了常量池 #19 項,即【SourceFile】
    • 00 00 00 02 表示此屬性的長度
    • 00 14 表示引用了常量池 #20 項,即【HelloWorld.java】

    3.2 字節(jié)碼指令

    3.2.1 入門

    參考鏈接:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5

    • 接著上一節(jié),研究一下兩組字節(jié)碼指令,一個是public cn.itcast.jvm.t5.HelloWorld(); 構(gòu)造方法的字節(jié)碼指令:

      2a b7 00 01 b1
    • 2a => aload_0 加載 slot 0 的局部變量,即 this,做為下面的 invokespecial 構(gòu)造方法調(diào)用的參數(shù)
    • b7 => invokespecial 預(yù)備調(diào)用構(gòu)造方法,哪個方法呢?
    • 00 01 引用常量池中 #1 項,即【 Method java/lang/Object.“”😦)V 】
    • b1 表示返回
    • 另一個是 public static void main(java.lang.String[]); 主方法的字節(jié)碼指令:

      b2 00 02 12 03 b6 00 04 b1
    • b2 => getstatic 用來加載靜態(tài)變量,哪個靜態(tài)變量呢?
    • 00 02 引用常量池中 #2 項,即【Field java/lang/System.out:Ljava/io/PrintStream;】
    • 12 => ldc 加載參數(shù),哪個參數(shù)呢?
    • 03 引用常量池中 #3 項,即 【String hello world】
    • b6 => invokevirtual 預(yù)備調(diào)用成員方法,哪個方法呢?
    • 00 04 引用常量池中 #4 項,即【Method java/io/PrintStream.println:(Ljava/lang/String;)V】
    • b1 表示返回

    3.2.2 javap工具獲取反編譯字節(jié)碼

    • 自己分析類文件結(jié)構(gòu)太麻煩了,Oracle 提供了 javap 工具來反編譯 class 文件,使用方法見:1.5.4 運(yùn)行時常量池
    • 方法一:
      編譯: javac -encoding utf-8 Demo1_22.java,生成字節(jié)碼文件
      反編譯字節(jié)碼文件:javap -v Demo1_22.class,在控制臺打印出字節(jié)碼文件的內(nèi)容
    • 方法二:
      直接在IDEA中進(jìn)行配置,可直接右鍵查看反編譯好的字節(jié)碼文件,方法見文章:Intellij IDEA 查看字節(jié)碼

    3.2.3 圖解方法執(zhí)行流程

    ① 原始 java 代碼

    package cn.itcast.jvm.t3.bytecode; /** * 演示 字節(jié)碼指令 和 操作數(shù)棧、常量池的關(guān)系*/ public class Demo3_1 { public static void main(String[] args) { int a = 10; int b = Short.MAX_VALUE + 1; int c = a + b; System.out.println(c); } }

    ② 編譯后的字節(jié)碼文件

    "D:\Program Files (x86)\Java\jdk11.0.15\bin\javap.exe" -v Demo3_1.class Classfile /E:/00_learning/JAVA/資料-解密JVM/代碼/jvm/out/production/jvm/cn/itcast/jvm/t3/bytecode/Demo3_1.classLast modified 2022年7月17日; size 635 bytesMD5 checksum 1a6413a652bcc5023f130b392deb76a1Compiled from "Demo3_1.java" public class cn.itcast.jvm.t3.bytecode.Demo3_1minor version: 0major version: 52flags: (0x0021) ACC_PUBLIC, ACC_SUPERthis_class: #6 // cn/itcast/jvm/t3/bytecode/Demo3_1super_class: #7 // java/lang/Objectinterfaces: 0, fields: 0, methods: 2, attributes: 1 Constant pool:#1 = Methodref #7.#25 // java/lang/Object."<init>":()V#2 = Class #26 // java/lang/Short#3 = Integer 32768#4 = Fieldref #27.#28 // java/lang/System.out:Ljava/io/PrintStream;#5 = Methodref #29.#30 // java/io/PrintStream.println:(I)V#6 = Class #31 // cn/itcast/jvm/t3/bytecode/Demo3_1#7 = Class #32 // java/lang/Object#8 = Utf8 <init>#9 = Utf8 ()V#10 = Utf8 Code#11 = Utf8 LineNumberTable#12 = Utf8 LocalVariableTable#13 = Utf8 this#14 = Utf8 Lcn/itcast/jvm/t3/bytecode/Demo3_1;#15 = Utf8 main#16 = Utf8 ([Ljava/lang/String;)V#17 = Utf8 args#18 = Utf8 [Ljava/lang/String;#19 = Utf8 a#20 = Utf8 I#21 = Utf8 b#22 = Utf8 c#23 = Utf8 SourceFile#24 = Utf8 Demo3_1.java#25 = NameAndType #8:#9 // "<init>":()V#26 = Utf8 java/lang/Short#27 = Class #33 // java/lang/System#28 = NameAndType #34:#35 // out:Ljava/io/PrintStream;#29 = Class #36 // java/io/PrintStream#30 = NameAndType #37:#38 // println:(I)V#31 = Utf8 cn/itcast/jvm/t3/bytecode/Demo3_1#32 = Utf8 java/lang/Object#33 = Utf8 java/lang/System#34 = Utf8 out#35 = Utf8 Ljava/io/PrintStream;#36 = Utf8 java/io/PrintStream#37 = Utf8 println#38 = Utf8 (I)V {public cn.itcast.jvm.t3.bytecode.Demo3_1();descriptor: ()Vflags: (0x0001) ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: invokespecial #1 // Method java/lang/Object."<init>":()V4: returnLineNumberTable:line 6: 0LocalVariableTable:Start Length Slot Name Signature0 5 0 this Lcn/itcast/jvm/t3/bytecode/Demo3_1;public static void main(java.lang.String[]);descriptor: ([Ljava/lang/String;)Vflags: (0x0009) ACC_PUBLIC, ACC_STATICCode:stack=2, locals=4, args_size=10: bipush 102: istore_13: ldc #3 // int 327685: istore_26: iload_17: iload_28: iadd9: istore_310: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;13: iload_314: invokevirtual #5 // Method java/io/PrintStream.println:(I)V17: returnLineNumberTable:line 8: 0line 9: 3line 10: 6line 11: 10line 12: 17LocalVariableTable:Start Length Slot Name Signature0 18 0 args [Ljava/lang/String;3 15 1 a I6 12 2 b I10 8 3 c I } SourceFile: "Demo3_1.java"

    ③ 常量池載入運(yùn)行時常量池

    ④ 方法字節(jié)碼載入方法區(qū)

    ⑤ main 線程開始運(yùn)行,分配棧幀內(nèi)存

    (stack=2,locals=4)

    ⑥ 執(zhí)行引擎開始執(zhí)行字節(jié)碼

    • bipush 10
      • 將一個 byte 壓入操作數(shù)棧(其長度會補(bǔ)齊 4 個字節(jié)),類似的指令還有
      • sipush 將一個 short 壓入操作數(shù)棧(其長度會補(bǔ)齊 4 個字節(jié))
      • ldc 將一個 int 壓入操作數(shù)棧
      • ldc2_w 將一個 long 壓入操作數(shù)棧(分兩次壓入,因為 long 是 8 個字節(jié))
      • 這里小的數(shù)字都是和字節(jié)碼指令存在一起,超過 short 范圍的數(shù)字存入了常量池
    • istore_1
      • 將操作數(shù)棧頂數(shù)據(jù)彈出,存入局部變量表的 slot 1
    • ldc #3
      • 從常量池加載 #3 數(shù)據(jù)到操作數(shù)棧
      • 注意 Short.MAX_VALUE 是 32767,所以 32768 = Short.MAX_VALUE + 1 實(shí)際是在編譯期間計算好的
    • istore_2
    • iload_1
    • iload_2
    • iadd
    • istore_3
    • getstatic #4

    • iload_3
    • invokevirtual #5
      • 找到常量池 #5 項
      • 定位到方法區(qū) java/io/PrintStream.println:(I)V 方法
      • 生成新的棧幀(分配 locals、stack等)
      • 傳遞參數(shù),執(zhí)行新棧幀中的字節(jié)碼
      • 執(zhí)行完畢,彈出棧幀
      • 清除 main 操作數(shù)棧內(nèi)容
    • return
      • 完成 main 方法調(diào)用,彈出 main 棧幀
      • 程序結(jié)束

    3.2.4 練習(xí) - 分析 i++

    • 目的:從字節(jié)碼角度分析 a++ 相關(guān)題目
    • 源碼:package cn.itcast.jvm.t3.bytecode;/*** 從字節(jié)碼角度分析 a++ 相關(guān)題目*/ public class Demo3_2 {public static void main(String[] args) {int a = 10;int b = a++ + ++a + a--;System.out.println(a);System.out.println(b);} }
    • 字節(jié)碼:"D:\Program Files (x86)\Java\jdk11.0.15\bin\javap.exe" -v Demo3_2.class Classfile /E:/00_learning/JAVA/資料-解密JVM/代碼/jvm/out/production/jvm/cn/itcast/jvm/t3/bytecode/Demo3_2.classLast modified 2022年7月17日; size 610 bytesMD5 checksum 5f6a35e5b9bb88d08249958a8d2ab043Compiled from "Demo3_2.java" public class cn.itcast.jvm.t3.bytecode.Demo3_2minor version: 0major version: 52flags: (0x0021) ACC_PUBLIC, ACC_SUPERthis_class: #4 // cn/itcast/jvm/t3/bytecode/Demo3_2super_class: #5 // java/lang/Objectinterfaces: 0, fields: 0, methods: 2, attributes: 1 Constant pool:#1 = Methodref #5.#22 // java/lang/Object."<init>":()V#2 = Fieldref #23.#24 // java/lang/System.out:Ljava/io/PrintStream;#3 = Methodref #25.#26 // java/io/PrintStream.println:(I)V#4 = Class #27 // cn/itcast/jvm/t3/bytecode/Demo3_2#5 = Class #28 // java/lang/Object#6 = Utf8 <init>#7 = Utf8 ()V#8 = Utf8 Code#9 = Utf8 LineNumberTable#10 = Utf8 LocalVariableTable#11 = Utf8 this#12 = Utf8 Lcn/itcast/jvm/t3/bytecode/Demo3_2;#13 = Utf8 main#14 = Utf8 ([Ljava/lang/String;)V#15 = Utf8 args#16 = Utf8 [Ljava/lang/String;#17 = Utf8 a#18 = Utf8 I#19 = Utf8 b#20 = Utf8 SourceFile#21 = Utf8 Demo3_2.java#22 = NameAndType #6:#7 // "<init>":()V#23 = Class #29 // java/lang/System#24 = NameAndType #30:#31 // out:Ljava/io/PrintStream;#25 = Class #32 // java/io/PrintStream#26 = NameAndType #33:#34 // println:(I)V#27 = Utf8 cn/itcast/jvm/t3/bytecode/Demo3_2#28 = Utf8 java/lang/Object#29 = Utf8 java/lang/System#30 = Utf8 out#31 = Utf8 Ljava/io/PrintStream;#32 = Utf8 java/io/PrintStream#33 = Utf8 println#34 = Utf8 (I)V {public cn.itcast.jvm.t3.bytecode.Demo3_2();descriptor: ()Vflags: (0x0001) ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: invokespecial #1 // Method java/lang/Object."<init>":()V4: returnLineNumberTable:line 6: 0LocalVariableTable:Start Length Slot Name Signature0 5 0 this Lcn/itcast/jvm/t3/bytecode/Demo3_2;public static void main(java.lang.String[]);descriptor: ([Ljava/lang/String;)Vflags: (0x0009) ACC_PUBLIC, ACC_STATICCode:stack=2, locals=3, args_size=10: bipush 102: istore_13: iload_14: iinc 1, 17: iinc 1, 110: iload_111: iadd12: iload_113: iinc 1, -116: iadd17: istore_218: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;21: iload_122: invokevirtual #3 // Method java/io/PrintStream.println:(I)V25: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;28: iload_229: invokevirtual #3 // Method java/io/PrintStream.println:(I)V32: returnLineNumberTable:line 8: 0line 9: 3line 10: 18line 11: 25line 12: 32LocalVariableTable:Start Length Slot Name Signature0 33 0 args [Ljava/lang/String;3 30 1 a I18 15 2 b I } SourceFile: "Demo3_2.java"
    • 分析:
      • 注意 iinc 指令是直接在局部變量 slot 上進(jìn)行運(yùn)算
      • a++ 和 ++a 的區(qū)別是先執(zhí)行 iload 還是 先執(zhí)行 iinc





    3.2.5 條件判斷指令

    參考鏈接:https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.lcmp

    • 幾點(diǎn)說明:

      • byte,short,char 都會按 int 比較,因為操作數(shù)棧都是 4 字節(jié)
      • goto 用來進(jìn)行跳轉(zhuǎn)到指定行號的字節(jié)碼
    • 源碼:

      public class Demo3_3 {public static void main(String[] args) {int a = 0;if(a == 0) {a = 10;} else {a = 20;}} }
    • 字節(jié)碼:

      0: iconst_01: istore_12: iload_13: ifne 126: bipush 108: istore_19: goto 15 12: bipush 20 14: istore_1 15: return

    3.2.6 循環(huán)控制指令

    循環(huán)控制還是前面介紹的那些指令。

    • while 循環(huán)源碼:public class Demo3_4 {public static void main(String[] args) {int a = 0;while (a < 10) {a++;}} }
    • 字節(jié)碼: 0: iconst_01: istore_12: iload_13: bipush 105: if_icmpge 148: iinc 1, 1 11: goto 2 14: return
    • do while 循環(huán)源碼:public class Demo3_5 {public static void main(String[] args) {int a = 0;do {a++;} while (a < 10);} }
    • 字節(jié)碼: 0: iconst_01: istore_12: iinc 1, 15: iload_16: bipush 108: if_icmplt 2 11: return
    • for 循環(huán)源碼:public class Demo3_6 {public static void main(String[] args) {for (int i = 0; i < 10; i++) {}} }
    • 字節(jié)碼: 0: iconst_01: istore_12: iload_13: bipush 105: if_icmpge 148: iinc 1, 1 11: goto 2 14: return

    3.2.7 練習(xí) - 判斷結(jié)果

    • 請從字節(jié)碼角度分析,下列代碼運(yùn)行的結(jié)果:public class Demo3_6_1 {public static void main(String[] args) {int i = 0;int x = 0;while (i < 10) {x = x++;i++;}System.out.println(x);// 結(jié)果是 0} }

    x = x++;:自增運(yùn)算 iinc 指令是直接在局部變量 slot 上進(jìn)行運(yùn)算,對于此語句,x為局部變量表上的一個槽點(diǎn),初值為0;x++對應(yīng)兩條指令iload_x(將局部變量表的0讀入操作數(shù)棧)與iinc_x(局部變量槽內(nèi)的0+1變?yōu)?);然后進(jìn)行賦值操作,用操作數(shù)棧中的0覆蓋局部變量槽內(nèi)x的值,即x的值又變回0。因此不論此語句循環(huán)幾次x的值都為0。

    3.2.8 構(gòu)造方法

    < cinit >()V

    public class Demo3_8_1 {static int i = 10;static {i = 20;}static {i = 30;} }

    編譯器會按從上至下的順序,收集所有 static 靜態(tài)代碼塊和靜態(tài)成員賦值的代碼,合并為一個特殊的方法 <cinit>()V :

    0: bipush 202: putstatic #3 // Field i:I5: bipush 307: putstatic #3 // Field i:I 10: bipush 10 12: putstatic #3 // Field i:I 15: return

    <cinit>()V 方法會在類加載的初始化階段被調(diào)用。

    < init >()V

    public class Demo3_8_2 {private String a = "s1";{b = 20;}private int b = 10;{a = "s2";}public Demo3_8_2(String a, int b) {this.a = a;this.b = b;}public static void main(String[] args) {Demo3_8_2 d = new Demo3_8_2("s3", 30);System.out.println(d.a);System.out.println(d.b);} }

    編譯器會按從上至下的順序,收集所有 {} 代碼塊和成員變量賦值的代碼,形成新的構(gòu)造方法,但原始構(gòu)造方法內(nèi)的代碼總是在最后

    3.2.9 方法調(diào)用

    public class Demo3_9 {public Demo3_9() { }private void test1() { }private final void test2() { }public void test3() { }public static void test4() { }@Overridepublic String toString() {return super.toString();}public static void main(String[] args) {Demo3_9 d = new Demo3_9();d.test1();d.test2();d.test3();d.test4();Demo3_9.test4();d.toString();} }

    不同的方法調(diào)用對應(yīng)的字節(jié)碼指令:

    0: new #3 // class cn/itcast/jvm/t3/bytecode/Demo3_93: dup4: invokespecial #4 // Method "<init>":()V7: astore_18: aload_19: invokespecial #5 // Method test1:()V 12: aload_1 13: invokespecial #6 // Method test2:()V 16: aload_1 17: invokevirtual #7 // Method test3:()V 20: aload_1 21: pop 22: invokestatic #8 // Method test4:()V 25: invokestatic #8 // Method test4:()V 28: aload_1 29: invokevirtual #9 // Method toString:()Ljava/lang/String; 32: pop 33: return
    • new 是創(chuàng)建【對象】,給對象分配堆內(nèi)存,執(zhí)行成功會將【對象引用】壓入操作數(shù)棧
    • dup 是復(fù)制操作數(shù)棧棧頂?shù)膬?nèi)容,本例即為【對象引用】,為什么需要兩份引用呢,一個是要配合 invokespecial 調(diào)用該對象的構(gòu)造方法 "<init>":()V (會消耗掉棧頂一個引用),另一個要配合 astore_1 賦值給局部變量
    • 最終方法(final),私有方法(private),構(gòu)造方法都是由 invokespecial 指令來調(diào)用,屬于靜態(tài)綁定
    • 普通成員方法是由 invokevirtual 調(diào)用,屬于動態(tài)綁定,即支持多態(tài)
    • 成員方法與靜態(tài)方法調(diào)用的另一個區(qū)別是,執(zhí)行方法前是否需要【對象引用】
    • 比較有意思的是 d.test4(); 是通過【對象引用】調(diào)用一個靜態(tài)方法,可以看到在調(diào)用invokestatic 之前執(zhí)行了 pop 指令,把【對象引用】從操作數(shù)棧彈掉了,因此在調(diào)用靜態(tài)方法時盡量使用類調(diào)用而不是對象,否則會產(chǎn)生冗余的虛擬機(jī)指令
    • 還有一個執(zhí)行 invokespecial 的情況是通過 super 調(diào)用父類方法

    3.2.10 多態(tài)的原理

    package cn.itcast.jvm.t3.bytecode;import java.io.IOException;/*** 演示多態(tài)原理,注意加上下面的 JVM 參數(shù),禁用指針壓縮* -XX:-UseCompressedOops -XX:-UseCompressedClassPointers*/ public class Demo3_10 {public static void test(Animal animal) {animal.eat();System.out.println(animal.toString());}public static void main(String[] args) throws IOException {test(new Cat());test(new Dog());System.in.read();} }abstract class Animal {public abstract void eat();@Overridepublic String toString() {return "我是" + this.getClass().getSimpleName();} }class Dog extends Animal {@Overridepublic void eat() {System.out.println("啃骨頭");} }class Cat extends Animal {@Overridepublic void eat() {System.out.println("吃魚");} }
  • 運(yùn)行代碼
    停在 System.in.read() 方法上,這時運(yùn)行 jps 獲取進(jìn)程 id

  • 運(yùn)行 HSDB 工具
    進(jìn)入 JDK 安裝目錄,執(zhí)行java -cp ./lib/sa-jdi.jar sun.jvm.hotspot.HSDB
    進(jìn)入圖形界面 attach 進(jìn)程 id

  • 查找某個對象
    打開 Tools -> Find Object By Query
    輸入 select d from cn.itcast.jvm.t3.bytecode.Dog d 點(diǎn)擊 Execute 執(zhí)行

  • 查看對象內(nèi)存結(jié)構(gòu)
    點(diǎn)擊超鏈接可以看到對象的內(nèi)存結(jié)構(gòu),此對象沒有任何屬性,因此只有對象頭的 16 字節(jié),前 8 字節(jié)是MarkWord,后 8 字節(jié)就是對象的 Class 指針
    但目前看不到它的實(shí)際地址

  • 查看對象 Class 的內(nèi)存地址
    可以通過 Windows -> Console 進(jìn)入命令行模式,執(zhí)行mem 0x00000001299b4978 2
    mem 有兩個參數(shù),參數(shù) 1 是對象地址,參數(shù) 2 是查看 2 行(即 16 字節(jié))
    結(jié)果中第二行 0x000000001b7d4028 即為 Class 的內(nèi)存地址

  • 查看類的 vtable
    方法1:Alt+R 進(jìn)入 Inspector 工具,輸入剛才的 Class 內(nèi)存地址,看到如下界面:

    方法2:或者 Tools -> Class Browser 輸入 Dog 查找,可以得到相同的結(jié)果

    無論通過哪種方法,都可以找到 Dog Class 的 vtable 長度為 6,意思就是 Dog 類有 6 個虛方法(多態(tài)相關(guān)的,final,static 不會列入)
    那么這 6 個方法都是誰呢?從 Class 的起始地址開始算,偏移 0x1b8 就是 vtable 的起始地址,進(jìn)行計算得到:

    通過 Windows -> Console 進(jìn)入命令行模式,執(zhí)行:mem 0x000000001b7d41e0 6,就得到了 6 個虛方法的入口地址。

  • 驗證方法地址
    通過 Tools -> Class Browser 查看每個類的方法定義,比較可知:

    對號入座,發(fā)現(xiàn):

    • eat() 方法是 Dog 類自己的
    • toString() 方法是繼承 String 類的
    • finalize() ,equals(),hashCode(),clone() 都是繼承 Object 類的
    • 小結(jié)
      當(dāng)執(zhí)行 invokevirtual 指令時:
    • 先通過棧幀中的對象引用找到對象
    • 分析對象頭,找到對象的實(shí)際 Class
    • Class 結(jié)構(gòu)中有 vtable,它在類加載的鏈接階段就已經(jīng)根據(jù)方法的重寫規(guī)則生成好了
    • 查表得到方法的具體地址
    • 執(zhí)行方法的字節(jié)碼

    3.2.11 異常處理

    try-catch

    public class Demo3_11_1 {public static void main(String[] args) {int i = 0;try {i = 10;} catch (Exception e) {i = 20;}} }

    字節(jié)碼:

    Code:stack=1, locals=3, args_size=10: iconst_01: istore_12: bipush 104: istore_15: goto 128: astore_29: bipush 2011: istore_112: returnException table:from to target type2 5 8 Class java/lang/ExceptionLineNumberTable:line 6: 0line 8: 2line 11: 5line 9: 8line 10: 9line 12: 12LocalVariableTable:Start Length Slot Name Signature9 3 2 e Ljava/lang/Exception;0 13 0 args [Ljava/lang/String;2 11 1 i I
    • 可以看到多出來一個 Exception table 的結(jié)構(gòu),[from, to)前閉后開的檢測范圍,一旦這個范圍內(nèi)的字節(jié)碼執(zhí)行出現(xiàn)異常,則通過 type 匹配異常類型,如果一致,進(jìn)入 target 所指示行號
    • 8 行的字節(jié)碼指令 astore_2 是將異常對象引用存入局部變量表的 slot 2 位置,通過 LocalVariableTable 可知其存儲的為e變量

    多個 single-catch 塊的情況

    public class Demo3_11_2 {public static void main(String[] args) {int i = 0;try {i = 10;} catch (ArithmeticException e) {i = 30;} catch (NullPointerException e) {i = 40;} catch (Exception e) {i = 50;}} }

    字節(jié)碼:

    Code:stack=1, locals=3, args_size=10: iconst_01: istore_12: bipush 104: istore_15: goto 268: astore_29: bipush 3011: istore_112: goto 2615: astore_216: bipush 4018: istore_119: goto 2622: astore_223: bipush 5025: istore_126: returnException table:from to target type2 5 8 Class java/lang/ArithmeticException2 5 15 Class java/lang/NullPointerException2 5 22 Class java/lang/ExceptionLineNumberTable:...LocalVariableTable:Start Length Slot Name Signature9 3 2 e Ljava/lang/ArithmeticException;16 3 2 e Ljava/lang/NullPointerException;23 3 2 e Ljava/lang/Exception;0 27 0 args [Ljava/lang/String;2 25 1 i I
    • 因為異常出現(xiàn)時,只能進(jìn)入 Exception table 中一個分支,所以局部變量表 slot 2 位置被共用

    multi-catch 的情況

    public class Demo3_11_3 {public static void main(String[] args) {try {Method test = Demo3_11_3.class.getMethod("test");test.invoke(null);} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {e.printStackTrace();}}public static void test() {System.out.println("ok");} }

    字節(jié)碼:

    Code:stack=3, locals=2, args_size=10: ldc #2 // class cn/itcast/jvm/t3/bytecode/Demo3_11_32: ldc #3 // String test4: iconst_05: anewarray #4 // class java/lang/Class8: invokevirtual #5 // Method java/lang/Class.getMethod:(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;11: astore_112: aload_113: aconst_null14: iconst_015: anewarray #6 // class java/lang/Object18: invokevirtual #7 // Method java/lang/reflect/Method.invoke:(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;21: pop22: goto 3025: astore_126: aload_127: invokevirtual #11 // Method java/lang/ReflectiveOperationException.printStackTrace:()V30: returnException table:from to target type0 22 25 Class java/lang/NoSuchMethodException0 22 25 Class java/lang/IllegalAccessException0 22 25 Class java/lang/reflect/InvocationTargetExceptionLineNumberTable:...LocalVariableTable:Start Length Slot Name Signature12 10 1 test Ljava/lang/reflect/Method;26 4 1 e Ljava/lang/ReflectiveOperationException;0 31 0 args [Ljava/lang/String;

    finally

    public class Demo3_11_4 {public static void main(String[] args) {int i = 0;try {i = 10;} catch (Exception e) {i = 20;} finally {i = 30;}} }

    字節(jié)碼:

    • 可以看到 finally 中的代碼被復(fù)制了 3 份,分別放入 try 流程,catch 流程以及 catch 剩余的異常類型流程

    3.2.12 練習(xí) - finally 面試題

    finally 出現(xiàn)了 return

    public class Demo3_12_1 {public static void main(String[] args) {int result = test();System.out.println(result);//20}public static int test() {try {int i = 1/0;return 10;} finally {return 20;}} }

    字節(jié)碼:

    Code:stack=2, locals=3, args_size=00: iconst_11: iconst_02: idiv3: istore_04: bipush 106: istore_17: bipush 209: ireturn10: astore_211: bipush 2013: ireturnException table:from to target type0 7 10 anyLineNumberTable:...LocalVariableTable:Start Length Slot Name Signature4 6 0 i I
    • 由于 finally 中的 ireturn 被插入了所有可能的流程,因此返回結(jié)果肯定以 finally 的為準(zhǔn)
    • 至于字節(jié)碼中第 2 行,似乎沒啥用,且留個伏筆,看下個例子
    • 跟上例中的 finally 相比,發(fā)現(xiàn)沒有 athrow 了,這告訴我們:如果在 finally 中出現(xiàn)了 return,會吞掉異常。因此在寫代碼時應(yīng)當(dāng)盡量避免在finally中寫return

    finally(無return)對返回值影響

    public class Demo3_12_2 {public static void main(String[] args) {int result = test();System.out.println(result);//10}public static int test() {int i = 10;try {return i;} finally {i = 20;}} }

    字節(jié)碼:

    3.2.13 synchronized

    public class Demo3_13 {public static void main(String[] args) {Object lock = new Object();synchronized (lock) {System.out.println("ok");}} }

    字節(jié)碼:

    3.3 編譯期處理(語法糖)

    • 所謂的 語法糖 ,其實(shí)就是指 java 編譯器把 *.java 源碼編譯為 *.class 字節(jié)碼的過程中,自動生成和轉(zhuǎn)換的一些代碼,主要是為了減輕程序員的負(fù)擔(dān),算是 java 編譯器給的一個額外福利
    • 注意,以下代碼的分析,借助了 javap 工具,idea 的反編譯功能,idea 插件 jclasslib 等工具。另外,編譯器轉(zhuǎn)換的結(jié)果直接就是 class 字節(jié)碼,只是為了便于閱讀,給出了 幾乎等價 的 java 源碼方式,并不是編譯器還會轉(zhuǎn)換出中間的 java 源碼,切記。

    3.3.1 默認(rèn)構(gòu)造器


    編譯成class后的代碼:

    3.3.2 自動拆裝箱

    這個特性是 JDK 5 開始加入的, 代碼片段1:

    這段代碼在 JDK 5 之前是無法編譯通過的,必須改寫為 代碼片段2:

    顯然之前版本的代碼太麻煩了,需要在基本類型和包裝類型之間來回轉(zhuǎn)換(尤其是集合類中操作的都是包裝類型),因此這些轉(zhuǎn)換的事情在 JDK 5 以后都由編譯器在編譯階段完成。即 代碼片段1 都會在編譯階段被轉(zhuǎn)換為 代碼片段2。

    3.3.3 泛型集合取值

    泛型也是在 JDK 5 開始加入的特性,但 java 在編譯泛型代碼后會執(zhí)行 泛型擦除 的動作,即泛型信息在編譯為字節(jié)碼之后就丟失了,實(shí)際的類型都當(dāng)做了 Object 類型來處理:

    所以在取值時,編譯器真正生成的字節(jié)碼中,還要額外做一個類型轉(zhuǎn)換的操作:

    如果前面的 x 變量類型修改為 int 基本類型那么最終生成的字節(jié)碼是:

    字節(jié)碼:


    擦除的是字節(jié)碼上的泛型信息,可以看到 LocalVariableTypeTable 仍然保留了方法參數(shù)泛型以及方法返回值泛型的信息:

    字節(jié)碼:

    public java.util.Set<java.lang.Integer> test(java.util.List<java.lang.String>, java.util.Map<java.lang.Integer, java.lang.Object>);descriptor: (Ljava/util/List;Ljava/util/Map;)Ljava/util/Set;flags: (0x0001) ACC_PUBLICCode:stack=1, locals=3, args_size=30: aconst_null1: areturnLineNumberTable:line 34: 0LocalVariableTable:Start Length Slot Name Signature0 2 0 this Lcn/itcast/jvm/t3/candy/Candy3;0 2 1 list Ljava/util/List;0 2 2 map Ljava/util/Map;LocalVariableTypeTable:Start Length Slot Name Signature0 2 1 list Ljava/util/List<Ljava/lang/String;>;0 2 2 map Ljava/util/Map<Ljava/lang/Integer;Ljava/lang/Object;>;Signature: #73 // (Ljava/util/List<Ljava/lang/String;>;Ljava/util/Map<Ljava/lang/Integer;Ljava/lang/Object;>;)Ljava/util/Set<Ljava/lang/Integer;>;

    使用反射,仍然能夠獲得這些信息:

    Method test = Candy3.class.getMethod("test", List.class, Map.class); Type[] types = test.getGenericParameterTypes(); for (Type type : types) {if (type instanceof ParameterizedType) {ParameterizedType parameterizedType = (ParameterizedType) type;System.out.println("原始類型 - " + parameterizedType.getRawType());Type[] arguments = parameterizedType.getActualTypeArguments();for (int i = 0; i < arguments.length; i++) {System.out.printf("泛型參數(shù)[%d] - %s\n", i, arguments[i]);}} }

    輸出:

    3.3.4 可變參數(shù)

    可變參數(shù)也是 JDK 5 開始加入的新特性:
    例如:

    可變參數(shù) String... args 其實(shí)是一個 String[] args ,從代碼中的賦值語句中就可以看出來。
    同樣 java 編譯器會在編譯期間將上述代碼變換為:

    • 注意
      如果調(diào)用了 foo() 則等價代碼為 foo(new String[]{}) ,創(chuàng)建了一個空的數(shù)組,而不會傳遞null 進(jìn)去

    3.3.5 foreach 循環(huán)

    仍是 JDK 5 開始引入的語法糖,數(shù)組的循環(huán):

    會被編譯器轉(zhuǎn)換為:

    而集合的循環(huán):

    實(shí)際被編譯器轉(zhuǎn)換為對迭代器的調(diào)用:

    • 注意
      for each 循環(huán)寫法,能夠配合數(shù)組,以及 所有實(shí)現(xiàn)了 Iterable 接口的集合類 一起使用,其中 Iterable 用來獲取集合的迭代器( Iterator )

    3.3.7 switch 枚舉

    switch 枚舉的例子,原始代碼:

    轉(zhuǎn)換后代碼:

    3.3.8 枚舉類

    JDK 7 新增了枚舉類,以前面的性別枚舉為例:

    轉(zhuǎn)換后代碼:

    3.3.9 try-with-resources

    JDK 7 開始新增了對需要關(guān)閉的資源處理的特殊語法 try-with-resources:

    其中資源對象需要實(shí)現(xiàn) AutoCloseable 接口,例如 InputStream 、 OutputStream 、 Connection 、 Statement 、 ResultSet 等接口都實(shí)現(xiàn)了 AutoCloseable,使用 try-with- resources 可以不用寫 finally 語句塊,編譯器會幫助生成關(guān)閉資源代碼,例如:

    會被轉(zhuǎn)換為:


    為什么要設(shè)計一個 addSuppressed(Throwable e) (添加被壓制異常)的方法呢?是為了防止異常信息的丟失(想想 try-with-resources 生成的 fianlly 中如果拋出了異常):

    輸出:

    3.3.10 方法重寫時的橋接方法

    我們都知道,方法重寫時對返回值分兩種情況:

    • 父子類的返回值完全一致
    • 子類返回值可以是父類返回值的子類(比較繞口,見下面的例子):

      對于子類,java 編譯器會做如下處理:

      其中橋接方法比較特殊,僅對 java 虛擬機(jī)可見,并且與原來的 public Integer m() 沒有命名沖突,可以用下面反射代碼來驗證:

      會輸出:

    3.3.11 匿名內(nèi)部類


    轉(zhuǎn)換后代碼:


    引用局部變量的匿名內(nèi)部類,源代碼:

    轉(zhuǎn)換后代碼:

    • 注意
      這同時解釋了為什么匿名內(nèi)部類引用局部變量時,局部變量必須是 final 的:因為在創(chuàng)建Candy11$1 對象時,將 x 的值賦值給了Candy11$1對象的 val$x 屬性,所以x 不應(yīng)該再發(fā)生變化了,如果變化,那么val$x屬性沒有機(jī)會再跟著一起變化。

    3.4 類加載階段

    3.4.1 加載

    • 將類的字節(jié)碼載入方法區(qū)中,內(nèi)部采用 C++ 的 instanceKlass 描述 java 類,它的重要 field 有:
      • _java_mirror 即 java 的類鏡像,例如對 String 來說,就是 String.class,作用是把 klass 暴露給 java 使用
      • _super 即父類
      • _fields 即成員變量
      • _methods 即方法
      • _constants 即常量池
      • _class_loader 即類加載器
      • _vtable 虛方法表:存儲方法的實(shí)際入口地址
      • _itable 接口方法表
    • 如果這個類還有父類沒有加載,先加載父類
    • 加載和鏈接可能是交替運(yùn)行的
    • 注意:
      • instanceKlass 這樣的【元數(shù)據(jù)】是存儲在方法區(qū)(1.8 后的元空間內(nèi)),但 _java_mirror是存儲在堆中
      • 可以通過前面介紹的 HSDB 工具查看

    3.4.2 鏈接

    驗證

    驗證類是否符合 JVM規(guī)范,安全性檢查
    - 用 UE 等支持二進(jìn)制的編輯器修改 HelloWorld.class 的魔數(shù),在控制臺運(yùn)行

    準(zhǔn)備

    static 變量分配空間,設(shè)置默認(rèn)值
    - static 變量在 JDK 7 之前存儲于 instanceKlass 末尾,從 JDK 7 開始,存儲于 _java_mirror 末尾
    - static 變量分配空間和賦值是兩個步驟,分配空間準(zhǔn)備階段完成,賦值初始化階段完成
    - 如果 static 變量final基本類型,以及字符串常量,那么編譯階段值就確定了,賦值在準(zhǔn)備階段完成
    - 如果 static 變量final 的,但屬于引用類型,那么賦值也會在初始化階段完成

    解析

    將常量池中的符號引用解析為直接引用(解析后才能知道類在內(nèi)存中的實(shí)際地址)

    • 注意:loadClass 方法僅會加載類,不會導(dǎo)致類的解析和初始化

    3.4.3 初始化

    • <cinit>()V 方法
      初始化即調(diào)用 <cinit>()V ,虛擬機(jī)會保證這個類的『構(gòu)造方法』的線程安全
    • 發(fā)生的時機(jī)
      概括得說,類初始化是【懶惰的
      • main 方法所在的類,總會被首先初始化
      • 首次訪問這個類的靜態(tài)變量或靜態(tài)方法時
      • 子類初始化,如果父類還沒初始化,會引發(fā)
      • 子類訪問父類的靜態(tài)變量,只會觸發(fā)父類的初始化
      • Class.forName
      • new 會導(dǎo)致初始化
    • 不會導(dǎo)致類初始化的情況
      • 訪問類的 static final 靜態(tài)常量基本類型字符串) 不會觸發(fā)初始化
      • 類對象.class 不會觸發(fā)初始化
      • 創(chuàng)建該類的數(shù)組不會觸發(fā)初始化
      • 類加載器的 loadClass 方法
      • Class.forName 的參數(shù) 2 為 false 時
    • 練習(xí)
    • 從字節(jié)碼分析,使用 a,b,c 這三個常量是否會導(dǎo)致 E 初始化

      public class Load4 {public static void main(String[] args) {System.out.println(E.a);//不會,static final 的基本類型System.out.println(E.b);//不會,static final 的字符串System.out.println(E.c);//會,static final 的對象} }class E {public static final int a = 10;public static final String b = "hello";public static final Integer c = 20; // Integer.valueOf(20)static {System.out.println("init E");} }
    • 典型應(yīng)用 - 完成懶惰初始化單例模式

      public class Load9 {public static void main(String[] args) { // Singleton.test();Singleton.getInstance();}}class Singleton {public static void test() {System.out.println("test");}private Singleton() {}private static class LazyHolder{private static final Singleton SINGLETON = new Singleton();static {System.out.println("lazy holder init");}}public static Singleton getInstance() {return LazyHolder.SINGLETON;} }

      以上的實(shí)現(xiàn)特點(diǎn)是:
      ① 懶惰實(shí)例化;
      ② 初始化時的線程安全是有保障的。

    3.5 類加載器

    • 類的加載:將class文件字節(jié)碼內(nèi)容加載到內(nèi)存中,并將這些靜態(tài)數(shù)據(jù)轉(zhuǎn)換成方法區(qū)的運(yùn)行時數(shù)據(jù)結(jié)構(gòu),然后生成一個代表這個類的java.lang.Class對象,作為方法區(qū)中類數(shù)據(jù)的訪問入口(即引用地址)。所有需要訪問和使用類數(shù)據(jù)只能通過這個Class對象。這個加載的過程需要類加載器參與。
    • Java虛擬機(jī)設(shè)計團(tuán)隊有意把類加載階段中的“通過一個類的全限定名來獲取描述該類的二進(jìn)制字節(jié)流”這個動作放到Java虛擬機(jī)外部去實(shí)現(xiàn),以便讓應(yīng)用程序自己決定如何去獲取所需的類。實(shí)現(xiàn)這個動作的代碼被稱為“類加載器”(ClassLoader)
    • 類加載器雖然只用于實(shí)現(xiàn)類的加載動作,但它在Java程序中起到的作用卻遠(yuǎn)超類加載階段
      對于任意一個類,都必須由加載它的類加載器和這個類本身一起共同確立其在Java虛擬機(jī)中的唯一性,每一個類加載器,都擁有一個獨(dú)立的類名稱空間。這句話可以表達(dá)得更通俗一些:比較兩個類是否“相等”,只有在這兩個類是由同一個類加載器加載的前提下才有意義,否則,即使這兩個類來源于同一個Class文件,被同一個Java虛擬機(jī)加載,只要加載它們的類加載器不同,那這兩個類就必定不相等
    • 類加載器分類(以 JDK 8 為例):

    3.5.1 啟動類加載器 Bootstrap ClassLoader

    引導(dǎo)類加載器是使用C++語言實(shí)現(xiàn)的,是JVM自身的一部分,主要負(fù)責(zé)將<JAVA_HOME>\lib路徑下的核心類庫或-Xbootclasspath參數(shù)指定的路徑下的jar包加載到內(nèi)存中。
    可以通過虛擬機(jī)參數(shù)將我們自己定義的類交由啟動類加載器進(jìn)行加載:

    package cn.itcast.jvm.t3.load;public class F {static {System.out.println("bootstrap F init");} } package cn.itcast.jvm.t3.load;public class Load5_1 {public static void main(String[] args) throws ClassNotFoundException {Class<?> aClass = Class.forName("cn.itcast.jvm.t3.load.F");//類的加載、鏈接和初始化System.out.println(aClass.getClassLoader()); //查看由那個類加載器加載的(原來:AppClassLoader ExtClassLoader)} }

    使用命令:java -Xbootclasspath/a:.

    E:\git\jvm\out\production\jvm>java -Xbootclasspath/a:. cn.itcast.jvm.t3.load.Load5 bootstrap F init null(說明使用的類加載器為啟動類加載器)
    • -Xbootclasspath 表示設(shè)置 bootclasspath
    • 其中 /a:. 表示將當(dāng)前目錄追加至 bootclasspath 之后
    • 可以用這個辦法替換核心類
      • java -Xbootclasspath:<new bootclasspath>
      • java -Xbootclasspath/a:<追加路徑>
      • java -Xbootclasspath/p:<追加路徑>

    3.5.2 擴(kuò)展類加載器 Extension ClassLoader

    這個類加載器是由sun公司實(shí)現(xiàn)的,位于HotSpot源碼目錄中的sun.misc.Launcher$ExtClassLoader位置。它主要負(fù)責(zé)加載<JAVA_HOME>\lib\ext目錄下或者由系統(tǒng)變量-Djava.ext.dira指定位路徑中的類庫。它可以直接被開發(fā)者使用。
    如果classpath和JAVA_HOME/jre/lib/ext下有同名類,加載時會使用拓展類加載器加載。當(dāng)應(yīng)用程序類加載器發(fā)現(xiàn)拓展類加載器已將該同名類加載過了,則不會再次加載。

    package cn.itcast.jvm.t3.load;public class G {static {System.out.println("classpath G init");} } package cn.itcast.jvm.t3.load;public class Load5_2 {public static void main(String[] args) throws ClassNotFoundException {Class<?> aClass = Class.forName("cn.itcast.jvm.t3.load.G");System.out.println(aClass.getClassLoader());//原來:sun.misc.Launcher$AppClassLoader@18b4aac2} }

    輸出:

    classpath G init sun.misc.Launcher$AppClassLoader@18b4aac2

    寫一個同名的類G并打個 jar 包

    E:\git\jvm\out\production\jvm>jar -cvf my.jar cn/itcast/jvm/t3/load/G.class 已添加清單 正在添加: cn/itcast/jvm/t3/load/G.class(輸入 = 481) (輸出 = 322)(壓縮了 33%)

    將 jar 包拷貝到 JAVA_HOME/jre/lib/ext
    重新執(zhí)行 Load5_2,輸出:

    ext G init sun.misc.Launcher$ExtClassLoader@29453f44

    3.5.3 雙親委派模式

    所謂的雙親委派,就是指調(diào)用類加載器的 loadClass 方法時,查找類的規(guī)則

    注意
    這里的雙親,翻譯為上級似乎更為合適,因為它們并沒有繼承關(guān)系

    loadClass源碼:

    protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException {synchronized (getClassLoadingLock(name)) {// 首先查找該類是否已經(jīng)被該類加載器加載過了Class<?> c = findLoadedClass(name);//如果沒有被加載過if (c == null) {long t0 = System.nanoTime();try {//看是否被它的上級加載器加載過了 Extension的上級是Bootstarp,但它顯示為nullif (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) {//如果還是沒有找到,先讓拓展類加載器調(diào)用findClass方法去找到該類,如果還是沒找到,就拋出異常//然后讓應(yīng)用類加載器去找classpath下找該類long t1 = System.nanoTime();c = findClass(name);// 記錄時間sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if (resolve) {resolveClass(c);}return c;} }

    ①當(dāng)App嘗試加載一個類時,它不會直接嘗試加載這個類,首先會在自己的命名空間中查詢是否已經(jīng)加載過這個類,如果沒有會先將這個類加載請求委派給父類加載器Ext完成
    ②當(dāng)嘗試加載一個類時,它也不會直接嘗試加載這個類,也會在自己的命名空間中查詢是否已經(jīng)加載過這個類,沒有的話也會先將這個類加載請求委派給父類加載器完成
    ③如果加載失敗,也就是代表著:這個需要被加載的類不在的加載范圍內(nèi),那么會重新將這個類加載請求交由子類加載器完成
    ④如果加載失敗,代表著這個類也不在的加載范圍內(nèi),最后會重新將這個類加載請求交給子類加載器完成
    ⑤如果加載器也加載失敗,就代表這個類根據(jù)全限定名無法查找到,則會拋出ClassNotFoundException異常

    3.5.4 線程上下文類加載器(雙親委派破壞者)

    • 在Java中,官方為我們提供了很多SPI接口,例如JDBC、JBI、JNDI等。這類SPI接口,官方往往只會定義規(guī)范,具體的實(shí)現(xiàn)則是由第三方來完成的,比如JDBC,不同的數(shù)據(jù)庫廠商都需自己根據(jù)JDBC接口的定義進(jìn)行實(shí)現(xiàn)。
      在使用 JDBC 時,都需要加載 Driver 驅(qū)動,不知道你注意到?jīng)]有,不寫Class.forName("com.mysql.jdbc.Driver")也是可以讓 com.mysql.jdbc.Driver 正確加載的,你知道是怎么做的嗎?
    • 總結(jié):
      Java提供了很多核心接口的定義,這些接口被稱為SPI接口,同時為了方便加載第三方的實(shí)現(xiàn)類,SPI提供了一種動態(tài)的服務(wù)發(fā)現(xiàn)機(jī)制(約定),只要第三方在編寫實(shí)現(xiàn)類時,在工程內(nèi)新建一個META-INF/services/目錄在該目錄下創(chuàng)建一個與服務(wù)接口名稱同名的文件,那么在程序啟動的時候,就會根據(jù)約定去找到所有符合規(guī)范的實(shí)現(xiàn)類,然后交給線程上下文類加載器進(jìn)行加載處理。

    3.5.5 自定義類加載器

    • 什么時候需要自定義類加載器
    • 想加載非 classpath 隨意路徑中的類文件
    • 都是通過接口來使用實(shí)現(xiàn),希望解耦時,常用在框架設(shè)計
    • 這些類希望予以隔離,不同應(yīng)用的同名類都可以加載,不沖突,常見于 tomcat 容器
    • 步驟:
    • 繼承 ClassLoader 父類
    • 要遵從雙親委派機(jī)制,重寫 findClass 方法(注意不是重寫 loadClass 方法,否則不會走雙親委派機(jī)制)
    • 讀取類文件的字節(jié)碼
    • 調(diào)用父類的 defineClass 方法來加載類
    • 使用者調(diào)用該類加載器的 loadClass 方法

    3.6 運(yùn)行期優(yōu)化

    3.6.1 即時編譯JIT

    • 分層編譯(TieredCompilation)
      JVM 將執(zhí)行狀態(tài)分成了 5 個層次:

      • 0 層,解釋執(zhí)行(Interpreter)
      • 1 層,使用 C1 即時編譯器編譯執(zhí)行(不帶 profiling)
      • 2 層,使用 C1 即時編譯器編譯執(zhí)行(帶基本的 profiling)
      • 3 層,使用 C1 即時編譯器編譯執(zhí)行(帶完全的 profiling)
      • 4 層,使用 C2 即時編譯器編譯執(zhí)行

      profiling 是指在運(yùn)行過程中收集一些程序執(zhí)行狀態(tài)的數(shù)據(jù),例如【方法的調(diào)用次數(shù)】,【循環(huán)的
      回邊次數(shù)】等

    • 即時編譯器(JIT)與解釋器的區(qū)別

      • 解釋器:
        • 將字節(jié)碼解釋為機(jī)器碼,下次即使遇到相同的字節(jié)碼,仍會執(zhí)行重復(fù)的解釋
        • 將字節(jié)碼解釋為針對所有平臺都通用的機(jī)器碼
      • JIT:
        • 將一些字節(jié)碼編譯為機(jī)器碼,并存入 Code Cache,下次遇到相同的代碼,直接執(zhí)行,無需再編譯
        • 會根據(jù)平臺類型,生成平臺特定的機(jī)器碼
          對于占據(jù)大部分的不常用的代碼,我們無需耗費(fèi)時間將其編譯成機(jī)器碼,而是采取解釋執(zhí)行的方式運(yùn)行;另一方面,對于僅占據(jù)小部分的熱點(diǎn)代碼,我們則可以將其編譯成機(jī)器碼,以達(dá)到理想的運(yùn)行速度。 執(zhí)行效率上簡單比較一下 Interpreter < C1 < C2,總的目標(biāo)是發(fā)現(xiàn)熱點(diǎn)代碼(hotspot名稱的由來),并對其進(jìn)行優(yōu)化

    3.6.2 常見JIT優(yōu)化

    逃逸分析

    逃逸分析(Escape Analysis)簡單來講就是,Java Hotspot 虛擬機(jī)可以分析新創(chuàng)建對象的使用范圍,并決定是否在 Java 堆上分配內(nèi)存的一項技術(shù)

    • 逃逸分析的 JVM 參數(shù)如下:
      • 開啟逃逸分析:-XX:+DoEscapeAnalysis
      • 關(guān)閉逃逸分析:-XX:-DoEscapeAnalysis
      • 顯示分析結(jié)果:-XX:+PrintEscapeAnalysis
    • 逃逸分析技術(shù)在 Java SE 6u23+ 開始支持,并默認(rèn)設(shè)置為啟用狀態(tài),可以不用額外加這個參數(shù)

    方法內(nèi)聯(lián)

    • 內(nèi)聯(lián)函數(shù):內(nèi)聯(lián)函數(shù)就是在程序編譯時,編譯器將程序中出現(xiàn)的內(nèi)聯(lián)函數(shù)的調(diào)用表達(dá)式用內(nèi)聯(lián)函數(shù)的函數(shù)體來直接進(jìn)行替換
    • C++是否為內(nèi)聯(lián)函數(shù)由自己決定,Java由編譯器決定。Java不支持直接聲明為內(nèi)聯(lián)函數(shù)的,如果想讓他內(nèi)聯(lián),你只能夠向編譯器提出請求: 關(guān)鍵字final修飾 用來指明那個函數(shù)是希望被JVM內(nèi)聯(lián)的。
    • 如果JVM監(jiān)測到一些小方法被頻繁的執(zhí)行,它會進(jìn)行內(nèi)聯(lián),把方法的調(diào)用替換成方法體本身,如下例中的square方法:
    package cn.itcast.jvm.t3.jit;import java.util.Random; import java.util.concurrent.ThreadLocalRandom;public class JIT2 {// -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining (解鎖隱藏參數(shù))打印 inlining 信息 // -XX:CompileCommand=dontinline,*JIT2.square 禁止某個方法 inlining // -XX:+PrintCompilation 打印編譯信息public static void main(String[] args) {int x = 0;for (int i = 0; i < 500; i++) {long start = System.nanoTime();for (int j = 0; j < 1000; j++) {x = square(9);}long end = System.nanoTime();System.out.printf("%d\t%d\t%d\n",i,x,(end - start));}}private static int square(final int i) {return i * i;} }

    反射優(yōu)化(字段優(yōu)化)

    public class Reflect1 {public static void foo() {System.out.println("foo...");}public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {Method foo = Demo3.class.getMethod("foo");for(int i = 0; i<=16; i++) {foo.invoke(null);}} }

    invoke方法源碼:

    默認(rèn)會通過DelegatingMethodAccessorImpl訪問NativeMethodAccessorImpl,NativeMethodAccessorImpl方法中規(guī)定:當(dāng)invoke方法調(diào)用次數(shù)超過15次時,會將本地方法訪問器替換為一個運(yùn)行期間動態(tài)生成的新的方法訪問器:

    class NativeMethodAccessorImpl extends MethodAccessorImpl {private final Method method;private DelegatingMethodAccessorImpl parent;private int numInvocations;NativeMethodAccessorImpl(Method var1) {this.method = var1;}public Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException {if (++this.numInvocations > ReflectionFactory.inflationThreshold() && !ReflectUtil.isVMAnonymousClass(this.method.getDeclaringClass())) {MethodAccessorImpl var3 = (MethodAccessorImpl)(new MethodAccessorGenerator()).generateMethod(this.method.getDeclaringClass(), this.method.getName(), this.method.getParameterTypes(), this.method.getReturnType(), this.method.getExceptionTypes(), this.method.getModifiers());this.parent.setDelegate(var3);}return invoke0(this.method, var1, var2);}void setParent(DelegatingMethodAccessorImpl var1) {this.parent = var1;}private static native Object invoke0(Method var0, Object var1, Object[] var2); }



    當(dāng)調(diào)用到第 16 次(從0開始算)時,會采用運(yùn)行時生成的類代替掉最初的實(shí)現(xiàn),可以通過 debug 得到類名為:sun.reflect.GeneratedMethodAccessor1,其在實(shí)現(xiàn)時會從反射調(diào)用變?yōu)檎U{(diào)用,即直接調(diào)用Reflect1.foo()

    4. 內(nèi)存模型

    4.1 java內(nèi)存模型

    • 很多人將【java 內(nèi)存結(jié)構(gòu)】與【java 內(nèi)存模型】傻傻分不清,【java 內(nèi)存模型】是 **Java Memory Model(JMM)**的意思。
    • 關(guān)于它的權(quán)威解釋,請參考https://download.oracle.com/otn-pub/jcp/memory_model-1.0-pfd-spec-oth-JSpec/memory_model-1_0-pfd-spec.pdf?AuthParam=1562811549_4d4994cbd5b59d964cd2907ea22ca08b
      簡單的說,JMM 定義了一套在多線程讀寫共享數(shù)據(jù)時(成員變量、數(shù)組)時,對數(shù)據(jù)的可見性、有序性、和原子性的規(guī)則和保障

    4.1.1 原子性

    原子性是指在一個操作中就是cpu不可以在中途暫停然后再調(diào)度,既不被中斷操作,要不執(zhí)行完成,要不就不執(zhí)行
    原子性在學(xué)習(xí)線程時講過,下面來個例子簡單回顧一下:
    問題提出,兩個線程對初始值為 0 的靜態(tài)變量一個做自增,一個做自減,各做 5000 次,結(jié)果是 0 嗎?

    • 問題分析:
      以上的結(jié)果可能是正數(shù)、負(fù)數(shù)、零。為什么呢?因為 Java 中對靜態(tài)變量的自增,自減并不是原子操作。
      例如對于 i++ 而言(i 為靜態(tài)變量),實(shí)際會產(chǎn)生如下的 JVM 字節(jié)碼指令:

      而對應(yīng) i--也是類似:

      而 Java 的內(nèi)存模型如下,完成靜態(tài)變量的自增,自減需要在主存和線程內(nèi)存中進(jìn)行數(shù)據(jù)交換:

      如果是單線程以下8 行代碼是順序執(zhí)行(不會交錯)沒有問題:

      但多線程下這 8 行代碼可能交錯運(yùn)行(不同線程搶占CPU資源):
    • 解決方法:synchronized (同步關(guān)鍵字)
      • 語法:synchronized( 對象 ) { 要作為原子操作代碼 }
      • 用 synchronized解決并發(fā)問題:
      • 理解:
        你可以把 obj 想象成一個房間,線程 t1,t2 想象成兩個人。
        當(dāng)線程 t1 執(zhí)行到 synchronized(obj) 時就好比 t1 進(jìn)入了這個房間,并反手鎖住了門,在門內(nèi)執(zhí)行count++ 代碼。
        這時候如果 t2 也運(yùn)行到了 synchronized(obj) 時,它發(fā)現(xiàn)門被鎖住了,只能在門外等待。
        當(dāng) t1 執(zhí)行完 synchronized{} 塊內(nèi)的代碼,這時候才會解開門上的鎖,從 obj 房間出來。t2 線程這時才可以進(jìn)入 obj 房間,反鎖住門,執(zhí)行它的 count-- 代碼。

        注意:上例中 t1 和 t2 線程必須用 synchronized 鎖住同一個 obj 對象,如果 t1 鎖住的是 m1 對象,t2 鎖住的是 m2 對象,就好比兩個人分別進(jìn)入了兩個不同的房間,沒法起到同步的效果。

    4.1.2 可見性

    • 退不出的循環(huán)
      先來看一個現(xiàn)象,main 線程對 run 變量的修改對于 t 線程不可見,導(dǎo)致了 t 線程無法停止:

      原因分析:

    • 初始狀態(tài), t 線程剛開始從主內(nèi)存讀取了 run 的值到工作內(nèi)存。
    • 因為 t 線程要頻繁從主內(nèi)存中讀取 run 的值,JIT 編譯器會將 run 的值緩存至自己工作內(nèi)存中的高速緩存中,減少對主存中 run 的訪問,提高效率
    • 1 秒之后,main 線程修改了 run 的值,并同步至主存,而 t 是從自己工作內(nèi)存中的高速緩存中讀取這個變量的值,結(jié)果永遠(yuǎn)是舊值
    • 解決方法:volatile(易變關(guān)鍵字)
      它可以用來修飾成員變量和靜態(tài)成員變量,他可以避免線程從自己的工作緩存中查找變量的值,必須到主存中獲取它的值,線程操作 volatile 變量都是直接操作主存

    • 可見性:
      前面例子體現(xiàn)的實(shí)際就是可見性,它保證的是在多個線程之間,一個線程對 volatile 變量的修改對另一個線程可見, 不能保證原子性,僅用在一個寫線程,多個讀線程的情況:
      上例從字節(jié)碼理解是這樣的:

      比較一下之前我們將線程安全時舉的例子:兩個線程一個 i++ 一個 i--,只能保證看到最新值,不能解決指令交錯

      注意
      synchronized 語句塊既可以保證代碼塊的原子性,也同時保證代碼塊內(nèi)變量的可見性。但缺點(diǎn)是
      synchronized是屬于重量級操作,性能相對更低

      • 如果在前面示例的死循環(huán)中加入 System.out.println() 會發(fā)現(xiàn)即使不加 volatile 修飾符,線程 t 也能正確看到對 run 變量的修改了,想一想為什么?
        println()方法底層包含synchronized關(guān)鍵字,強(qiáng)制當(dāng)前線程從主存中讀取變量值。

    4.1.3 有序性

    • 詭異的結(jié)果

      • I_Result 是一個對象,有一個屬性 r1 用來保存結(jié)果,問,可能的結(jié)果有幾種?

        • 情況1:線程1 先執(zhí)行,這時 ready = false,所以進(jìn)入 else 分支結(jié)果為 1
        • 情況2:線程2 先執(zhí)行 num = 2,但沒來得及執(zhí)行 ready = true,線程1 執(zhí)行,還是進(jìn)入 else 分支,結(jié)果為1
        • 情況3:線程2 執(zhí)行到 ready = true,線程1 執(zhí)行,這回進(jìn)入 if 分支,結(jié)果為 4(因為 num 已經(jīng)執(zhí)行過了)
        • 情況4:線程2 執(zhí)行 ready = true,切換到線程1,進(jìn)入 if 分支,相加為 0,再切回線程2 執(zhí)行num = 2
      • 這種現(xiàn)象叫做指令重排,是 JIT 編譯器在運(yùn)行時的一些優(yōu)化,這個現(xiàn)象需要通過大量測試才能復(fù)現(xiàn):借助 java 并發(fā)壓測工具 jcstress
        mvn archetype:generate -DinteractiveMode=false - DarchetypeGroupId=org.openjdk.jcstress -DarchetypeArtifactId=jcstress-java-test- archetype -DgroupId=org.sample -DartifactId=test -Dversion=1.0
        創(chuàng)建 maven 項目,提供如下測試類:

        執(zhí)行:

        mvn clean install java -jar target/jcstress.jar

        會輸出我們感興趣的結(jié)果,摘錄其中一次結(jié)果:


        可以看到,出現(xiàn)結(jié)果為 0 的情況有 638 次,雖然次數(shù)相對很少,但畢竟是出現(xiàn)了。

    • 解決方法
      volatile 修飾的變量,可以禁用指令重排

      結(jié)果為:

    • 有序性理解
      JVM 會在不影響正確性的前提下,可以調(diào)整語句的執(zhí)行順序,思考下面一段代碼:

      可以看到,至于是先執(zhí)行 i 還是 先執(zhí)行 j ,對最終的結(jié)果不會產(chǎn)生影響。這種特性稱之為『指令重排』,多線程下『指令重排』會影響正確性,例如著名的 double-checked locking 模式實(shí)現(xiàn)單例:

      以上的實(shí)現(xiàn)特點(diǎn)是:

      • 懶惰實(shí)例化
      • 首次使用 getInstance() 才使用 synchronized 加鎖,后續(xù)使用時無需加鎖

      但在多線程環(huán)境下,上面的代碼是有問題的, INSTANCE = new Singleton() 對應(yīng)的字節(jié)碼為:

      其中 4 7 兩步的順序不是固定的,也許 jvm 會優(yōu)化為:先將引用地址賦值給 INSTANCE 變量后,再執(zhí)行構(gòu)造方法,如果兩個線程 t1,t2 按如下時間序列執(zhí)行:

      這時 t1 還未完全將構(gòu)造方法執(zhí)行完畢,如果在構(gòu)造方法中要執(zhí)行很多初始化操作,那么 t2 拿到的是將是一個未初始化完畢的單例
      對 INSTANCE 使用 volatile 修飾即可,可以禁用指令重排,但要注意在 JDK 5 以上的版本的 volatile 才會真正有效

    4.1.4 happens-before

    happens-before 規(guī)定了哪些寫操作對其它線程的讀操作可見,它是可見性與有序性的一套規(guī)則總結(jié),拋開以下 happens-before 規(guī)則,JMM 并不能保證一個線程對共享變量的寫,對于其它線程對該共享變量的讀可見:

    • 線程解鎖 m 之前對變量的寫,對于接下來對 m 加鎖的其它線程對該變量的讀可見
    • 線程對 volatile 變量的寫,對接下來其它線程對該變量的讀可見
    • 線程 start 前對變量的寫,對該線程開始后對該變量的讀可見
    • 線程結(jié)束前對變量的寫,對其它線程得知它結(jié)束后的讀可見(比如其它線程調(diào)用 t1.isAlive() 或t1.join()等待它結(jié)束)
    • 線程 t1 打斷 t2(interrupt)前對變量的寫,對于其他線程得知 t2 被打斷后對變量的讀可見(通過t2.interrupted 或 t2.isInterrupted)
    • 對變量默認(rèn)值(0,false,null)的寫,對其它線程對該變量的讀可見
    • 具有傳遞性,如果 x hb-> y 并且 y hb-> z 那么有 x hb-> z

    變量都是指成員變量或靜態(tài)成員變量

    4.2 CAS 與 原子類

    4.2.1 CAS(Compare and Swap)

    CAS 即 Compare and Swap ,它體現(xiàn)的一種樂觀鎖的思想,比如多個線程要對一個共享的整型變量執(zhí)行 +1 操作:

    • 獲取共享變量時,為了保證該變量的可見性,需要使用 volatile 修飾(保證拿到的是最新值,否則CAS沒有意義)。結(jié)合 CAS 和 volatile可以實(shí)現(xiàn)無鎖并發(fā),適用于競爭不激烈、多核 CPU 的場景下。
      • 因為沒有使用 synchronized,所以線程不會陷入阻塞,這是效率提升的因素之一
      • 但如果競爭激烈,可以想到重試必然頻繁發(fā)生,反而效率會受影響
    • CAS 底層依賴于一個 Unsafe 類來直接調(diào)用操作系統(tǒng)底層的 CAS 指令

    4.2.2 樂觀鎖與悲觀鎖

    • CAS 是基于樂觀鎖的思想:最樂觀的估計,不怕別的線程來修改共享變量,就算改了也沒關(guān)系,我吃虧點(diǎn)再重試唄。
    • synchronized 是基于悲觀鎖的思想:最悲觀的估計,得防著其它線程來修改共享變量,我上了鎖你們都別想改,我改完了解開鎖,你們才有機(jī)會。

    4.2.3 原子操作類

    **juc(java.util.concurrent)**中提供了原子操作類,可以提供線程安全的操作,例如:AtomicInteger、AtomicBoolean等,它們底層就是采用 CAS 技術(shù) + volatile 來實(shí)現(xiàn)的。

    4.3 synchronized 優(yōu)化

    Java HotSpot 虛擬機(jī)中,每個對象都有對象頭(包括 class 指針和 Mark Word)Mark Word 平時存儲這個對象的哈希碼 、 分代年齡 ,當(dāng)加鎖時,這些信息就根據(jù)情況被替換為標(biāo)記位 、 線程鎖記錄指針 、 重量級鎖指針 、 線程ID 等內(nèi)容。

    4.3.1 輕量級鎖

    • 如果一個對象雖然有多線程訪問,但多線程訪問的時間是錯開的(也就是沒有競爭),那么可以使用輕量級鎖來優(yōu)化。這就好比:
      學(xué)生(線程 A)用課本占座,上了半節(jié)課,出門了(CPU時間到),回來一看,發(fā)現(xiàn)課本沒變,說明沒有競爭,繼續(xù)上他的課。
      如果這期間有其它學(xué)生(線程 B)來了,會告知(線程A)有并發(fā)訪問,線程 A 隨即升級為重量級鎖,進(jìn)入重量級鎖的流程。
      而重量級鎖就不是那么用課本占座那么簡單了,可以想象線程 A 走之前,把座位用一個鐵柵欄圍起來。
    • 假設(shè)有兩個方法同步塊,利用同一個對象加鎖:

      每個線程都的棧幀都會包含一個鎖記錄的結(jié)構(gòu),內(nèi)部可以存儲鎖定對象的 Mark Word

    4.3.2 鎖膨脹

    如果在嘗試加輕量級鎖的過程中,CAS 操作無法成功,這時一種情況就是有其它線程為此對象加上了輕量級鎖(有競爭),這時需要進(jìn)行鎖膨脹,將輕量級鎖變?yōu)橹亓考夋i。

    4.3.3 重量鎖

    • 重量級鎖競爭的時候,還可以使用自旋來進(jìn)行優(yōu)化,如果當(dāng)前線程自旋成功(即這時候持鎖線程已經(jīng)退出了同步塊,釋放了鎖),這時當(dāng)前線程就可以避免阻塞。
    • Java 6 之后自旋鎖是自適應(yīng)的,比如對象剛剛的一次自旋操作成功過,那么認(rèn)為這次自旋成功的可能性會高,就多自旋幾次;反之,就少自旋甚至不自旋,總之,比較智能。
      • 自旋會占用 CPU 時間,單核 CPU 自旋就是浪費(fèi),多核 CPU 自旋才能發(fā)揮優(yōu)勢。
      • 好比等紅燈時汽車是不是熄火,不熄火相當(dāng)于自旋(等待時間短了劃算),熄火了相當(dāng)于阻塞(等待時間長了劃算)
      • Java 7 之后不能控制是否開啟自旋功能
    • 自旋重試成功的情況:
    • 自旋重試失敗的情況:

    4.3.4 偏向鎖

    輕量級鎖沒有競爭時(就自己這個線程),每次重入仍然需要執(zhí)行 CAS 操作。Java 6 中引入了偏向鎖來做進(jìn)一步優(yōu)化:只有第一次使用時CAS 將線程 ID 設(shè)置到對象的 Mark Word 頭,之后發(fā)現(xiàn)這個線程 ID是自己的就表示沒有競爭,不用重新 CAS。

    • 撤銷偏向需要將持鎖線程升級為輕量級鎖,這個過程中所有線程需要暫停(STW)

    • 訪問對象的 hashCode 也會撤銷偏向鎖

    • 如果對象雖然被多個線程訪問,但沒有競爭,這時偏向了線程 T1 的對象仍有機(jī)會重新偏向 T2,重偏向會重置對象的 Thread ID

    • 撤銷偏向和重偏向都是批量進(jìn)行的,以類為單位

    • 如果撤銷偏向到達(dá)某個閾值,整個類的所有對象都會變?yōu)椴豢善虻?/p>

    • 可以主動使用 -XX:-UseBiasedLocking 禁用偏向鎖
      可以參考這篇論文:https://www.oracle.com/technetwork/java/biasedlocking-oopsla2006-wp-149958.pdf

    • 假設(shè)有兩個方法同步塊,利用同一個對象加鎖:

    4.3.5 其它優(yōu)化

  • 減少上鎖時間
    同步代碼塊中盡量短
  • 減少鎖的粒度
    將一個鎖拆分為多個鎖提高并發(fā)度,例如:
    • ConcurrentHashMap
    • LongAdder 分為 base 和 cells 兩部分。沒有并發(fā)爭用的時候或者是 cells 數(shù)組正在初始化的時候,會使用 CAS 來累加值到 base,有并發(fā)爭用,會初始化 cells 數(shù)組,數(shù)組有多少個 cell,就允許有多少線程并行修改,最后將數(shù)組中每個 cell 累加,再加上 base 就是最終的值
    • LinkedBlockingQueue 入隊和出隊使用不同的鎖,相對于LinkedBlockingArray只有一個鎖效率要高
  • 鎖粗化
    多次循環(huán)進(jìn)入同步塊不如同步塊內(nèi)多次循環(huán)
    另外 JVM 可能會做如下優(yōu)化,把多次 append 的加鎖操作粗化為一次(因為都是對同一個對象加鎖,沒必要重入多次)
  • 鎖消除
    JVM 會進(jìn)行代碼的逃逸分析,例如某個加鎖對象是方法內(nèi)局部變量,不會被其它線程所訪問到,這時候就會被即時編譯器忽略掉所有同步操作。
  • 讀寫分離
    CopyOnWriteArrayList
    ConyOnWriteSet
  • 參考:
    https://wiki.openjdk.java.net/display/HotSpot/Synchronization
    http://luojinping.com/2015/07/09/java鎖優(yōu)化/
    https://www.infoq.cn/article/java-se-16-synchronized
    https://www.jianshu.com/p/9932047a89be
    https://www.cnblogs.com/sheeva/p/6366782.html
    https://stackoverflow.com/questions/46312817/does-java-ever-rebias-an-individual-lock

    總結(jié)

    以上是生活随笔為你收集整理的黑马程序员JVM完整教程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。

    国产黄色精品网站 | 日日干狠狠操 | 国产特级毛片 | a黄色 | 中文字幕亚洲情99在线 | 精品亚洲欧美无人区乱码 | 西西444www大胆高清视频 | 免费视频久久久久 | 一区二区在线影院 | 日韩大片免费观看 | 麻豆传媒视频在线免费观看 | 亚洲精品乱码久久久久久久久久 | 国产精品手机看片 | 性色av一区二区三区在线观看 | 美女视频黄网站 | 日本最新中文字幕 | 国产理论免费 | 亚洲永久免费av | 很污的网站 | 国产人成精品一区二区三 | 久草视频资源 | 狠色在线| 国产成人精品一区二区三区网站观看 | 黄色av电影一级片 | 依人成人综合网 | 欧美a级片网站 | 色噜噜日韩精品欧美一区二区 | 亚洲伦理一区二区 | 国产丝袜一区二区三区 | 在线观看免费观看在线91 | 超碰精品在线观看 | 在线免费视频你懂的 | 成人毛片一区二区三区 | 精品国产一区二区三区久久久蜜月 | 国产精品久久电影观看 | 在线免费观看麻豆 | 91在线视频网址 | 黄色a级片在线观看 | 亚洲永久国产精品 | 青青射| 99久久精品免费一区 | 色com网 | 99精品视频在线观看视频 | 日本久久精 | 日本成人中文字幕在线观看 | 国产另类xxxxhd高清 | 99热高清 | 欧美va天堂va视频va在线 | 麻豆视频免费在线观看 | 精品国产一区二 | 色的网站在线观看 | 在线你懂的视频 | 免费国产黄线在线观看视频 | 免费亚洲电影 | 欧美久久电影 | 国产原厂视频在线观看 | 日b黄色片 | 97视频免费看 | 精品国产一区二区三区四区vr | 热久久最新地址 | 国产糖心vlog在线观看 | 免费日韩在线 | 天天插天天爽 | 一区二区三区中文字幕在线 | 看毛片网站 | 成人免费观看视频网站 | 久久伊人综合 | 国产精品国产三级国产不产一地 | 日韩在线看片 | 狠狠操狠狠干天天操 | 欧美精品免费在线观看 | 国产日韩精品一区二区在线观看播放 | 日韩免费网站 | 免费高清无人区完整版 | 国产麻豆果冻传媒在线观看 | 中文字幕 成人 | 黄色a一级片| 国产尤物在线观看 | 最近免费中文字幕大全高清10 | 成人动漫一区二区三区 | 久久精品亚洲综合专区 | 欧美日韩国产一区二区三区在线观看 | 岛国av在线免费 | 日韩夜夜爽 | 在线黄色国产电影 | 欧美激情精品久久久久久免费印度 | 精品九九九| 国产免费又爽又刺激在线观看 | av免费黄色 | 在线观看中文字幕dvd播放 | 狠狠操操操 | 欧美福利久久 | 亚洲国产69 | 五月婷婷在线观看 | 蜜臀av夜夜澡人人爽人人 | 99精品久久久久久久 | 亚洲天堂视频在线 | 日韩精品中文字幕在线不卡尤物 | 日韩视频一 | 亚洲综合狠狠干 | 99久久婷婷国产综合精品 | 在线国产日本 | 亚洲成人第一区 | 亚洲精品黄色在线观看 | 日韩不卡高清 | www.激情五月.com | 最新一区二区三区 | 亚洲国产精品久久 | 欧美了一区在线观看 | 91丨九色丨首页 | 久久久久久毛片精品免费不卡 | 国产高清在线免费 | 免费三级av| 中国一级片在线播放 | 超碰国产在线观看 | 久久精品网站免费观看 | 99 精品 在线 | 欧美视频日韩视频 | 日韩有码中文字幕在线 | 成人黄色av免费在线观看 | 丝袜制服综合网 | 在线观看涩涩 | 国产成人一二三 | 中文字幕在线观看视频一区二区三区 | 天天综合色天天综合 | 精品国产乱码久久久久久久 | 国产不卡一| 国产精品99久久久久久人免费 | 欧美精品免费一区二区 | 久久久久久久久久久成人 | 最新久久久 | 精品久久久久久久久久岛国gif | a黄色| av免费在线观看1 | 中文字幕在线看视频 | 99精品亚洲| 99久久99热这里只有精品 | 国产中文字幕在线观看 | 久久免费毛片视频 | 国产玖玖在线 | 国产区 在线 | 麻豆国产网站入口 | 精品在线视频观看 | 日韩免费三区 | 久久久精品成人 | 91精品中文字幕 | 免费观看国产视频 | 色999精品 | 少妇自拍av | 中文字幕中文字幕在线一区 | 亚洲传媒在线 | 久久1区| 国产高清免费av | 又黄又爽又刺激视频 | 日韩最新av | 天天色天天上天天操 | 伊人久久一区 | 99精品国产亚洲 | 五月天开心| 91视频国产免费 | 免费在线观看中文字幕 | 最近中文字幕在线中文高清版 | 久久精久久精 | 97香蕉超级碰碰久久免费软件 | 91av在线免费视频 | 国产污视频在线观看 | 亚洲精品黄网站 | 亚洲3级 | 久久影院中文字幕 | 亚洲成人精品久久 | 黄网站免费大全入口 | 国产成人精品综合 | 欧美性久久久 | 免费一级片在线 | 中文字幕日韩免费视频 | 天天草天天色 | 国产精品久久久久久久久久久久久 | 国产亚洲一区二区三区 | 国产美女久久久 | 91亚洲在线 | 欧美一区二视频在线免费观看 | 99久久精品免费看国产免费软件 | 国产精品一区二区免费视频 | 国产精品视频专区 | 国产高清视频在线播放一区 | 999超碰 | 中文字幕免费国产精品 | 亚洲mv大片欧洲mv大片免费 | 欧美日韩三区二区 | 91精品啪在线观看国产81旧版 | 91成熟丰满女人少妇 | 国产精品久久久久一区二区三区 | 狠狠精品| 超碰官网 | 天天干天天草 | 中文字幕一区2区3区 | 美女av免费 | 亚洲专区一二三 | 在线看的av网站 | 欧美精品一区二区三区一线天视频 | 欧美狠狠色 | 国产成人av网站 | www.色婷婷 | 精品国产自在精品国产精野外直播 | 久久久蜜桃 | 黄色视屏在线免费观看 | 超碰在线99| 奇米7777狠狠狠琪琪视频 | 久久久久国产精品厨房 | 久久伊人免费视频 | 激情久久伊人 | 久久久成人精品 | 一区二区三区动漫 | 国产一级片免费播放 | 国产精品白丝jk白祙 | 人成免费网站 | 久久精品网站免费观看 | 日韩av视屏 | 久久久久婷 | 久久综合视频网 | www免费看| 91桃色在线观看视频 | 在线免费黄色片 | 99精品在这里 | 国产99久久久精品 | 日本最新中文字幕 | 日韩久久午夜一级啪啪 | 91成版人在线观看入口 | 日韩在线电影 | 国产视频精品久久 | 国产午夜精品一区二区三区四区 | 最新中文字幕在线观看视频 | 国产精品久久一卡二卡 | 欧美亚洲国产精品久久高清浪潮 | 久久精品99国产 | 欧美成人精品三级在线观看播放 | 日韩亚洲国产精品 | 久久精品视频免费播放 | 日韩精品中文字幕在线观看 | 麻豆视频免费网站 | 国模一区二区三区四区 | 蜜臀aⅴ精品一区二区三区 久久视屏网 | 91高清一区 | 天天爱天天 | 色婷婷伊人 | 麻豆视频国产在线观看 | 国产一区二区三精品久久久无广告 | 最新色站| 欧美一区二区视频97 | 国产精品理论在线观看 | 99精品免费视频 | 日韩在线精品 | 最新av网站在线观看 | 日韩手机在线 | 国产免费作爱视频 | 激情综合色综合久久 | 成年人app网址 | 国产麻豆果冻传媒在线观看 | 国产精品视频内 | 日韩动漫免费观看高清完整版在线观看 | 午夜日b视频 | 久久精品视频在线观看免费 | 国产精品免费久久久久影院仙踪林 | 91黄色免费网站 | 96国产精品视频 | 久久在线观看视频 | 日韩三级在线 | 免费看三级 | 久久综合狠狠综合久久狠狠色综合 | 一区二区三区动漫 | 亚洲欧美国内爽妇网 | www.亚洲黄| 嫩模bbw搡bbbb搡bbbb | 亚洲高清视频在线 | 国内精品一区二区 | 超碰人人在线观看 | 在线观看日韩一区 | 亚洲精品伦理在线 | 欧美色图视频一区 | 国产精品久久久久久久久久久久冷 | 人人要人人澡人人爽人人dvd | 免费看十八岁美女 | 99久热在线精品 | 五月婷婷播播 | 日本中文字幕电影在线免费观看 | 91成人午夜 | 中日韩在线视频 | 日韩手机在线 | 久久免费福利 | 色视频成人在线观看免 | 中文字幕二区在线观看 | 亚洲黄色一级电影 | 911免费视频 | 国产成人精品不卡 | 日日干夜夜草 | 天天操夜夜操夜夜操 | www久久九| 三级午夜片 | 国产不卡在线 | 久久公开免费视频 | 欧洲精品码一区二区三区免费看 | 午夜av免费观看 | 免费观看91视频 | 国产精品久久久久久久久久不蜜月 | 夜夜夜草| 色婷婷在线播放 | 麻花豆传媒mv在线观看 | 久久图| 日韩色av色资源 | 九色91在线视频 | 美女网站色免费 | 国产无遮挡猛进猛出免费软件 | 蜜桃av久久久亚洲精品 | 国产精品久久久久aaaa九色 | 91精品一区二区三区久久久久久 | 天天操天 | 欧美亚洲一区二区在线 | 波多野结衣在线观看一区 | 友田真希av| 国产精品久久久久av | 国产成人久久精品77777综合 | 国产精品毛片一区二区 | 性色av免费在线观看 | 国产精品永久免费视频 | 日韩网站视频 | 成人黄视频 | 综合色婷婷 | 99久久久久久久久久 | 国产又黄又猛又粗 | 日韩在线视频一区二区三区 | 超碰97人人射妻 | 中文字幕视频网 | 国产精品爽爽爽 | 日韩有码在线播放 | 超碰在线日本 | 麻豆精品视频在线观看免费 | 在线va网站 | 国产午夜精品一区二区三区欧美 | 久久久久99精品成人片三人毛片 | 五月婷婷视频在线 | 国产又粗又硬又长又爽的视频 | 婷婷色吧 | 国产精品国产三级国产不产一地 | 国产精品久久久久婷婷二区次 | 午夜精品久久久久久久99无限制 | 成 人 免费 黄 色 视频 | 999成人国产 | 亚洲网站在线看 | 成全在线视频免费观看 | 99久久精品国产一区二区三区 | 男女拍拍免费视频 | 久久黄色免费观看 | 成人免费一级 | 亚洲免费高清视频 | 亚州成人av在线 | 亚洲欧美色婷婷 | 中文字幕亚洲欧美 | 五月亚洲婷婷 | 国产精品网站一区二区三区 | 五月花丁香婷婷 | 麻豆视频免费播放 | 日韩视频三区 | 激情久久一区二区三区 | a级国产乱理论片在线观看 伊人宗合网 | 亚洲一级特黄 | 亚洲精品视频在线观看视频 | 人人玩人人添人人澡97 | 成人免费一区二区三区在线观看 | 91精品1区| 日韩欧美视频在线观看免费 | 中文字幕在线有码 | 在线av资源 | 亚洲精品视频网站在线观看 | 99久久综合狠狠综合久久 | 日韩国产精品一区 | 精品国产一区二区三区久久久 | 蜜臀91丨九色丨蝌蚪老版 | 日韩在线免费视频观看 | av字幕在线| 一级片免费观看 | 狠狠狠色丁香婷婷综合久久五月 | 国产一级二级视频 | 天天干天天碰 | 日日摸日日 | 天天草综合| 在线 日韩 av | 欧美日韩亚洲在线 | 成人免费观看视频网站 | 美女网站色免费 | av在线永久免费观看 | 久久久久久久久久久影视 | 亚洲精品毛片一级91精品 | 亚洲国产播放 | 日本最大色倩网站www | 国产在线观看黄 | 日韩av一区二区在线播放 | 粉嫩高清一区二区三区 | 97色在线视频 | 久久草草影视免费网 | 亚洲精品国产精品国自产观看 | 99视频免费在线观看 | 免费在线播放黄色 | 一区二区中文字幕在线播放 | 国产精品久久久久久久久久 | 男女视频久久久 | avwww在线| 日韩亚洲在线 | 天天干,天天操 | 免费观看的黄色片 | 亚洲开心激情 | 久久久久9999亚洲精品 | 欧美黑人巨大xxxxx | 天天天天天天干 | 天堂在线一区 | 深爱婷婷激情 | 欧美日本一二三 | 黄色三级免费 | 久章草在线 | 亚洲 综合 国产 精品 | 天天综合入口 | 色综合久久久久综合99 | 亚洲国产大片 | 在线天堂中文在线资源网 | av网站手机在线观看 | 成人99免费视频 | 日韩av电影中文字幕在线观看 | 在线观看视频福利 | 性色xxxxhd | 美女免费视频一区 | 奇米影音四色 | 超碰在线日韩 | 国产一二三区在线观看 | 在线中文字幕播放 | 亚洲精品黄色 | 9在线观看免费高清完整版在线观看明 | 久久99精品国产麻豆宅宅 | 国产成人综合图片 | 激情网站 | 色欧美成人精品a∨在线观看 | 欧美综合国产 | 欧美性性网 | 18久久久久久 | 国产亚洲成av片在线观看 | 美女搞黄国产视频网站 | 亚洲黄色片一级 | 久艹在线观看视频 | 日本视频高清 | 国产精品嫩草影院123 | 欧美日韩视频在线一区 | 日韩久久一区二区 | 麻豆免费在线播放 | 天天操网站 | 91伊人久久大香线蕉蜜芽人口 | 91久久丝袜国产露脸动漫 | 91精彩视频 | 亚洲精品国产精品国自产观看 | 色婷婷狠狠| 亚洲精品人人 | 黄av免费在线观看 | 天堂av在线中文在线 | 91正在播放 | 日韩欧美精品在线 | 免费观看成年人视频 | 激情久久久 | www.五月婷 | 色www永久免费 | 天天综合网天天综合色 | 天天伊人狠狠 | 欧美粗又大 | 99视频在线观看免费 | www.超碰| 亚洲国产精品成人va在线观看 | 日日夜夜爱 | 日韩高清免费在线观看 | 一区二区三区四区五区六区 | 91成人天堂久久成人 | av蜜桃在线 | 国产中文在线视频 | 日本特黄一级 | 亚洲欧美日韩精品久久奇米一区 | 国产偷国产偷亚洲清高 | 午夜在线看片 | 国产精品免费一区二区三区 | 亚洲精品免费在线观看 | 国产精品成人一区二区三区 | 人人草人 | 中文日韩在线视频 | 在线视频 你懂得 | 久久99亚洲网美利坚合众国 | 在线你懂的视频 | 日日夜夜添| 在线激情网 | 国产成人av在线影院 | 一区二区三区在线免费观看 | 天天曰夜夜爽 | 17videosex性欧美 | 一区二区三区在线免费观看 | 亚洲爱爱视频 | 午夜影院日本 | 国产精品99久久久久 | 国产一级大片免费看 | 2024av在线播放 | 欧美伦理一区二区三区 | 特级毛片在线免费观看 | 这里只有精彩视频 | 日韩一级片大全 | 精品国产一二区 | 日韩三级精品 | 亚洲区视频在线观看 | 日韩av偷拍 | 人人舔人人爱 | 欧美成人视 | 在线观看免费视频你懂的 | 国色天香在线观看 | 久久天天躁 | 国产99在线免费 | 99久久久久久 | 久久久黄色免费网站 | 国产黄色精品在线观看 | 久久精品视频免费播放 | 毛片网站观看 | 国产一区二区精品在线 | 国产视频精品免费播放 | 欧美日韩观看 | 日本69hd| 国产精品国产精品 | 久热免费在线观看 | 日本mv大片欧洲mv大片 | 久草在线看片 | 黄污视频大全 | 久久成视频| 国内精品视频在线 | 黄色av网站在线免费观看 | 精品一区 在线 | 国产精品一区二区免费在线观看 | 日韩欧美视频在线播放 | 色99视频 | 国产视频精品久久 | 精品免费久久久久久 | 亚洲干视频在线观看 | 中文在线天堂资源 | 亚洲人成在 | 在线观看免费日韩 | 97精品国产91久久久久久 | 九九在线国产视频 | 日韩欧美区| 在线性视频日韩欧美 | 久久久久久久久久久免费视频 | 欧美激情综合色 | 不卡的av在线 | 黄网站色 | 天天干天天射天天爽 | 亚洲成成品网站 | 亚洲成a人片综合在线 | 欧美人操人 | 国产精品国产亚洲精品看不卡15 | 国内精品久久久久影院一蜜桃 | 4438全国亚洲精品观看视频 | 亚洲成人av在线电影 | 国产午夜三级一二三区 | 91av综合| av丝袜在线 | 欧美日韩在线网站 | 激情五月婷婷综合 | 亚洲dvd | 九九影视理伦片 | 精品国产视频一区 | 欧美激情综合五月色丁香小说 | 中文高清av | 日韩色视频在线观看 | 亚洲精选在线 | 免费在线观看av电影 | 91c网站色版视频 | 亚洲欧美日韩在线看 | 美女网站在线观看 | 国产精品一区二 | 成片人卡1卡2卡3手机免费看 | 久久艹人人 | 久久色视频 | 国产a国产a国产a | 国产91在线 | 美洲 | 日韩在线视频在线观看 | 在线电影播放 | 国产一二三精品 | 一区二区三区在线不卡 | 97超碰在线人人 | 97精品国产97久久久久久春色 | 黄色av电影免费观看 | 国产福利精品一区二区 | 五月天伊人网 | 91成人天堂久久成人 | 国产伦理一区 | 欧美男女爱爱视频 | 国产成人一区三区 | 九九色在线观看 | 蜜臀久久99精品久久久无需会员 | 玖玖玖精品| 丁香六月婷 | 日日摸日日添夜夜爽97 | 视频99爱 | 日韩一区二区三区视频在线 | 国产福利精品一区二区 | 色综合久久久网 | 日韩精品一区二区三区第95 | 69av国产| 最新高清无码专区 | 日韩性网站 | 欧美激情片在线观看 | 婷婷色5月 | 欧美精品国产综合久久 | 草久视频在线 | 久久这里只精品 | 亚洲经典在线 | 精品久久一 | 色av网站 | 欧美超碰在线 | 天天色播| 中文字幕在线观看免费高清完整版 | 天堂av一区二区 | 欧美综合干 | 国产精品小视频网站 | 最新一区二区三区 | 亚洲精品视频一 | 激情综合中文娱乐网 | 成人h电影在线观看 | 欧美色综合天天久久综合精品 | 在线免费试看 | 久久免费成人精品视频 | 激情五月激情综合网 | 精品美女国产在线 | 亚洲婷久久 | 免费看久久| 中文字幕第一页av | 成人黄大片视频在线观看 | av中文字幕av | 人人爽久久涩噜噜噜网站 | 天堂av免费 | av丁香花 | 国产高清av免费在线观看 | 久久人人爽爽人人爽人人片av | 最新国产福利 | 一区二区不卡高清 | www.狠狠 | 狠狠色狠狠色综合日日小说 | 国产日本在线观看 | 福利网址在线观看 | 成年人免费观看国产 | 91久久国产露脸精品国产闺蜜 | 免费观看一区二区三区视频 | 亚州日韩中文字幕 | 国产区久久| 亚洲黄色免费观看 | 国产在线播放一区二区 | 成人av亚洲 | 国产+日韩欧美 | 一级精品视频在线观看宜春院 | 亚洲免费av在线播放 | 麻豆影视网 | 最近中文字幕大全中文字幕免费 | 亚洲精品久久久蜜桃 | 亚洲国内精品在线 | 久久久综合九色合综国产精品 | 国产在线97 | 又污又黄的网站 | 很污的网站 | 欧美一区影院 | 日韩免费高清在线 | 六月色丁香 | 日韩免费视频在线观看 | 午夜精品久久一牛影视 | 香蕉色综合 | 丁香六月婷 | 伊人中文字幕在线 | 狠狠色丁香婷婷综合 | 最新av在线播放 | 一级成人网| 激情久久网 | 午夜久草 | 国产在线观看你懂的 | 99日精品 | 精品自拍网 | 国产午夜一区二区 | 国产亚洲情侣一区二区无 | 欧美va电影 | 欧美小视频在线观看 | 久久久不卡影院 | 91久久爱热色涩涩 | 天天玩夜夜操 | 精品一区二区在线免费观看 | 国产黄影院色大全免费 | 91视频网址入口 | 天天躁日日躁狠狠躁 | 久久全国免费视频 | 国产福利一区在线观看 | 免费69视频 | 欧美精品久久久久久久久久 | 99久久精品网 | 丁香综合激情 | 日日色综合 | 日韩精品观看 | 天天av在线播放 | 最新日韩在线观看视频 | 亚洲精品美女久久17c | 色综合久久88色综合天天免费 | av线上看 | 欧美 日韩 国产 中文字幕 | 97超碰色偷偷 | 久久久久久久久久国产精品 | 午夜18视频在线观看 | 欧美精品久久人人躁人人爽 | 亚洲精品一区中文字幕乱码 | 国产成人一区二区三区电影 | 亚洲成av人影片在线观看 | 日韩免费小视频 | 黄色三级在线观看 | 91看国产| 手机在线看永久av片免费 | 国产色视频一区 | 久久综合精品一区 | 久久国产精品电影 | 欧美久久影院 | 福利精品在线 | 日韩在线三区 | 天天干天天干天天 | 亚洲久久视频 | 日韩av电影网站在线观看 | 亚洲成av人片在线观看香蕉 | 国产成人av片 | 狠狠干婷婷 | 视频一区二区三区视频 | 91精品国产福利在线观看 | 欧美日韩在线视频观看 | 国产亚洲精品精品精品 | 国产精品日韩高清 | 欧美三级免费 | 狠狠综合久久av | 亚洲乱码久久久 | 免费手机黄色网址 | 色a4yy| 成人91视频 | 婷婷www| 久草视频免费看 | 亚洲一区二区三区毛片 | 天天天色综合a | 91亚洲精品久久久蜜桃借种 | 午夜色性片 | 精品产品国产在线不卡 | 国产中文字幕免费 | 五月天激情开心 | 久草亚洲视频 | 香蕉网在线播放 | 日韩在线视频不卡 | 久久免费精品视频 | 欧美色图88 | 偷拍精偷拍精品欧洲亚洲网站 | 精品中文字幕视频 | 免费人成网ww44kk44 | 韩国av电影网 | 久久精品中文字幕一区二区三区 | 国产成视频在线观看 | 国产精品亚洲片在线播放 | 国产成人精品久久久 | 国产专区欧美专区 | 国色天香永久免费 | 久久综合久久综合九色 | 婷婷播播网 | 国产成人精品久久久久蜜臀 | 久久国产成人午夜av影院宅 | 久久久999精品视频 国产美女免费观看 | 日日骑| 天干啦夜天干天干在线线 | 99视频在线免费看 | 在线观看视频免费播放 | 亚洲成av人影片在线观看 | 操高跟美女 | 婷婷视频 | 在线观看日韩一区 | 中国一级片在线 | 国产精品手机看片 | 亚洲欧美日韩精品久久久 | 国内小视频在线观看 | 久久久久亚洲a | 成人毛片一区 | 日韩av影视在线 | 国产一级片网站 | 中文字幕欲求不满 | 日韩欧美视频免费看 | 深爱激情久久 | 久久99热这里只有精品 | 国产精品va最新国产精品视频 | 激情五月在线视频 | 欧美与欧洲交xxxx免费观看 | 国内久久久久 | 亚洲成av人电影 | 国产亚洲婷婷免费 | a在线一区 | 国产综合福利在线 | 黄色大片免费网站 | 久久精品日韩 | 精品欧美一区二区在线观看 | 91av看片| 日日干激情五月 | 在线网站黄 | 黄色av电影在线 | 91网址在线观看 | 成人免费看片网址 | 国产精品黄网站在线观看 | 成人资源网 | 在线观看中文字幕网站 | 亚洲欧洲中文日韩久久av乱码 | 黄色91在线| 日韩高清在线观看 | 久久精品久久99 | 91福利区一区二区三区 | 99久久99久久精品免费 | 国产成人a v电影 | 免费福利片2019潦草影视午夜 | 精品日韩在线 | 日韩有码中文字幕在线 | 中文一区二区三区在线观看 | 欧美aa在线 | 美女网站视频免费都是黄 | 国产小视频免费观看 | 精品久久久精品 | 色就色,综合激情 | 国产不卡在线观看视频 | 午夜丁香网 | 中文字幕在线播放一区二区 | 国产精品99久久99久久久二8 | 国产色视频一区二区三区qq号 | 一级c片| 国产小视频你懂的 | 成人国产在线 | 成人av电影免费在线观看 | 欧美精品久久久久久 | 日韩一级片观看 | 国产成人在线播放 | 综合国产在线观看 | 91在线免费播放 | 婷婷色网视频在线播放 | 81精品国产乱码久久久久久 | 深夜成人av | 一级黄色片在线免费看 | 九九免费精品视频 | 精品免费视频 | 久久综合久久鬼 | 色久av | 亚洲视频免费在线观看 | 欧美极度另类性三渗透 | 欧美成人91 | 中文字幕一二三区 | 国产三级久久久 | 日本久久成人中文字幕电影 | 欧美91视频 | 亚洲一级黄色大片 | 久久综合狠狠综合久久狠狠色综合 | 国产免费观看久久黄 | 色天天综合网 | 伊人婷婷网 | 久草91视频| 狠狠综合久久 | 精品久久久久一区二区国产 | 日韩视频一区二区三区在线播放免费观看 | 人人爽久久涩噜噜噜网站 | 亚洲高清国产视频 | 91成品人影院 | 麻豆成人精品 | 国产精品久久久久久久久搜平片 | 久久国产精品二国产精品中国洋人 | 天天天在线综合网 | www在线观看国产 | 国产中年夫妇高潮精品视频 | 激情网五月婷婷 | 涩涩色亚洲一区 | 亚洲高清色综合 | 日本九九视频 | 欧美成人a在线 | 91麻豆精品国产91久久久无限制版 | 久久国产精品免费一区 | 欧美91片 | 成年人视频在线免费观看 | 国产一及片 | 99久久婷婷国产一区二区三区 | 91九色在线视频观看 | 狠狠激情中文字幕 | 激情欧美一区二区三区 | 欧美日韩不卡一区二区三区 | 91精品视频在线看 | 日韩欧美在线观看一区二区 | 亚洲午夜精品久久久久久久久 | 一级理论片在线观看 | 四虎国产精品免费观看视频优播 | 91精品爽啪蜜夜国产在线播放 | 欧美日韩一区二区三区在线观看视频 | 日本公妇色中文字幕 | 精品亚洲免费 | 激情欧美日韩一区二区 | 日日干网址 | 国产美女视频免费观看的网站 | 欧美性受极品xxxx喷水 | 97精品国产一二三产区 | 午夜久久久久久久久久影院 | 亚洲精品www久久久久久 | 色视频网站在线观看一=区 a视频免费在线观看 | 91视频91自拍 | 日日夜夜国产 | 久久国产精品精品国产色婷婷 | 久草网首页 | 国产精品69av | 久久精品欧美日韩精品 | 中文字幕在线免费看线人 | 麻豆成人精品 | 亚洲伊人天堂 | 正在播放 久久 | 亚洲资源一区 | 中文字幕在线视频一区二区三区 | av大全在线免费观看 | 福利久久| 日韩成人邪恶影片 | 91香蕉视频色版 | 亚洲国产一区二区精品专区 | 在线观看国产www | 日韩在线电影一区 | .国产精品成人自产拍在线观看6 | 人人干人人艹 | 国产在线精品一区二区不卡了 | 九九久久国产 | av网站大全免费 | 日本精品一二区 | 玖玖在线精品 | 中文字幕在线播出 | 久久久www免费电影网 | 中文字幕av网站 | 国产黄色av | 一级一片免费看 | 99精品视频免费在线观看 | 欧美在线视频一区二区三区 | 91久久精品一区 | 亚洲精品国产区 | 国产精品永久久久久久久久久 | 成人不用播放器 | 国产高清在线观看av | 黄色1级毛片 | 亚洲国产成人精品在线 | 九九在线视频 | 色综合天天在线 | 日韩精品黄 | 国产又粗又猛又色 | 久久久久福利视频 | 五月婷婷丁香在线观看 | 美女网站视频免费黄 | 免费在线播放视频 | 五月婷婷欧美视频 | 最近中文字幕免费大全 | 成人免费一区二区三区在线观看 | 成人免费在线视频观看 | 国产精品久久久久久久久久妇女 | 欧美精品三级在线观看 | 黄色网址av| 欧美一级性生活片 | 国产精品久久久久久久久软件 | 中国一级特黄毛片大片久久 | 色婷婷导航 | 亚洲高清不卡av | 国产精品中文字幕在线 | 婷婷色网视频在线播放 | 91最新在线 | 国产福利在线不卡 | 亚洲国产大片 | 亚洲精品理论 | 免费三级a | 日韩精品一区二区三区不卡 | 欧美日韩国产免费视频 | 日韩中文字幕国产精品 | 亚洲日本黄色 | 亚洲精品白浆高清久久久久久 | 色偷偷网站视频 | 亚洲欧美日韩在线看 | 国产第一页在线播放 | 在线观看视频国产 | 国产成人久久精品 | 在线视频 国产 日韩 | 亚洲激情在线视频 | 蜜桃视频在线视频 | 98精品国产自产在线观看 | 成年人黄色在线观看 | av免费电影在线观看 | 五月婷婷久久丁香 | 缴情综合网五月天 | 免费黄色网址大全 | 中文字幕免费在线看 | 中文字幕永久免费 | 亚洲高清在线 | 四虎在线免费观看视频 | 久久伦理影院 | 国产精品一区二区久久精品爱微奶 | 久久综合一本 | 国产精品99久久久久久久久久久久 |