<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          JVM說---直接內(nèi)存的使用

          共 8771字,需瀏覽 18分鐘

           ·

          2023-05-07 08:37

          前言:學習底層原理有的時候不一定你是要用到他,而是學習他們的設計思想和思路。再或者,當你在日常工作中遇到棘手的問題時候,可以拓展解決問題的思路

          分享大綱:本次分享主要由io與nio讀取文件速度差異的情況,去了解nio為什么讀取大文件的時候效率較高,查看nio是如何使用直接內(nèi)存的,再深入到如何使用直接內(nèi)存


          1c795e712a5ebb624d05970ae3e4ef3c.webp

          • JVM說---直接內(nèi)存的使用

            • 1.新建虛引用

            • 2.聲明清理緩存任務

            • 3.ReferenceHandler進行調(diào)用

            • 1.nio與io讀寫文件的效率比對

            • 2.直接內(nèi)存的讀寫性能強的原理

            • 3.nio使用直接內(nèi)存的源碼解讀

            • 4.直接內(nèi)存的使用方式

            • 5.總結


          1.nio與io讀寫文件的效率比對

          首先上代碼,有興趣的同學可以將代碼拿下來進行調(diào)試查看

          主函數(shù)調(diào)用

          為排除當前環(huán)境不同導致的文件讀寫效率不同問題,使用多線程分別調(diào)用io方法和nio方法

          c663ab05374c77d3824076ff182e96e0.webp

          分別進行IO調(diào)用和NIO調(diào)用

          通nio和io的讀取寫入文件方式進行操作

          29bc3a8d0e30c75e33d126ea5a698a9e.webp

          結果

          經(jīng)過多次測試后,發(fā)現(xiàn)nio讀取文件的效率是高于io的,尤其是讀取大文件的時候

                
                  11:12:26.606 [Thread-1] INFO com.lzl.netty.study.jvm.DirectBufferTest - StopWatch '': running time (millis) = 1157
                
                
                  
                    -----------------------------------------
                  
                
                
                  ms     %     Task name
                
                
                  
                    -----------------------------------------
                  
                
                
                  01157  100%  nioDirectTimeWatch
                
                
                  
                    
          11:12:27.146 [Thread-0] INFO com.lzl.netty.study.jvm.DirectBufferTest - StopWatch '': running time (millis) = 1704 ----------------------------------------- ms % Task name ----------------------------------------- 01704 100% ioTimeWatch

          提出疑問

          那到底為什么nio的速度要快于普通的io呢,結合源碼查看以及網(wǎng)上的資料,核心原因是:

          • nio讀取文件的時候,使用直接內(nèi)存進行讀取

          那么,如果在nio中也不使用直接內(nèi)存的話,會是什么情況呢?

          再次驗證

          新增使用堆內(nèi)存讀取文件

          8764bd5ee27f5b005e023321e241c46f.webp

          執(zhí)行時間驗證如下:

                
                  11:30:35.050 [Thread-1] INFO com.lzl.netty.study.jvm.DirectBufferTest - StopWatch '': running time (millis) = 2653
                
                
                  
                    -----------------------------------------
                  
                
                
                  ms     %     Task name
                
                
                  
                    -----------------------------------------
                  
                
                
                  02653  100%  nioDirectTimeWatch
                
                
                  
                    
          11:30:35.399 [Thread-2] INFO com.lzl.netty.study.jvm.DirectBufferTest - StopWatch '': running time (millis) = 3038 ----------------------------------------- ms % Task name ----------------------------------------- 03038 100% nioHeapTimeWatch
          11:30:35.457 [Thread-0] INFO com.lzl.netty.study.jvm.DirectBufferTest - StopWatch '': running time (millis) = 3096 ----------------------------------------- ms % Task name ----------------------------------------- 03096 100% ioTimeWatch

          根據(jù)上述的實際驗證,nio讀寫文件比較快的主要原因還是在于使用了直接內(nèi)存,那么為什么會出現(xiàn)這種情況呢?

          2.直接內(nèi)存的讀寫性能強的原理

          直接上圖說明

          836321e0c754b2c9e4666ddab2498720.webp


          堆內(nèi)存讀寫文件的步驟:

          當JVM想要去和磁盤進行交互的時候,因為JVM和操作系統(tǒng)之間存在讀寫屏障,所以在進行數(shù)據(jù)交互的時候需要進行頻繁的復制

          • 先由操作系統(tǒng)進行磁盤的讀取,將讀取數(shù)據(jù)放入系統(tǒng)內(nèi)存緩沖區(qū)中

          • JVM與系統(tǒng)內(nèi)存緩沖區(qū)進行數(shù)據(jù)拷貝

          • 應用程序再到JVM的堆內(nèi)存空間中進行數(shù)據(jù)的獲取


          1093e7fed19f9ca03aadb0f7fafb5f39.webp

          直接內(nèi)存讀寫文件的步驟

          如果使用直接內(nèi)存進行文件讀取的時候,步驟如下

          • 會直接調(diào)用native方法allocateMemory進行直接內(nèi)存的分配

          • 操作系統(tǒng)將文件讀取到這部分的直接內(nèi)存中

          • 應用程序可以通過JVM堆空間的DirectByteBuffer進行讀取

          與使用對堆內(nèi)存讀寫文件的步驟相比減少了數(shù)據(jù)拷貝的過程,避免了不必要的性能開銷,因此NIO中使用了直接內(nèi)存,對于性能提升很多

          那么,直接內(nèi)存的使用方式是什么樣的呢?

          3.nio使用直接內(nèi)存的源碼解讀

          在閱讀源碼之前呢,我們首先對于兩個知識進行補充,
          虛引用Cleanersun.misc.Cleaner

          什么是虛引用

          • 虛引用所引用的對象,永遠不會被回收,除非指向這個對象的所有虛引用都調(diào)用了clean函數(shù),或者所有這些虛引用都不可達

          • 必須關聯(lián)一個引用隊列

          Cleaner繼承自虛引用PhantomReference,關聯(lián)引用隊列ReferenceQueue<Object>

          f2fe01bfb4bbc4dc715c2b7502d7f2da.webp

          概述的說一下,他的作用就是,JVM會將其對應的Cleaner加入到pending-Reference鏈表中,同時通知ReferenceHandler線程處理,ReferenceHandler收到通知后,會調(diào)用Cleaner#clean方法

          Unsafesun.misc.Unsafe?

          位于sun.misc包下的一個類,主要提供一些用于執(zhí)行低級別、不安全操作的方法,如直接訪問系統(tǒng)內(nèi)存資源、自主管理內(nèi)存資源等,這些方法在提升Java運行效率、增強Java語言底層資源操作能力方面起到了很大的作用。

          直接內(nèi)存是如何進行申請的 java.nio.DirectByteBuffer

          b6296cb33f7b458215099680fee9ade8.webp

          進入到DirectBuffer中進行查看

          cb287b152d96232a954d46018713b960.webp

          源碼解讀

          PS:只需要讀核心的劃紅框的位置的源碼,其他內(nèi)容按個人興趣閱讀

          • 直接調(diào)用ByteBuffer.allocateDirect方法

          • 聲明一個一個DirectByteBuffer對象

          • 在DirectByteBuffer的構造方法中主要進行三個步驟

            • 步驟1:調(diào)用Unsafe的native方法allocateMemory進行緩存空間的申請,獲取到的base為內(nèi)存的地址

            • 步驟2:設置內(nèi)存空間需要和步驟1聯(lián)合進行使用

            • 步驟3:使用虛引用Cleaner類型,創(chuàng)建一個緩存的釋放的虛引用

          直接緩存是如何釋放的

          我們前面說的了Cleaner的使用方式,那么cleaner在直接內(nèi)存的釋放中的流程是什么樣的呢?

          1.新建虛引用

          java.nio.DirectByteBuffer

          8318e5a0109f6d8c161a49abaa1e0a5e.webp

          b875d3b2aab6b6fb83e4ccdc41040858.webp

          2a9d0f16a6405f91f57b6e40ee522fc2.webp

          步驟如下

          • 調(diào)用Cleaner.create()方法

          • 將當前新建的Cleaner加入到鏈表中

          2.聲明清理緩存任務

          查看java.nio.DirectByteBuffer.Deallocator的方法

          00ee61f7f6567977e826928698157319.webp

          • 實現(xiàn)了Runnable接口

          • run方法中調(diào)用了unsafe的native方法freeMemory()進行內(nèi)存的釋放

          當前線程優(yōu)先級最高,調(diào)用方法 tryHandlePending

          進入方法中,會調(diào)用c.clean c-->(Cleaner)


          6e252343f8ffb3f911bbfb8c29c76093.webp


          clean方法為Cleaner中聲明的Runnable,調(diào)用其run()方法
          Cleaner中的聲明:
          private final Runnable thunk;


          b9c21f0d15f90a2e45ce76be01500c2a.webp


          回到《聲明清理緩存任務》這一節(jié),查看Deallocator,使用unsafe的native方法 freeMemory 進行緩存的釋放

          00ee61f7f6567977e826928698157319.webp


          4.直接內(nèi)存的使用方式

          直接內(nèi)存特性

          • nio中比較經(jīng)常使用,用于數(shù)據(jù)緩沖區(qū)ByteBuffer

          • 因為其不受JVM的垃圾回收管理,故分配和回收的成本較高

          • 使用直接內(nèi)存的讀寫性能非常高

          直接內(nèi)存是否會內(nèi)存溢出

          直接內(nèi)存是跟系統(tǒng)內(nèi)存相關的,如果不做控制的話,走的是當前系統(tǒng)的內(nèi)存,當然JVM中也可以對其使用的大小進行控制,設置JVM參數(shù)-XX:MaxDirectMemorySize=5M,再執(zhí)行的時候就會出現(xiàn)內(nèi)存溢出


          26596b7c8d4de67b21f12857422e2e59.webp


          直接內(nèi)存是否會被JVM的GC影響

          如果在直接內(nèi)存聲明的下面調(diào)用System.gc();,因為會觸發(fā)一次FullGC,則對象會被回收,則ReferenceHandler中的會被調(diào)用,直接內(nèi)存會被釋放

          我想使用直接內(nèi)存,怎么辦

          如果你很想使用直接內(nèi)存,又想讓直接內(nèi)存盡快的釋放,是不是我直接調(diào)用System.gc();就行?
          答案是不行的

          • 首先調(diào)用System.gc();會觸發(fā)FullGC,造成stop the world,影響系統(tǒng)性能

          • 系統(tǒng)怕有初級研發(fā)顯式調(diào)用System.gc();會配置JVM參數(shù):-XX:+DisableExplicitGC,禁止顯式調(diào)用

          如果還想調(diào)用的話,自己使用Unsafe進行操作,以下為示例代碼
          PS:僅為建議,如果沒有對于Unsafe有很高的理解,請勿嘗試

                
                  package com.lzl.netty.study.jvm;
                
                
                  
                    
          import sun.misc.Unsafe;
          import java.lang.reflect.Field;
          /** * 使用Unsafe對象操作直接內(nèi)存 * * @author liuzuolong * @date 2022/7/1 **/ public class UnsafeOperateDirectMemory {
          private static final int SIZE_100MB = 100 * 1024 * 1024;
          public static void main(String[] args) { Unsafe unsafe = getUnsafePersonal(); long base = unsafe.allocateMemory(SIZE_100MB); unsafe.setMemory(base, SIZE_100MB, (byte) 0); unsafe.freeMemory(base); }
          /** * 因為Unsafe為底層對象,所以正式是無法獲取的,但是反射是萬能的,可以通過反射進行獲取 * Unsafe自帶的方法getUnsafe 是不能使用的,會拋異常SecurityException * 獲取 Unsafe對象 * * @return unsafe對象 * @see sun.misc.Unsafe#getUnsafe() */ public static Unsafe getUnsafePersonal() {
          Field f; Unsafe unsafe; try { f = Unsafe.class.getDeclaredField("theUnsafe"); f.setAccessible(true); unsafe = (Unsafe) f.get(null); } catch (Exception e) { throw new RuntimeException("initial the unsafe failure..."); } return unsafe; } }

          5.總結

          JVM相關知識是中高級研發(fā)人員必備的知識,學習他的一些運行原理,對我們的日常工作會有很大的幫助


          瀏覽 50
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  色AV欲| 大香蕉在线网 | 日本极品在线 | 成人AV麻豆系列 | 久操7777 |