深入理解java虛擬機_第二章_讀書筆記

来源:http://www.cnblogs.com/Mairr/archive/2017/11/15/7756785.html
-Advertisement-
Play Games

1.本章內容目錄: 概述 運行時數據區域 程式計數器 java虛擬機棧 本地方法棧 java堆 方法區 運行時常量池 直接記憶體 HotSpot虛擬機對象探秘 對象的創建 對象的記憶體佈局 對象的訪問定位 程式計數器 java虛擬機棧 本地方法棧 java堆 方法區 運行時常量池 直接記憶體 對象的創建 ...


1.本章內容目錄:(java記憶體區域於記憶體溢出異常)

  • 概述
  • 運行時數據區域
    • 程式計數器
    • java虛擬機棧
    • 本地方法棧
    • java堆
    • 方法區
    • 運行時常量池
    • 直接記憶體
  • HotSpot虛擬機對象探秘
    • 對象的創建
    • 對象的記憶體佈局
    • 對象的訪問定位        
  • 實戰:OutOfMemoryError異常
    • java堆溢出
    • 虛擬機棧和本地方法棧溢出
    • 方法區和運行時常量池溢出
    • 本機直接記憶體溢出

     

2.本章具體內容:

 

2.1 概述:

  對於C/C++而言,記憶體管理具有最高的權利,既擁有每一個對象的“所有權”,又擔負著每一個對象生命開始到結束的維護責任。

   對於java而言,則把記憶體控制的權利交給了java虛擬機,不再需要為每一個new操作去寫配對的delete/free代碼,不容易出現記憶體泄露和溢出問題。但是,一旦出現記憶體泄露和溢出方面的問題,如果不瞭解虛擬機記憶體運行過程,排查會很艱難。以下是整個java虛擬機運行的基本結構。

 

2-1

 

 

2.2 運行時數據區域

 

  java虛擬機運行時數據區域如下圖所示。其中,虛擬機棧、本地方法棧、程式計數器為線程隔離的數據區。而本地庫介面、堆、執行引擎和方法區則是所有線程共用的區域。

 

2-2

 

 

2.2.1  程式計數器

 

  程式計數器是一塊很小的記憶體空間,是當前線程所執行的位元組碼的行號指示器。利用它來選取下一條執行的位元組碼的行號指令。(分支、迴圈、跳轉、異常處理、線程恢復等都依賴這個計數器來完成)

  java虛擬機的多線程是通過線程的輪流切換並分配處理器執行時間來實現的。一個處理器(多核處理器而言只是一個內核)在任何一個確定的時刻只會處理一條線程中的指令。為了線程切換後能恢復到正確的位置,每條線程都需要一個獨立的程式計數器,各個線程之間獨立存儲,互不影響,我們把這類記憶體區域稱之為“線程私有”的記憶體。

  如果線程正在執行一個java方法,則這個計數器記錄的是正在執行的虛擬機位元組碼指令的地址。如果正在執行Native方法(本地方法),這個計數器則為空(undefined)。此記憶體區域是唯一一個在java虛擬機規範中沒有規定OutOfMemoryError情況的區域。

  

2.2.2  java虛擬機棧

 

  java虛擬機棧(Java Virtual Machine Starks)也是線程私有的,它的生命周期與線程相同。虛擬機棧描述的是java方法執行的記憶體模型:每個方法在執行時會創建一個棧幀(Stack Fram)用於存儲局部變數表、操作數棧、動態鏈接、方法出口等信息。每一個方法調用直至完成過程,就對應著一個棧在虛擬機中入棧到出棧的過程。

  虛擬機棧中的局部變數表,存放了編譯期可知的各種基本數據類型(boolean、byte、char、short、int、float、double)、對象引用(reference類型,它不等同於對象本身,可能是一個指向對象起始地址的引用指針,也可能是指向一個代表對象的句柄或者其他與此對象相關的位置)、returnAddress類型(指向一條位元組碼指令的地址)。其中,64位長度的long和double類型的數據會占用2個局部變數空間(Slot),其餘的數據類型只占用1個。局部變數表所需的記憶體空間在編譯期間完成分配,當進入一個方法時,這個方法需要在幀中分配多大的局部變數空間完全確定,方法運行期間不會改變局部變數表的大小。

  java虛擬機棧中規定了兩種異常處理狀況:如果線程請求的棧深度大於虛擬機所允許的深度,將拋出StackOverflowError異常;如果虛擬機棧可以動態擴展(目前大部分虛擬機都可以動態擴展),當擴展時無法申請到足夠的記憶體,就會拋出OutOfMemoryError異常。

 

2.2.3 本地方法棧(Native Method Stack)

 

  本地方法棧(Native Method Stack)和虛擬機棧所發揮的作用是相似的。唯一的區別是,虛擬機棧為虛擬機執行java方法(也就是位元組碼)服務,而本地方法棧則為虛擬機使用到的Native方法服務。

   有的虛擬機直接把本地方法棧和虛擬機棧合二為一。和虛擬機棧一樣,本地方法棧也會拋出StackOverflowError和OutOfMemoryError異常。

  註:  native方法:一個Native Method就是一個java調用非java代碼的介面。一個Native Method是這樣一個java的方法:該方法的實現由非java語言實現,比如C。這個特征並非java所特有,很多其它的編程語言都有這一機制,比如在C++中,你可以用extern "C"告知C++編譯器去調用一個C的函數。  有時java應用需要與java外面的環境交互。這是本地方法存在的主要原因,你可以想想java需要與一些底層系統如操作系統或某些硬體交換信息時的情況。本地方法正是這樣一種交流機制:它為我們提供了一個非常簡潔的介面,而且我們無需去瞭解java應用之外的繁瑣的細節。

 

2.2.4 Java堆(Java Heap)

 

  一般而言,java堆(java heap)是java虛擬機所管理的記憶體中最大的一塊。java堆被所有線程共用的一個記憶體區域,在虛擬機啟動時被創建。這部分記憶體創建的唯一目的就是存放對象實例,幾乎所有的對象實例都在這裡分配記憶體。java虛擬機規範中描述是“所有的對象實例和數組都要在堆上分配”。

  java堆是垃圾收集器管理的主要區域,因此很多時候也被稱之為“GC堆”(Garbage Collected Heap)

  java堆可以處於物理上不連續的記憶體空間中,只需要邏輯上是連續的既可,就像我們的磁碟空間一樣。實現時,可以時固定大小的,也可以是可擴展來實現的。如果堆中沒有完成實例分配,並且堆也無法再擴展時,將會拋出OutOfMemoryError異常。

 

2.2.5 方法區

 

  方法區(Method Area)與Java堆一樣,是各個線程共用的記憶體區域,它用於存儲已經被虛擬機載入的類信息、常量、靜態變數、即時編譯器編譯後的代碼等數據。java虛擬機規範把方法區描述為堆的一個邏輯部分,但是它卻有一個別名“No-Heap”(非堆),目的是與java堆區分開。

  java虛擬機的方法區,除了和java堆一樣不需要連續的記憶體和可以選擇固定大小或者可擴展外,還可以選擇不實現垃圾收集。垃圾收集的行為在這個區域是比較少見的,但並不是數據進入了方法區就相當於“永遠存在了”。這個區域的回收目標主要是針對常量池的回收和對類型的卸載。

  當方法區無法滿足記憶體分配需求時,將拋出OutOfMemoryError異常。

 

2.2.6 運行時常量池

 

  運行時常量池(Runtime Constant Pool)是方法區的一部分 Class文件中除了有類的版本、欄位、方法、介面等描述信息外,還有一項信息是常量池(Constant Pool Table),用於存放編譯時期生成的各種字面量和符號引用,這部分內容將在類載入後進入方法區的運行時常量池中存放。

  常量池是方法區的一部分,所以,當記憶體池無法申請到方法時會拋出OutOfMemoryError異常。


2.2.7 直接記憶體

 

  直接記憶體並不是虛擬機運行數據的一部分,也不是java虛擬機規範中定義的記憶體區域,為了以示區分,寫在這裡。

  在JDK1.4中新加入了NIO(new input/output)類,引入了一種基於通道(Channel)和緩衝區(Buffer)的I/O方式,它可以使用Native函數庫直接分配堆外記憶體,然後通過一個存儲在java堆中的DirectByteBuffer對象作為這塊記憶體的引用進行操作。這樣通過一個存儲在java堆中的DirectByteBuffer對象作為這塊記憶體的引用進行操作。避免了在java堆和native堆中來回覆制數據,進而提高了性能。

 

2.3  HotSpot虛擬機對象探秘

 

2.3.1 對象的創建

 

  以下是整個虛擬機對象創建的全過程。其中,虛擬機為新生對象分配記憶體的方法分為兩種:第一種,java堆是絕對規整的,所有用過的記憶體都放在一邊,空閑記憶體放在另外一邊,中間放著一個指針視作為分界點的指示器,那所分配的記憶體就僅僅是把那個指針指向空閑空間那邊挪動一段於對象大小相等的距離,這種分配方法稱為“指針碰撞”(Bump the Point);第二種,java堆中的記憶體並不規整,已使用的記憶體和空閑的記憶體相互交錯,虛擬機必須維護一個列表,記錄上哪些記憶體塊是可用的,分配的時候從列表中找到一塊足夠大的空間劃分給對象實例,並更新列表上的記錄,這種分配方式稱之為“空閑列表”(Free List)。選擇哪種分配方式由java堆是否規整決定,而java堆是否規整又由所採用垃圾收集器是否帶有壓縮整理功能決定。此外,在劃分可用空間的過程中可能對多個對象進行分配記憶體,正在對對象A分配記憶體的時候,指針還沒來得及修改,對象B同時使用了原來的指針分配記憶體。解決這個問題有兩個方案:方案一:記憶體空間的動作進行同步處理;方案二:把記憶體分配動作按照線程劃分在不同的空間中進行,即每個線程在java堆中預先分配一小塊記憶體,稱之為本地線程分配緩衝(Thread Local Allocation Buffer,TLAB)。

 

2-3

 

2.3.2 對象的記憶體佈局

 

  在hotspot虛擬機中,對象在記憶體中存儲的佈局可以分為3塊區域:對象頭(Header)、實例數據(Instance Data)、和對齊填充(padding)。

  HotSpot虛擬機的對象頭包括兩部分信息,第一部分用於存儲對象自身的運行時數據,如:哈希碼(HashCode)、GC分代年齡、鎖狀態標誌、線程持有的鎖、偏向線程ID、偏向時間戳等。

 

表2-3-2 HotSpot虛擬機對象頭 Mark Word

存儲內容 標誌位 狀態
對象哈希碼、對象分代年齡 01 未鎖定
指向鎖記錄的指針 00 輕量級鎖定
指向重量級鎖的指針 10 膨脹(重量級鎖定)
空,不需要記錄信息 11 GC標誌

偏向線程ID、偏向時間戳、對象分代年齡 

01 可偏向

 

  對象頭的另外一部分是類型指針,即對象指向它的類元數據的指針,虛擬機通過這個指針來確定這個對象是哪個類的實例。如果對象是一個java屬數組,那在對象頭中還必須有一塊用於記錄數組長度的數據,因為虛擬機可以通過普通java對象的元數據信息確定java對象的大小,但是從數組的元數據中卻無法確定數組的大小。

  實例數據部分是對象真正存儲的有效信息,也是程式代碼中所定義的各種類型的欄位內容。無論是從父類繼承下來的,還是子類中定義的,都需要記錄起來。

  對齊填充並不是必然存在的,沒有什麼特殊意義,它僅僅是起著占位符的作用。由於HotSpot VM的自動記憶體管理系統要求對象起始地址必須是8位元組的整數倍。也就是說,對象的大小必須是8位元組的整數被。因此,當對象實例數據部分沒有對齊時,就需要通過對齊填充來補全。

 

2.3.3 對象的訪問定位

 

  建立對象是為了使用對象,我們的java程式需要通過棧上的reference數據來操作堆上的具體對象。由於reference類型在java虛擬機中規定了一個指向對象的引用,並沒有定義這個引用應該通過何種方式去定位、訪問堆中的對象的具體位置,所以對象的訪問方式也是取決於虛擬機實現而定的。目前主流的對象訪問方式有使用句柄和直接指針兩種。

  (1)句柄訪問對象

  如果使用句柄訪問的話,那麼java堆中將會劃分一塊記憶體來作為句柄池,reference中存儲的就是對象的句柄地址,而句柄中包含了對象實例數據和類型數據各自的具體地址信息。如下圖所示:(ps:句柄(reference)是一種特殊的智能指針,當一個應用程式要引用其他系統(如資料庫、操作系統)所管理的記憶體塊或對象時,就要使用句柄,在C++中句柄稱為引用)。

 

2-4

 

  (2) 直接指針訪問

   如果使用直接指針訪問,那麼java堆對象的佈局中就必須考慮如何放置訪問類型數據的相關信息,而reference中存儲的直接就是對象地址,如下圖所示,

2-5

 

  (2) 比較兩種訪問方式的優勢

  句柄訪問最大的好處是reference中存儲的是穩定的句柄地址,在對象被移動(垃圾收集是移動對象是非常普遍的行為)時只會改變句柄中的實例數據指針,而reference本身不需要修改。

  直接指針訪問最大的好處是速度更快節省了一次指針定位的時間開銷,由於對象訪問在java中非常頻繁,因此這類開銷積少成多後也是一項非常可觀的執行成本。

記憶體泄露:指程式中動態分配記憶體給一些臨時對象,但是對象不會被GC所回收,它始終占用記憶體。即被分配的對象可達但已無用。
作者: yeiqing000
鏈接:http://www.imooc.com/article/15379
來源:慕課網

 

註解記憶體泄露:指程式中動態分配記憶體給一些臨時對象,但是,對象不會被GC所回收,它始終占用記憶體,即分配的對象可達但已無用;

記憶體溢出:是指程式運行過程中,無法申請到足夠的記憶體而發生的一種錯誤;

 

2.4  實戰:OutOfMemoryError 異常(簡稱“OOM”異常)

 

2.4.1 java堆溢出

 

  java堆用於存儲對象實例,只要不斷地創建對象,並且保證GC Roots到對象之間有可到達路徑來避免垃圾回收機制清除這些對象,數量到達最大堆的容量限制後就會產生記憶體異常。

  代碼清單中代碼限制java堆的大小為20MB,不可擴展(將堆的最小值-Xms參數與最大值-Xmx參數設置為一樣既可避免堆自動擴展),通過參數-XX:+HeapDumpOnOutOfMemoryError可以讓虛擬機在出現記憶體溢出異常時Dump出當前的記憶體堆轉儲快照一邊事後分析。

/**
*VM Args: -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
*
*/


public
class HeapOOM{ static class OOMObject{}; public static void main(String[] args){ List<OOMObject> list = new ArrayList<OOMObject>(); while(ture) { list.add(new OOMObject()); } } }

  運行結果:

java.lang.OutOfMemoryError:java Heap Space
Dumping heap to java_pid3404.hprof ...
Heap dump file created [22045981 bytes in 0.663 secs]

  java堆記憶體的OOM異常是實際應用中常見的記憶體溢出異常情況。當出現java堆記憶體溢出時候,異常棧信息“java.lang.OutOfMemoryError”會跟著進一步提示“java Heap Space”。

 

2.4.2 java虛擬機棧和本地方法棧溢出

 

  HotSpot虛擬機中並不區分虛擬機棧和本地方法棧,因此,對於HotSpot來說雖然-Xoss參數(本地方法棧大小)存在,但實際上是無效的,棧容量只由-Xss參數設定。關於虛擬機棧和本地方法棧,在java虛擬機中描述了兩種異常:

  1. 如果線程請求的棧深度大於虛擬機所允許的最大深度,將拋出StarkOverflowError異常;
  2. 如果虛擬機無法申請到足夠大的記憶體空間,將拋出OutOfMemoryError異常;
  3. 使用-Xss參數減少棧記憶體容量。結果拋出StarkOverflowError異常,異常出現時輸出的堆棧深度相應縮小;
  4. 定義了大量的本地變數,增大此方法幀中本地變數表的長度。結果:拋出StarkOverflowError異常使輸出的對堆棧深度相應縮小。

 

  此處代碼測試第一點;

/**
 *VM Args: -Xss128k
 *
 */


public class JavaVMStackSOF{

    private int stackLength = 1;

    public void stackLeak(){
                stackLength++;
                stackLeak();
              }
   
    
    public static void main(String[] args) throws Throwable {
           JavaVMStackSOF oom = new JavaVMStackSOF();
           try{
                    oom.stackLeak();
               } catch (Throwable e){
                    System.out.println("stack length:"+oom.stackLength);
                    throw e;
               }
          }
}

   運行結果:

stack length:2402
Exception in thread "main" java.lang.StackOverflowError
                ......

  結果表明:在單個線程下,無論由於棧幀太大還是虛擬機棧容量太小,當記憶體無發分配的時候,虛擬機拋出的都是StackOverflowError異常。

  如果創建的不限於單線程,通過不斷的建立多線程倒是可以產生記憶體溢出的異常,如下代碼清單所示:

/**
 *VM Args: -Xss2M(這個時候可以設置大一些)
 *
 */

public class javaVMStackOOM{ private void dontStop() { while(true){} }
public void stackLeakByThread() { while(ture){ Thread thread = new Thread (new Runnable() { @override public void run() { dontStop(); } }); thread.start(); } public static void main(String[] args) throws Throwable { JavaVMStackOOM oom = new JavaVMStackOOM(); oom.stackLeakByThread(); }
}

   運行結果為:

Exception in thread "main" java.lang.OutOfMemoryError:unable to create new native thread.

  多線程產生的記憶體溢出(OutOfMemoryError)與棧空間的大小不存在任何聯繫,準確地說,在這種情況下,為每個線程的棧分配的記憶體越大,反而越容易產生產生記憶體溢出異常(因為棧記憶體越大,java堆記憶體變小,能夠分配的線程數量就越少,越容易造成記憶體溢出OutOfMemoryError)。而棧溢出(stackoverflow)才與棧分配記憶體大小直接相關。

  多線程導致的記憶體溢出,在不能減少線程數或者更換64位虛擬機情況下,就只能通過減少最大堆和減少棧容量來換取更多的線程。因為,棧的容量減小,java堆就能夠獲得更多的記憶體,也就可以分配更多的線程;此外,最大堆減小,java堆上剩餘的堆空間就越多,同樣可以創建更多的線程。(參見JVM內部原理圖可知,圖2-1/圖2-2)。

 

2.4.3 方法區和運行時常量池溢出

 

  由於運行時常量池是方法區的一部分,因此這兩個區域的溢出測試就放在一起進行。

  String.intern()是一個native方法(C/C++寫的),他的作用是:如果字元串常量池中已經包含一個等於此String對象的字元串,則返回代表池中這個字元串的String對象,否則,將此String對象包含的字元串添加到常量池中,並且返回此String對象的引用。

  運行時常量池導致的記憶體溢出異常:(JDK1.6)

/**
 *VM Args: -XX:PermSize = 10M   -XX:MaxPermSize = 10M
 *
 */


public class RuntimeConstantPoolOOM{
public static void main(String[] args) {
            List<String> list = new ArrayList<String>();     //使用List保持著常量池的引用,避免Full GC回收常量池行為
       int i = 0;                       //10MB的Permsize在integer範圍內足夠產生OOM了
       while(ture){
          list.add(String.valueOf(i++).intern());
    }
   }
}

  運行結果:

Exception in thread ”main“ java.lang.OutOfMemoryError:PermGen space
  at java.lang.String.intern(Native Method)
  at org.fenixsoft.oom.RuntimeConstantPoolOOM.main(RuntimeConstantPoolOOM.java:18

  運行結果可以看到,OutOfMemoryError後面緊跟的提示信息是”PermGen space“,說明運行時常量池屬於方法區的一部分。

  

  String.intern()返回引用的測試:(JDK1.7)

public class RuntimeConstantPoolOOM {
    
    public static void main (String[] args) {
                        public void main(String[] args) {
                        String str1 = new StringBuilder ("電腦").append("軟體").toString();
                        System.out.println (str2.intern () == str1);

                        String str2 = new StringBuilder("ja").append("va").ToString();
                        System.out.println(str2.intern() == str2);
                        }
                }
}

   JDK1.6中運行會得到2個false,而JDK1.7會得到一個ture和一個false。產生差異的原因是在JDK1.6中,intern()方法會把首次遇到的字元實例複製到永久代中,返回的也會是永久代中這個實例的引用,而由StringBuilder創建的字元串實例在java堆上,所以必然不是同一個引用,將返回false。而JDK1.7中的intern()實現不會再複製實例,只是在常量池記錄首次出現的實例引用,因此intern()返回的引用和由StringBuilder創建的字元串是同一個。

 

2.4.4 本機直接記憶體溢出

 

  DirectMemory容量可以通過-XX:MaxDirectMemorySize指定,如果不指定,則預設與java堆最大值(-Xmx指定)一樣。

  使用unsafe分配本機記憶體:

 

/**
 *VM Args: -Xmx20M -xx:MaxDirectMemorySize = 10M
 *
 */

public class DirectMemoryOOM {

            private static final int_1MB = 1024*1024;

            public static void main(String[] args) throws Exception {
                           Filed unsafeFiled =Unsafe.class.getDeclaredFields () [0];
                           unsafeField.setAccessible(ture);
                           Unsafe unsafe = (Unsafe) unsafeFiled.get(null);        
         while(ture){
          unsafe.allocateMemory (_1MB);
        }
   }
}

 

  運行結果:

Exception in thread "main" java.langOutOfMemoryError
    ... ..

  上述代碼越過了DirectByteBuffer類,直接通過反射獲取unsafe實例進行記憶體分配。雖然使用DirectByteBuffer分配記憶體也會拋出記憶體異常溢出異常,但它拋出異常時並沒有真正向操作系統申請分配記憶體,而是通過計算得知記憶體無法滿足需求(無法分配),於是手動拋出異常,真正申請分配記憶體的方法是 unsafe.allocateMemory()。

 

   由DirectMemory導致的記憶體溢出,一個明顯的特征是在HeapDump文件中不會看見明顯的異常,如果讀者發現記憶體溢出(OOM)之後的Dump文件很小,而程式又直接或者間接使用了NIO,那就考慮一下是否是這方面的原因。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 原文鏈接:http://www.mono-project.com/news/2017/11/13/mono-interpreter/ Mono即將通過它的JIT編譯器和靜態編譯器以及一個.net解釋器以帶來一些新的方式來運行你的代碼. 在2001年Mono項目誕生之時,我們為.net指令集實現了一個 ...
  • 返回總目錄 四、Long Parameter List(過長參數列) 太長的參數列難以理解,太多參數會造成前後不一致、不易使用。 1、一般情況來說,如果參數多於3個,就可以考慮使用對象了。將函數所需要的參數封裝在一個新類中,將這個類當做參數。 2、如果方法的參數數目不定,且參數類型一致,也可以使用p ...
  • 開發環境:VS2017 資料庫:SQL 2012 本文是學習EF的第一篇筆記,參考lloydsheng的帖子實踐,總結記錄。 一、創建項目 先創建一個空白的解決方案,後續關於EF入門筆記基於此項目完成 。 二、安裝 使用NuGet下載安裝EntityFramework到項目中,如果不會使用NuGet... ...
  • 總結一下.NET的編譯過程, 一般的高級編程語言會把代碼編譯成機器碼,也就是我們說的非托管代碼,執行在編譯它的電腦上。 而.NET編譯代碼的時候會把高級編程語言編譯成中間語言 運行在CLR(公共語言運行庫)上,也就是把代碼集成一個exe文件中, .NET 在編譯過程中 沒有直接編譯成CPU認識的代碼 ...
  • 首先要設置主窗體KeyPreview為true,可直接在屬性中進行設置,或者在窗體載入中設置: this.KeyPreview = true;然後添加窗體KeyDown事件,如下: ...
  • <!DOCTYPE html><html> <head> <meta charset="utf-8"> </head></html><?php/** * Created by 陳藝赫. * User: Vvvvv * Date: 2017/10/9 * Time: 9:01 *///面向對象//什麼 ...
  • ngrok的使用 1、下載Tomcat並啟動。 2、訪問本地Tomcat,代表啟動Tomcat成功。 3、到https://ngrok.com下載ngrok. 4、下載存放的目錄。 5、命令行進入到該目錄,執行 ngrok http 8080 。 8080是Tomcat預設的埠號。 6、之後就可映 ...
  • 1.1 Python語言介紹 1.1.1 什麼是編程?為什麼要編程 編程 是個動詞,編程==寫代碼,寫代碼為了什麼? 為了讓電腦乾你想要乾的事情,比如,馬化騰想跟別人聊天,於是寫了個聊天軟體,這個軟體就是一堆代碼的集合,這些代碼是什麼?這些代碼是電腦能理解的語言。 例子:你是公司老闆,你有一個員 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...