日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > java >内容正文

java

JVM内存结构和Java内存模型别再傻傻分不清了

發(fā)布時間:2023/12/10 java 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 JVM内存结构和Java内存模型别再傻傻分不清了 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

JVM內(nèi)存結(jié)構(gòu)和Java內(nèi)存模型都是面試的熱點(diǎn)問題,名字看感覺都差不多,網(wǎng)上有些博客也都把這兩個概念混著用,實(shí)際上他們之間差別還是挺大的。
通俗點(diǎn)說,JVM內(nèi)存結(jié)構(gòu)是與JVM的內(nèi)部存儲結(jié)構(gòu)相關(guān),而Java內(nèi)存模型是與多線程編程相關(guān),本文針對這兩個總是被混用的概念展開講解。

文章目錄

  • JVM內(nèi)存結(jié)構(gòu)
    • JVM構(gòu)成
    • JVM內(nèi)存結(jié)構(gòu)
        • 程序計數(shù)器
        • 虛擬機(jī)棧
        • 本地方法棧
        • 方法區(qū)
    • GC垃圾回收機(jī)制
        • 1. 垃圾判別方法
          • 引用計數(shù)算法
          • 可達(dá)性分析算法
        • 2. 垃圾回收算法
          • 標(biāo)記清除法
          • 標(biāo)記整理法
          • 復(fù)制算法
        • 3. 分代垃圾回收機(jī)制
        • 4. 垃圾回收器
        • 5. 四種引用
    • 類加載
        • 類加載器的分類
        • 類加載過程
        • LoadClass和forName的區(qū)別
        • 雙親委派機(jī)制
        • 自定義類加載器
    • 反射機(jī)制
        • 反射的定義
        • 反射的常用場景
        • 反射相關(guān)的類
          • Class類:
          • Filed類
          • Method類
          • Constructor類
        • 案例
  • Java內(nèi)存模型
      • 什么是Java內(nèi)存模型(JMM)
      • 為什么會有Java內(nèi)存模型
      • 原子性
      • 可見性
      • 有序性(重排序)
      • volatile
      • happens-before規(guī)則

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

JVM構(gòu)成

說到JVM內(nèi)存結(jié)構(gòu),就不會只是說內(nèi)存結(jié)構(gòu)的5個分區(qū),而是會延展到整個JVM相關(guān)的問題,所以先了解下JVM的構(gòu)成。

  • Java源代碼編譯成Java Class文件后通過類加載器ClassLoader加載到JVM中
    • 類存放在方法區(qū)
    • 類創(chuàng)建的對象存放在
    • 堆中對象的調(diào)用方法時會使用到虛擬機(jī)棧,本地方法棧,程序計數(shù)器
    • 方法執(zhí)行時每行代碼由解釋器逐行執(zhí)行
    • 熱點(diǎn)代碼由JIT編譯器即時編譯
    • 垃圾回收機(jī)制回收堆中資源
    • 和操作系統(tǒng)打交道需要調(diào)用本地方法接口

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

程序計數(shù)器


(通過移位寄存器實(shí)現(xiàn))

  • 程序計數(shù)器是線程私有的,每個線程單獨(dú)持有一個程序計數(shù)器
  • 程序計數(shù)器不會內(nèi)存溢出

虛擬機(jī)棧

  • 棧:線程運(yùn)行需要的內(nèi)存空間

  • 棧幀:每一個方法運(yùn)行需要的內(nèi)存(包括參數(shù),局部變量,返回地址等信息)

  • 每個線程只有一 個活動棧幀(棧頂?shù)臈?#xff09;,對應(yīng)著正在執(zhí)行的代碼

  • 常見問題解析

    • 垃圾回收是否涉及棧內(nèi)存:不涉及,垃圾回收只涉及堆內(nèi)存

    • 棧內(nèi)存分配越大越好嗎:內(nèi)存一定時,棧內(nèi)存越大,線程數(shù)就越少,所以不應(yīng)該過大

    • 方法內(nèi)的局部變量是否是線程安全的:

      • 普通局部變量是安全的
      • 靜態(tài)的局部變量是不安全的
      • 對象類型的局部變量被返回了是不安全的
      • 基本數(shù)據(jù)類型局部變量被返回時安全的
      • 參數(shù)傳入對象類型變量是不安全的
      • 參數(shù)傳入基本數(shù)據(jù)類型變量時安全的
    • 棧內(nèi)存溢出(StackOverflowError)

      • 棧幀過多

        • 如遞歸調(diào)用沒有正確設(shè)置結(jié)束條件
      • 棧幀過大

        • json數(shù)據(jù)轉(zhuǎn)換 對象嵌套對象 (用戶類有部門類屬性,部門類由用戶類屬性)
      • 線程運(yùn)行診斷

        • CPU占用過高(定位問題)

          • ‘top’命令獲取進(jìn)程編號,查找占用高的進(jìn)程
          • ‘ps H -eo pid,tid,%cpu | grep 進(jìn)程號’ 命令獲取線程的進(jìn)程id,線程id,cpu占用
          • 將查看到的占用高的線程的線程號轉(zhuǎn)化成16進(jìn)制的數(shù) :如6626->19E2
          • ‘ jstack 進(jìn)程id ’獲取進(jìn)程棧信息, 查找‘nid=0X19E2’的線程
          • 問題線程的最開始‘#數(shù)字’表示出現(xiàn)問題的行數(shù),回到代碼查看
        • 程序運(yùn)行很長時間沒有結(jié)果(死鎖問題)

          • ‘ jstack 進(jìn)程id ’獲取進(jìn)程棧信息
          • 查看最后20行左右有無‘Fount one Java-level deadlock’
          • 查看下面的死鎖的詳細(xì)信息描述和問題定位
          • 回到代碼中定位代碼進(jìn)行解決

本地方法棧

  • 本地方法棧為虛擬機(jī)使用到的 Native 方法服務(wù)
  • Native 方法是 Java 通過 JNI 直接調(diào)用本地 C/C++ 庫,可以認(rèn)為是 Native 方法相當(dāng)于 C/C++ 暴露給 Java 的一個接口
  • 如notify,hashcode,wait等都是native方法

  • 通過new關(guān)鍵字創(chuàng)建的對象都會使用堆內(nèi)存

  • 堆是線程共享的

  • 堆中有垃圾回收機(jī)制

  • 堆內(nèi)存溢出(OutOfMemoryError)

    • 死循環(huán)創(chuàng)建對象
  • 堆內(nèi)存診斷

    • 命令行方式

      • ‘jps’獲取運(yùn)行進(jìn)程號
      • ‘jmap -heap 進(jìn)程號’查看當(dāng)前時刻的堆內(nèi)存信息
    • jconsole

      • 命令行輸入jconsole打開可視化的界面連接上進(jìn)程
      • 可視化的檢測連續(xù)的堆內(nèi)存信息
    • jvisualvm

      • 命令行輸入jvisualvm打開可視化界面選擇進(jìn)程
      • 可視化的查看堆內(nèi)存信息

方法區(qū)

  • 方法區(qū)只是一種概念上的規(guī)范,具體的實(shí)現(xiàn)各種虛擬機(jī)和不同版本不相同
    • HotSpot1.6 使用永久代作為方法區(qū)的實(shí)現(xiàn)
    • HotSpot1.8使用本地內(nèi)存的元空間作為方法區(qū)的實(shí)現(xiàn)(但StringTable還是放在堆中)
  • 常見問題
    • StringTable特性

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

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

      • 字符串常量拼接原理是StringBuilder(1.8)

      • 字符串常量拼接原理是編譯器優(yōu)化

      • StringTable在1.6中存放在永久代,在1.8中存放在堆空間

      • intern方法主動將串池中沒有的字符串對象放入串池

        • 1.8中:嘗試放入串池,如果有就不放入,只返回一個引用;如果沒有就放入串池,同時返回常量池中對象引用

        • 1.6中:嘗試放入串池,如果有就不放入,只返回一個引用;如果沒有就復(fù)制一個放進(jìn)去(本身不放入),同時返回常量池中的對象引用

        • 字符串常量池分析(1.8環(huán)境)

          String s1 = "a"; String s2 = "b"; String s3 = "a"+"b"; String s4 = s1+s2; String s5 = "ab"; String s6 = s4.intern();System.out.println(s3==s4);// s3在常量池中,s4在堆上(intern嘗試s4放入常量池,因?yàn)閍b存在了就拒絕放入返回ab引用給s6,s4還是堆上的) System.out.println(s3==s5);// s3在常量池中,s4也在常量池中(字符串編譯期優(yōu)化) System.out.println(s3==s6);// s3在常量池中,s6是s4的intern返回常量池中ab的引用,所以也在常量池中String x2 = new String("c")+new String("d"); String x1 = "cd"; x2.intern();System.out.println(x1==x2);//x2調(diào)用intern嘗試放入常量池,但常量池中已經(jīng)有cd了,所以只是返回一個cd的引用,而x2還是堆上的引用
    • JVM調(diào)優(yōu)三大參數(shù)(如: java -Xms128m -Xmx128m -Xss256k -jar xxxx.jar)

      • -Xss:規(guī)定了每個線程虛擬機(jī)棧的大小(影響并發(fā)線程數(shù)大小)
      • -Xms:堆大小的初始值(超過初始值會擴(kuò)容到最大值)
      • -Xmx:堆大小的最大值(通常初始值和最大值一樣,因?yàn)閿U(kuò)容會導(dǎo)致內(nèi)存抖動,影響程序運(yùn)行穩(wěn)定性)
    • JVM內(nèi)存結(jié)構(gòu)中堆和棧的區(qū)別

      • 管理方式:棧自動釋放,堆需要GC
      • 空間大小:棧比堆小
      • 碎片:棧產(chǎn)生的碎片遠(yuǎn)少于堆
      • 分配方式:棧支持靜態(tài)分配和動態(tài)分配,堆只支持動態(tài)分配
      • 效率:棧的效率比堆高

GC垃圾回收機(jī)制

1. 垃圾判別方法

引用計數(shù)算法
  • 判斷對象的引用數(shù)量來決定對象是否可以被回收

  • 每個對象實(shí)例都有一個引用計數(shù)器,被引用則+1,完成引用則-1

  • 優(yōu)點(diǎn):執(zhí)行效率高,程序執(zhí)行受影響小

  • 缺點(diǎn):無法檢測出循環(huán)引用的情況,導(dǎo)致內(nèi)存泄露

可達(dá)性分析算法
  • Java虛擬機(jī)中的垃圾回收器采用可達(dá)性分析來探索所有存活對象

  • 掃描堆中的對象,看是否能沿著GC Root對象為起點(diǎn)的引用鏈找到該對象,找不到則可以回收

  • 哪些對象可以作為GC Root

  • 通過System Class Loader或者Boot Class Loader加載的class對象,通過自定義類加載器加載的class不一定是GC Root

    • 虛擬機(jī)棧中的引用的對象

    • 本地方法棧中JNI(natice方法)的引用的對象

    • 方法區(qū)中的常量引用的對象

    • 方法區(qū)中的類靜態(tài)屬性引用的對象

    • 處于激活狀態(tài)的線程

    • 正在被用于同步的各種鎖對象

    • GC保留的對象,比如系統(tǒng)類加載器等。

2. 垃圾回收算法

標(biāo)記清除法
  • 標(biāo)記沒有被GC Root引用的對象
  • 清除被標(biāo)記位置的內(nèi)存
  • 優(yōu)點(diǎn):處理速度快
  • 缺點(diǎn):造成空間不連續(xù),產(chǎn)生內(nèi)存碎片
標(biāo)記整理法
  • 標(biāo)記沒有被GC Root引用的對象
  • 整理被引用的對象
  • 優(yōu)點(diǎn):空間連續(xù),沒有內(nèi)存碎片
  • 缺點(diǎn):整理導(dǎo)致效率較低

復(fù)制算法
  • 分配同等大小的內(nèi)存空間
  • 標(biāo)記被GC Root引用的對象
  • 將引用的對象連續(xù)的復(fù)制到新的內(nèi)存空間
  • 清除原來的內(nèi)存空間
  • 交換FROM空間和TO空間
  • 優(yōu)點(diǎn):空間連續(xù),沒有內(nèi)存碎片
  • 缺點(diǎn):占用雙倍的內(nèi)存空間


3. 分代垃圾回收機(jī)制

  • 分代垃圾回收流程

    • 對象首先分配在伊甸園區(qū)域
    • 新生代空間不足時,觸發(fā)Minor GC,伊甸園和from存活的對象使用【復(fù)制算法】復(fù)制到to中,存活的對象年齡加一,并且交換from區(qū)和to區(qū)
    • Minor GC會引發(fā)Stop the world(STW)現(xiàn)象,暫停其他用戶的線程。垃圾回收結(jié)束后,用戶線程才恢復(fù)運(yùn)行
    • 當(dāng)對象壽命超過閾值時,會晉升至老年代,最大壽命是15(4位二進(jìn)制)
    • 當(dāng)老年代空間不足,會先嘗試觸發(fā)Minor GC,如果之后空間仍不足,會觸發(fā)Full GC(STW時間更長,老年代可能使用標(biāo)簽清除或標(biāo)記整理算法)
    • 當(dāng)存放大對象新生代放不下而老年代可以放下,大文件會直接晉升到老年代
    • 當(dāng)存放大對象新生代和老年代都放不下時,拋出OOM異常
  • 默認(rèn)堆內(nèi)存分配

    • 新生代占1/3,老年代占2/3
    • -XX:NewRatio:老年代和年輕代內(nèi)存大小的比例
    • 新生代中按8 1 1進(jìn)行分配,兩個幸存區(qū)大小需要保持一致
    • -XX:SurvivorRatio: Eden和Survivor的比值,默認(rèn)是8(8:1)
  • GC相關(guān)VM參數(shù)

4. 垃圾回收器

  • 安全點(diǎn)(SafePoint)

    • 分析過程中對象引用關(guān)系不會發(fā)生改變的點(diǎn)

    • 產(chǎn)生安全點(diǎn)的地方:

      • 方法調(diào)用
      • 循環(huán)跳轉(zhuǎn)
      • 異常跳轉(zhuǎn)
    • 安全點(diǎn)的數(shù)量應(yīng)該設(shè)置適中

  • 串行(SerialGC)

    • 單線程的垃圾回收器
    • 堆內(nèi)存較小,CPU核數(shù)少,適合個人電腦
    • SerialGC收集器 (-XX:+UseSerialGC 復(fù)制算法) Client模式下默認(rèn)的年輕代收集器
    • SerialGC Old收集器 (-XX:+UseSerialOldGC 標(biāo)記-整理算法)Client模式下默認(rèn)的老年代收集器
  • 吞吐量優(yōu)先(ParallelGC)

    • 多線程的垃圾回收器
    • 堆內(nèi)存較大,多核CPU,適合服務(wù)器
    • 盡可能讓單位時間內(nèi)STW暫停時間最短(吞吐量=運(yùn)行代碼時間/(運(yùn)行代碼時間+垃圾回收時間))
    • 并行的執(zhí)行
    • ParallelGC收集器(-XX:+UseParallelGC 復(fù)制算法) Server模式下默認(rèn)的年輕代垃圾回收器
    • ParallelGC Old收集器(-XX:+UseParallelOldGC 復(fù)制算法)

  • 響應(yīng)時間優(yōu)先(CMS -XX:+UseConcMarkSweepGC 標(biāo)記清除算法)

    • 多線程的垃圾回收器

    • 堆內(nèi)存較大,多核CPU,Server模式下默認(rèn)的老年代垃圾回收器

    • 盡可能讓單次STW暫停時間最短

    • 部分時期內(nèi)可以并發(fā)執(zhí)行

    • 執(zhí)行流程

      • 初始標(biāo)記:stop-the-world
      • 并發(fā)標(biāo)記:并發(fā)追溯標(biāo)記,程序不會停頓
      • 并發(fā)預(yù)清理:查找執(zhí)行并發(fā)標(biāo)記階段從年輕代晉升到老年代的對象
      • 重新標(biāo)記:暫停虛擬機(jī),掃描CMS堆中的剩余對象
      • 并發(fā)清理:清理垃圾對象,程序不會停頓
      • 并發(fā)重置:重置CMS收集器的數(shù)據(jù)結(jié)構(gòu)

  • G1(-XX:+UseG1GC 復(fù)制+標(biāo)記清除算法)

    • G1l垃圾回收器簡介
    • 定義:Garbage First (2017 jdk9 默認(rèn))
    • 特點(diǎn)
      • 并發(fā)和并行
      • 分代收集
      • 空間整合
      • 可預(yù)測的停頓
    • 使用場景
      • 同時注重吞吐量和低延遲,默認(rèn)暫停目標(biāo)是200ms
      • 超大堆內(nèi)存,會將整個堆劃分為多個大小相等的Region(新生代和老年代不再物理隔離了)
      • 整體上是標(biāo)記整理算法,兩個區(qū)域之間是復(fù)制算法
  • 垃圾回收階段

    • 新生代垃圾收集

      • 會發(fā)生STW
    • 新生代垃圾收集+并發(fā)標(biāo)記

      • 在Young GC時會進(jìn)行GC Root的初始標(biāo)記
      • 老年代占用堆內(nèi)存空間比例達(dá)到閾值時,進(jìn)行并發(fā)標(biāo)記(不會STW)
    • 混合收集,對新生代,幸存區(qū)和老年代都進(jìn)行收集

      • 最終標(biāo)記,會STW
      • 拷貝存活,會STW
      • 三種階段循環(huá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)存不足

        • 并發(fā)收集成功:并發(fā)的垃圾收集
        • 并發(fā)收集失敗:串行的full GC
    • G1

      • 新生代內(nèi)存不足發(fā)生的垃圾收集:minor GC

      • 老年代內(nèi)存不足,達(dá)到閾值時進(jìn)入并發(fā)標(biāo)記和混合收集階段

        • 如果回收速度>新產(chǎn)生垃圾的速度 :并發(fā)垃圾收集
        • 如果回收速度<新產(chǎn)生垃圾的速度:串行的full GC

5. 四種引用

  • 強(qiáng)引用

    • 最常見的對象:通過new關(guān)鍵字創(chuàng)建,通過GC Root能找到的對象。
    • 當(dāng)所有的GC Root都不通過【強(qiáng)引用】引用該對象時,對象才能被垃圾回收
  • 軟引用

    • 僅有【軟引用】引用該對象時,在垃圾回收后,內(nèi)存仍不足時會再次發(fā)起垃圾回收,回收軟引用對象

    • 可以配合引用隊列來釋放軟引用自身

    • 創(chuàng)建一個軟引用:SoftReference ref = new SoftReference<>(new Object());

    • 軟引用被回收后,仍然還保留一個null,如將軟引用加入集合,回收后遍歷集合仍然還存在一個null

      • 解決:使用引用隊列,軟引用關(guān)聯(lián)的對象被回收時,軟引用自身會被加入到引用隊列中,通過queue.poll()取得對象進(jìn)行刪除
      • 創(chuàng)建一個而引用隊列:ReferenceQueue queue = new ReferenceQueue<>();
      • 創(chuàng)建加入了引用隊列的軟引用:SoftReference ref = new SoftReference<>(new Object(),queue);
  • 弱引用

    • 僅有【弱引用】引用該對象時,在垃圾回收時,無論內(nèi)存是否充足,都會回收弱引用對象
    • 可以配合引用隊列來釋放弱引用自身
    • 創(chuàng)建一個弱引用:WeakReference ref = new WeakReference<>(new Object());
    • 引用隊列使用同軟引用
  • 虛引用

    • 必須配合引用隊列使用,主要配合ByteBuffer使用,被引用對象回收時,會將【虛引用】入隊,由Reference Hanler線程調(diào)用虛引用相關(guān)方法釋放【直接內(nèi)存】(unsafe類中方法)
  • 終結(jié)器引用

    • 無需手動編碼,但其內(nèi)部配合引用隊列使用,在垃圾回收時,終結(jié)器引用隊列入隊(引用對象暫未回收),再由Finalizer線程通過終結(jié)器引用找到被引用對象并調(diào)用他的finalize方法,第二次gc時回收被引用對象

類加載

類加載器的分類

類加載過程

  • 加載

    • 通過ClassLoader加載Class文件字節(jié)碼,生成Class對象
  • 鏈接

    • 校驗(yàn):檢查加載的的Class的正確性和安全性

    • 準(zhǔn)備:為類變量分配存儲空間并設(shè)置類變量初始值

    • 解析:JVM將常量池內(nèi)的符號引用轉(zhuǎn)換為直接引用

  • 初始化

    • 執(zhí)行類變量賦值和靜態(tài)代碼塊

LoadClass和forName的區(qū)別

  • Class.ForName得到的class是已經(jīng)初始化完成的
  • ClassLoader.loadClass得到的class是還沒有鏈接的

雙親委派機(jī)制

  • 什么是雙親委派機(jī)制
    • 當(dāng)某個類加載器需要加載某個.class文件時,它首先把這個任務(wù)委托給他的上級類加載器,遞歸這個操作,如果上級的類加載器沒有加載,自己才會去加載這個類。
  • 為什么要使用雙親委派機(jī)制
    • 防止重復(fù)加載同一個.class文件,通過委托去向上級問,加載過了就不用加載了。
    • 保證核心.class文件不會被串改,即使篡改也不會加載,即使加載也不會是同一個對象,因?yàn)椴煌虞d器加載同一個.class文件也不是同一個class對象,從而保證了class執(zhí)行安全

自定義類加載器

  • 需求場景

    • 想要加載非classpath的隨意路徑的類文件
    • 通過接口來使用實(shí)現(xiàn),希望解耦合
  • 步驟

    • 繼承Classloader父類
    • 遵循雙親委派機(jī)制,重寫findClass方法(不能重寫loadClass,重寫了就不符合雙親委派了)
    • 讀取類的字節(jié)碼
    • 調(diào)用父類的defineClass方法加載類
    • 使用者調(diào)用類加載的loadClass方法
  • 案例演示

創(chuàng)建自定義類加載器

public class MyClassLoader extends ClassLoader {private String path;private String classLoaderName;public MyClassLoader(String path, String classLoaderName) {this.path = path;this.classLoaderName = classLoaderName;}//用于尋找類文件@Overridepublic Class findClass(String name) {byte[] b = loadClassData(name);return defineClass(name, b, 0, b.length);}//用于加載類文件private byte[] loadClassData(String name) {name = path + name + ".class";try (InputStream in = new FileInputStream(new File(name));ByteArrayOutputStream out = new ByteArrayOutputStream();) {int i = 0;while ((i = in.read()) != -1) {out.write(i);}return out.toByteArray();} catch (Exception e) {e.printStackTrace();}return null;} }

調(diào)用自定義類加載器加載類

public class MyClassLoaderChecker {public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException {MyClassLoader m = new MyClassLoader("C:\\Users\\73787\\Desktop\\","myClassLoader");Class<?> c = m.loadClass("Robot");System.out.println(c.getClassLoader());c.newInstance();} }

反射機(jī)制

反射的定義

JAVA反射機(jī)制是在運(yùn)行狀態(tài)中,對于任意一個類,都能夠知道這個類的所有屬性和方法;對于任意一個對象,都能夠調(diào)用它的任意方法和屬性;這種動態(tài)獲取信息以及動態(tài)調(diào)用對象方法的功能稱為java語言的反射機(jī)制。

反射的常用場景

第三方應(yīng)用開發(fā)過程中,會需要某個類的某個成員變量、方法或是屬性是私有的或者只對系統(tǒng)應(yīng)用開放,就可以通過Java的反射機(jī)制來獲取所需的私有成員或者方法

反射相關(guān)的類

Class類:

代表類的實(shí)體,在運(yùn)行的Java應(yīng)用程序中表示類和接口

  • 獲得類的方法

  • 獲得類中屬性的方法

  • 獲得類中方法的方法
  • 獲取類中構(gòu)造器的方法
Filed類

Filed代表類的成員變量(屬性)

Method類

Constructor類

案例

定義一個Robot類

public class Robot {//私有屬性private String name;//公有方法public void sayHi(String hello){System.out.println(hello+" "+name);}//私有方法private String thorwHello(String tag){return "hello "+tag;} }

編寫一個反射應(yīng)用類,針對私有的屬性和方法必須設(shè)置setAccessible(true)才能進(jìn)行訪問

public class ReflectSample {public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {//加載類Class<?> rc = Class.forName("leetcode.Robot");//獲取類實(shí)例Robot r = (Robot)rc.newInstance();//打印類名System.out.println(rc.getName());//加載一個私有方法Method getHello = rc.getDeclaredMethod("thorwHello",String.class);getHello.setAccessible(true);Object bob = getHello.invoke(r, "bob");System.out.println(bob);//加載一個公有方法Method sayHi = rc.getMethod("sayHi",String.class);Object welcome = sayHi.invoke(r,"welcome");//加載一個私有屬性Field name = rc.getDeclaredField("name");name.setAccessible(true);name.set(r,"tom");sayHi.invoke(r,"welcome");} }

Java內(nèi)存模型

什么是Java內(nèi)存模型(JMM)

  • 通俗來說,JMM是一套多線程讀寫共享數(shù)據(jù)時,對數(shù)據(jù)的可見性,有序性和原子性的規(guī)則

為什么會有Java內(nèi)存模型

JVM實(shí)現(xiàn)不同會造成“翻譯”的效果不同,不同CPU平臺的機(jī)器指令有千差萬別,無法保證同一份代碼并發(fā)下的效果一致。所以需要一套統(tǒng)一的規(guī)范來約束JVM的翻譯過程,保證并發(fā)效果一致性

原子性

  • 什么是原子性
    • 原子性指一系列的操作,要么全部執(zhí)行成功,要么全部不執(zhí)行,不會出現(xiàn)執(zhí)行一半的情況,是不可分的。
  • 原子性怎么實(shí)現(xiàn)
    • 使用synchronized或Lock加鎖實(shí)現(xiàn),保證任一時刻只有一個線程訪問該代碼塊
    • 使用原子操作
  • Java中的原子操作有哪些
    • 除long和double之外的基本類型的賦值操作(64位值,當(dāng)成兩次32位的進(jìn)行操作)
    • 所有引用reference的賦值操作
    • java.concurrent.Atomic.*包中所有類的原子操作
  • 創(chuàng)建對象的過程是否是原子操作(常應(yīng)用于雙重檢查+volatile創(chuàng)建單例場景)
    • 創(chuàng)建對象實(shí)際上有3個步驟,并不是原子性的
      • 創(chuàng)建一個空對象
      • 調(diào)用構(gòu)造方法
      • 創(chuàng)建好的實(shí)例賦值給引用

可見性

  • 什么是可見性問題
    • 可見性指的是當(dāng)一個線程修改了某個共享變量的值,其他線程是否能夠馬上得知這個修改的值。
  • 為什么會有可見性問題、
    • 對于單線程程序來說,可見性是不存在的,因?yàn)槲覀冊谌魏我粋€操作中修改了某個變量的值,后續(xù)的操作中都能讀取這個變量值,并且是修改過的新值。
    • 對于多線程程序而言。由于線程對共享變量的操作都是線程拷貝到各自的工作內(nèi)存進(jìn)行操作后才寫回到主內(nèi)存中的,這就可能存在一個線程A修改了共享變量x的值,還未寫回主內(nèi)存時,另外一個線程B又對主內(nèi)存中同一個共享變量x進(jìn)行操作,但此時A線程工作內(nèi)存中共享變量x對線程B來說并不可見,這種工作內(nèi)存與主內(nèi)存同步延遲現(xiàn)象就造成了可見性問題
  • 如何解決可見性問題
    • 解決方法1:加volatile關(guān)鍵字保證可見性。當(dāng)一個共享變量被volatile修飾時,它會保證修改的值立即被其他的線程看到,即修改的值立即更新到主存中,當(dāng)其他線程需要讀取時,它會去內(nèi)存中讀取新值
    • 解決方法2:使用synchronized和Lock保證可見性。因?yàn)樗鼈兛梢员WC任一時刻只有一個線程能訪問共享資源,并在其釋放鎖之前將修改的變量刷新到內(nèi)存中
  • 案例
/** * 〈可見性問題分析〉 * * @author Chkl * @create 2020/3/4 * @since 1.0.0 */ public class FieldVisibility {int a = 1;int b = 2;private void change() {a = 3;b = a;}private void print() {System.out.println("b=" + b + ";a=" + a);}public static void main(String[] args) {while (true) {FieldVisibility test = new FieldVisibility();new Thread(new Runnable() {@Overridepublic void run() {try {Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}test.change();}}).start();new Thread(new Runnable() {@Overridepublic void run() {try {Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}test.print();}}).start();}} }

循環(huán)創(chuàng)建兩類線程,一個線程用于做值的交換,一個線程用于打印值

比較直觀的三種結(jié)果

  • 打印線程先執(zhí)行:b = 2, a = 1
  • 交換線程先執(zhí)行:b = 3, a = 3
  • 交換線程執(zhí)行到一半就切出去打印了,只執(zhí)行了a=3賦值操作:b = 2 , a =3

實(shí)際上除了很容易想到的三種情況外還有一種特殊情況:b = 3 , a = 1

  • 這種情況就是可見性問題
  • a的值在線程1(執(zhí)行交換線程)的本地緩存中進(jìn)行了更新,但是并沒有同步到共享緩存,而b的值成功的更新到了共享緩存,導(dǎo)致線程2(執(zhí)行打印線程)從共享緩存中獲取到的數(shù)據(jù)并不是實(shí)時的最新數(shù)據(jù)
    -

有序性(重排序)

  • 什么是重排序
    • 在線程內(nèi)部的兩行代碼的實(shí)際執(zhí)行順序和代碼在Java文件中的邏輯順序不一致,代碼指令并不是嚴(yán)格按照代碼語句順序執(zhí)行的,他們的順序被改變了,這就是重排序。
  • 重排序的意義
    • JVM能根據(jù)處理器特性(CPU多級緩存系統(tǒng)、多核處理器等)適當(dāng)?shù)膶C(jī)器指令進(jìn)行重排序,使機(jī)器指令能更符合CPU的執(zhí)行特性,最大限度的發(fā)揮機(jī)器性能。
    • 案例計算: a = 3; b = 2; a = a + 1; 重排序優(yōu)化前的instructionsload a set to 3 store 3load b set to 2 store bload a set to 4 store a經(jīng)過重排序處理后load a set to 3 set to 4 store aload b set to 2 store b上述少了兩個指令,優(yōu)化了性能
  • 重排序的3種情況
    • 編譯器優(yōu)化( JVM,JIT編輯器等): 編譯器在不改變單線程程序語義放入前提下,可以重新安排語句的執(zhí)行順序
    • 指令級并行的重排序:現(xiàn)代處理器采用了指令級并行技術(shù)來將多條指令重疊執(zhí)行。如果不存在數(shù)據(jù)依賴性,處理器可以改變語句對應(yīng)機(jī)器指令的執(zhí)行順序。
    • 內(nèi)存系統(tǒng)的重排序: 由于處理器使用緩存和讀寫緩沖區(qū),這使得加載和存儲操作看上去可能是在亂序執(zhí)行。

volatile

  • 什么是volatile

    • volatile是一種同步機(jī)制,比synchronized或者Lock相關(guān)類更輕量級,因?yàn)槭褂胿olacile并不會發(fā)生上下文切換等開銷很大的行為
    • volatile是無鎖的,并且只能修飾單個屬性
  • 什么時候適合用vilatile

    • 一個共享變量始終只被各個線程賦值,沒有其他操作
    • 作為刷新的觸發(fā)器,引用刷新之后使修改內(nèi)容對其他線程可見(如CopyOnRightArrayList底層動態(tài)數(shù)組通過volatile修飾,保證修改完成后通過引用變化觸發(fā)volatile刷新,使其他線程可見)
  • volatile的作用

    • 可見性保障:修改一個volatile修飾變量之后,會立即將修改同步到主內(nèi)存,使用一個volatile修飾的變量之前,會立即從主內(nèi)存中刷新數(shù)據(jù)。保證讀取的數(shù)據(jù)都是最新的,之前的修改都是可見的。
    • 有序性保障(禁止指令重排序優(yōu)化):有volatile修飾的變量,賦值后多了一個“內(nèi)存屏障“( 指令重排序時不能把后面的指令重排序到內(nèi)存屏障之前的位置)
  • volatile的性能

    • volatile 的讀性能消耗與普通變量幾乎相同,但是寫操作稍慢,因?yàn)樗枰诒镜卮a中插入許多內(nèi)存屏障指令來保證處理器不發(fā)生亂序執(zhí)行。

happens-before規(guī)則

什么是happens-before規(guī)則:前一個操作的結(jié)果可以被后續(xù)的操作獲取。

  • 程序的順序性規(guī)則:在一個線程內(nèi)一段代碼的執(zhí)行結(jié)果是有序的。雖然還會指令重排,但是隨便它怎么排,結(jié)果是按照我們代碼的順序生成的不會變!
  • volatile規(guī)則: 就是如果一個線程先去寫一個volatile變量,然后一個線程去讀這個變量,那么這個寫操作的結(jié)果一定對讀的這個線程可見。
  • 傳遞性規(guī)則:happens-before原則具有傳遞性,即A happens-before B , B happens-before C,那么A happens-before C。
  • 管程鎖定規(guī)則:無論是在單線程環(huán)境還是多線程環(huán)境,對于同一個鎖來說,一個線程對這個鎖解鎖之后,另一個線程獲取了這個鎖都能看到前一個線程的操作結(jié)果!(管程是一種通用的同步原語,synchronized就是管程的實(shí)現(xiàn))
  • 線程啟動規(guī)則:在主線程A執(zhí)行過程中,啟動子線程B,那么線程A在啟動子線程B之前對共享變量的修改結(jié)果對線程B可見。
  • 線程終止規(guī)則: 在主線程A執(zhí)行過程中,子線程B終止,那么線程B在終止之前對共享變量的修改結(jié)果在線程A中可見。
  • 線程中斷規(guī)則: 對線程interrupt()方法的調(diào)用先行發(fā)生于被中斷線程代碼檢測到中斷事件的發(fā)生,可以通過Thread.interrupted()檢測到是否發(fā)生中斷。
  • 對象終結(jié)規(guī)則:一個對象的初始化的完成,也就是構(gòu)造函數(shù)執(zhí)行的結(jié)束一定 happens-before它的finalize()方法。

如果有用,點(diǎn)個贊再走吧

總結(jié)

以上是生活随笔為你收集整理的JVM内存结构和Java内存模型别再傻傻分不清了的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

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