Java虛擬機原理(上)-揭秘Java GC黑匣子-知其所以然,從此不再捆手捆腳


對于Java開發者來說,GC(垃圾回收器)就如同一個神秘的黑匣子,它在背后不知疲倦地運作,卻也時常給我們帶來諸多疑惑和挫折。今天,就讓我們切開這個黑匣子,深入解析Java GC的工作原理,助你了解其中的奧秘,從此不再被GC所擾。


這篇文章的主要內容包括:

  1. 對象存活判斷及引用類型辨析
  2. 三種主流GC算法的原理和特點
  3. 如何正確解讀GC日志

一、對象存活與引用類型


在深入GC算法前,我們先了解一下Java中對象的生命周期。通過判斷對象是否可被訪問,JVM會決定其是否會被回收。這里涉及一個重要的概念——引用。

1、對象的生命周期


對象的生命周期開始于它的創建,結束于它的回收。在Java中,對象的回收由垃圾回收器(GC)負責,GC會根據引用的類型和數量來決定何時回收對象。
  • 強引用(Strong Reference) - 最普通的對象引用方式,只要存在強引用指向對象,它就不會被GC回收。

  • 軟引用(Soft Reference) - 有時候用于實現內存敏感的高速緩存,當內存不足時會被GC回收。

  • 弱引用(Weak Reference) - 如WeakHashMap所使用,即使沒被GC回收,也可能會被回收。

  • 虛引用(Phantom Reference) - 最弱的引用,唯一目的是能在對象被GC回收時收到系統通知。

理解了引用類型,我們就能判斷對象在何種情況下會被回收。比如,當一個對象沒有任何強引用指向它,那它就處于可被回收狀態。


### 2、強引用(Strong Reference)
  • 定義: 強引用是最常見的引用類型,如果一個對象具有強引用,那么它永遠不會被垃圾回收器回收,直到這個引用被顯式地設置為null

  • 示例

Object obj = new Object();
// obj是一個強引用,只要obj存在,對象就不會被回收

3、軟引用(Soft Reference)


  • 定義: 軟引用用來描述一些有用但非必需的對象。當系統內存不足時,這些對象會被垃圾回收器回收。
  • 用途: 軟引用通常用于實現內存敏感的緩存。
  • 示例
import java.lang.ref.SoftReference;Object obj = new Object();
SoftReference<Object> softRef = new SoftReference<>(obj);
obj = null; // 顯式地清空強引用// 當系統內存不足時,軟引用指向的對象可能會被回收
if (softRef.get() == null) {System.out.println("對象已被回收");
}

4、弱引用(Weak Reference)


  • 定義: 弱引用不足以阻止對象的垃圾回收。也就是說,只要垃圾回收器發現了弱引用,不管當前內存是否足夠,都會回收其指向的對象。
  • 用途: 弱引用通常用于監聽對象的消失,例如,用于實現弱鍵(WeakHashMap)。
  • 示例
import java.lang.ref.WeakReference;Object obj = new Object();
WeakReference<Object> weakRef = new WeakReference<>(obj);
obj = null; // 顯式地清空強引用// 對象幾乎立即會被回收,因為弱引用不會阻止垃圾回收
if (weakRef.get() == null) {System.out.println("對象已被回收");
}

5、虛引用(Phantom Reference)


  • 定義: 虛引用是最弱的一種引用類型。一個對象是否有虛引用的存在,完全不會對其生存時間構成影響,也無法通過虛引用來獲取一個對象的實例。
  • 用途: 虛引用主要用于在對象被回收后收到一個系統通知,用來跟蹤對象被垃圾回收的狀態。
  • 示例
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;Object obj = new Object();
PhantomReference<Object> phantomRef = new PhantomReference<>(obj, new ReferenceQueue<Object>());
obj = null; // 顯式地清空強引用// 虛引用可以注冊到引用隊列上
// 當對象被回收后,虛引用會被放入引用隊列
if (phantomRef.isEnqueued()) {System.out.println("對象已被回收");
}

二、三種主流GC算法原理


JVM的GC算法負責探測并回收上述可回收的對象,以釋放內存空間。目前主流的GC算法主要有以下三種:


1、標記-清除(Mark-Sweep)算法原理

這是最基礎和常見的GC算法。

(1)、它分兩個階段工作

  • 標記階段(Marking Phase)

    • 垃圾回收器從根對象(如類變量、局部變量等)開始,遞歸地訪問所有可達的對象。
    • 所有被訪問到的對象都會被標記為“存活”。
  • 清除階段(Sweeping Phase)

    • 在標記階段結束后,垃圾回收器會遍歷堆內存。
    • 對于未被標記的對象,垃圾回收器會認為它們是“垃圾”,并進行回收。
    • 清除后,這些內存空間會被重新整理,為新對象的分配做準備。

(2)、標記-清除算法的問題

  • 內存碎片:清除階段可能會導致內存碎片,因為回收的對象可能不是連續的。

  • 效率問題:標記和清除階段可能需要暫停整個應用程序(Stop-The-World,STW),影響性能。


(3)、Java案例代碼演示

下面演示了標記-清除算法的工作原理:

public class MarkSweepDemo {public static void main(String[] args) {// 創建一些對象Object obj1 = new Object();Object obj2 = new Object();Object obj3 = new Object();// 假設obj1和obj2是根對象,它們被標記為存活// obj3沒有被根對象直接或間接引用,將被標記為垃圾// 模擬標記階段mark(obj1); // 標記obj1mark(obj2); // 標記obj2// obj3不會被標記// 模擬清除階段sweep();// 檢查對象是否被回收System.out.println("obj1 is alive: " + (obj1 == null ? "No" : "Yes"));System.out.println("obj2 is alive: " + (obj2 == null ? "No" : "Yes"));System.out.println("obj3 is alive: " + (obj3 == null ? "No" : "Yes"));}// 模擬標記過程private static void mark(Object obj) {// 這里只是模擬,實際的標記過程由GC執行System.out.println(obj + " is marked as alive.");}// 模擬清除過程private static void sweep() {// 清除未被標記的對象,這里只是模擬System.out.println("Sweeping phase: clearing unmarked objects.");}
}

在這個示例中,我們創建了三個對象obj1obj2obj3。在模擬的標記階段,obj1obj2被標記為存活,而obj3沒有被標記。在模擬的清除階段,未被標記的obj3將被“回收”(在實際的Java程序中,對象的回收是由JVM的垃圾回收器自動完成的)。

請注意,這個示例只是為了演示標記-清除算法的原理,并不是實際的Java垃圾回收過程。在實際的Java程序中,你不需要手動進行標記和清除,這些工作都是由JVM自動完成的。


2、復制(Copying)

復制(Copying)算法,也被稱為“半區算法”或“新生代算法”,是一種簡單且高效的垃圾回收算法,尤其適用于對象生命周期短的場景,如Java中的新生代(Young Generation)。

復制算法將堆內存分為兩個相等的區域:一個用于分配新對象(稱為From區),另一個用于垃圾回收時的復制操作(稱為To區)。

// 初始內存布局
// ----容器1----      ----容器2----
// [obj1,obj2]          []// 進行GC后
// ----容器1----      ----容器2----  
//    []              [obj1,obj2] 

(1)、算法的步驟如下

  • 對象分配

    • 新對象首先在From區分配。
  • 標記階段

    • 從根對象開始,標記所有存活的對象。這一步與標記-清除算法相同。
  • 復制階段

    • 將所有存活的對象從From區復制到To區,同時更新所有引用,使其指向To區的新位置。
    • 復制完成后,From區的所有對象都可以被清除。
  • 角色交換

    • 復制完成后,From區和To區的角色互換。新的To區變為新的From區,用于下一次垃圾回收。


(2)、復制算法的優點

  • 內存碎片問題:由于每次回收后都會進行復制,因此不會產生內存碎片。

  • 簡單高效:復制算法實現簡單,且在對象生命周期短的情況下效率很高。


    (3)、復制算法的缺點

  • 內存空間浪費:由于需要兩個區域,因此需要額外的內存空間。

  • 復制開銷:復制存活對象需要一定的時間開銷。


(4)、Java案例代碼演示

下面代碼演示了復制算法的工作原理:

public class CopyingGCDemo {public static void main(String[] args) {// 模擬From區和To區Object[] fromArea = new Object[10];Object[] toArea = new Object[10];// 假設fromArea中前5個對象是新分配的for (int i = 0; i < 5; i++) {fromArea[i] = new Object();}// 模擬標記階段mark(fromArea);// 模擬復制階段int toIndex = 0;for (int i = 0; i < fromArea.length; i++) {if (fromArea[i] != null) {toArea[toIndex++] = fromArea[i];}}// 角色交換fromArea = toArea;toArea = new Object[10]; // 為下一次回收準備新的To區// 檢查對象是否被復制for (int i = 0; i < fromArea.length; i++) {if (fromArea[i] != null) {System.out.println("Object at index " + i + " is alive and copied.");}}}// 模擬標記過程private static void mark(Object[] area) {// 這里只是模擬,實際的標記過程由GC執行for (Object obj : area) {if (obj != null) {System.out.println(obj + " is marked as alive.");}}}
}

在這個示例中,我們使用兩個數組fromAreatoArea來模擬From區和To區。我們首先在fromArea中分配了一些新對象,然后模擬了標記階段,接著將存活的對象復制到toArea,并更新索引。最后,我們交換了fromAreatoArea的角色,準備下一次垃圾回收。

請注意,這個示例只是為了演示復制算法的原理,并不是實際的Java垃圾回收過程。在實際的Java程序中,你不需要手動進行復制,這些工作都是由JVM的垃圾回收器自動完成的。


3、標記-整理(Mark-Compact)

標記-整理算法是垃圾回收中的另一種算法,它結合了標記-清除算法和復制算法的優點,旨在解決清除算法中的內存碎片問題。


(1)、標記-整理算法三個階段

  • 標記階段(Marking Phase)

    • 從根對象開始,遞歸地標記所有可達對象。被標記的對象被認為是存活的。
  • 整理階段(Compacting Phase)

    • 將所有存活的對象向內存的一端移動,未被標記的對象則被忽略。
    • 移動對象時,會更新所有指向這些對象的引用,確保它們指向新的位置。
  • 清除階段(Clearing Phase)(可選):

    • 在整理完成后,可能需要清除未被移動的對象占用的空間,以避免內存碎片。
// 初始內存布局
// ----內存區----
// [obj1,  ,obj2, ,  ,  ,obj3]// 標記整理后
// ----內存區----  
// [obj1,obj2,obj3,        ]

(2)、標記-整理算法的優點

  • 內存碎片:通過整理階段,可以減少或消除內存碎片。
  • 內存利用率:由于整理了存活對象,所以可以更有效地利用內存空間。

(3)、標記-整理算法的缺點

  • 移動開銷:移動對象和更新引用需要額外的時間開銷。
  • 暫停時間:標記和整理階段可能需要暫停應用程序,影響性能。

(4)、案例代碼演示

以下模擬了標記-整理算法的工作原理。

public class MarkCompactDemo {static class ObjectWithIndex {Object object;int index;ObjectWithIndex(Object object, int index) {this.object = object;this.index = index;}}public static void main(String[] args) {// 假設有5個對象,其中3個是存活的Object[] objects = new Object[5];objects[0] = new Object();objects[1] = new Object();objects[2] = new Object();objects[3] = null; // 假設這個對象是垃圾objects[4] = new Object();// 模擬根引用Object root = objects[0];// 標記階段mark(objects, root);// 整理階段int newIndex = 0;for (int i = 0; i < objects.length; i++) {if (objects[i] != null) {objects[newIndex] = objects[i];objects[i] = null; // 清除原位置newIndex++;}}// 清除階段(可選)// 在實際的GC中,這一步通常由垃圾回收器自動完成// 輸出整理后的對象for (int i = 0; i < newIndex; i++) {System.out.println("Object at index " + i + " is alive.");}}private static void mark(Object[] objects, Object root) {// 使用一個集合來記錄已訪問的對象HashSet<Object> marked = new HashSet<>();// 使用隊列模擬遞歸過程Queue<Object> queue = new LinkedList<>();queue.add(root);while (!queue.isEmpty()) {Object current = queue.poll();if (marked.add(current)) { // 如果對象未被標記for (int i = 0; i < objects.length; i++) {if (objects[i] == current) {System.out.println("Object at index " + i + " is marked as alive.");break;}}// 假設current對象有引用屬性,模擬遞歸標記// 這里簡化處理,實際情況需要根據對象的實際引用進行標記}}}
}

在這個示例中,我們創建了一個對象數組objects,其中包含幾個對象和一個null。我們模擬了一個根對象root,它引用了數組中的第一個對象。mark方法模擬了標記階段,使用一個隊列和一個集合來遞歸地標記所有可達對象。然后,我們在main方法中模擬了整理階段,將所有存活的對象移動到數組的開始位置,并清除了原位置。

請注意,這個示例僅用于演示標記-整理算法的基本原理,實際的Java垃圾回收過程要復雜得多,并且由JVM自動管理。


三、解讀GC日志


理解了垃圾回收的原理后,我們來看看如何解讀GC日志 。

GC(Garbage Collection)日志是Java虛擬機(JVM)在執行垃圾回收時生成的日志信息,它記錄了GC的觸發時間、持續時間、回收的內存量、使用的GC算法等信息。通過分析GC日志,我們可以了解應用的內存使用情況,發現潛在的問題和性能瓶頸。


1、如何開啟GC日志

在Java應用啟動時,可以通過設置JVM參數來開啟GC日志:

-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:+PrintGCTimeStamps
  • PrintGC:打印GC發生的基本日志。
  • PrintGCDetails:打印GC的詳細日志。
  • PrintGCDateStamps:在日志中包含日期時間戳。
  • PrintGCTimeStamps:在日志中包含自JVM啟動以來的時間戳。

2、GC日志的關鍵信息

  • 時間戳:GC事件發生的時間。

  • GC類型:如Minor GC(新生代回收)、Full GC(全堆回收)等。

  • 持續時間:GC事件持續的時間。

  • 回收前后的內存使用情況:包括新生代、老年代等內存區域的內存使用情況。

  • GC原因:觸發GC的原因,如分配失敗、系統內存不足等。


3、解讀GC日志示例

假設我們有以下GC日志片段:

2024-05-23T14:37:12.123+0000 [GC [PSYoungGen: 73328K->6336K(94208K)] 73328K->6336K(190464K), 0.003602 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
2024-05-23T14:37:12.126+0000 [Full GC [PSYoungGen: 6336K->0K(94208K)] [ParOldGen: 0K->5120K(95296K)] 6336K->5120K(189504K), [Metaspace: 3258K->3258K(1056768K)], 0.006773 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 

解讀

(1)、時間戳

  • 第一條日志發生在 2024-05-23T14:37:12.123+0000

  • 第二條日志發生在 2024-05-23T14:37:12.126+0000

(2)、GC類型

  • 第一條日志是一次Minor GC。

  • 第二條日志是一次Full GC。

(3)、持續時間

  • 第一次Minor GC持續了 0.003602 秒。

  • 第二次Full GC持續了 0.006773 秒。

(4)、內存使用情況

  • 第一次Minor GC前,新生代使用了 73328K,回收后剩余 6336K
  • 第一次Minor GC后,整個堆使用了 6336K
  • 第二次Full GC前,新生代使用了 6336K,老年代未使用,回收后新生代 0K,老年代 5120K
  • 第二次Full GC后,整個堆使用了 5120K

(5)、GC原因:

  • 第一次Minor GC可能因為新生代空間不足。
  • 第二次Full GC可能因為Minor GC后仍然有內存需求,或者達到了Full GC的條件。

4、監控應用內存使用狀況

通過定期檢查GC日志,我們可以監控應用的內存使用情況:

  • 頻繁的GC:如果GC頻繁發生,可能表明內存使用率高,需要優化內存使用。

  • 長時間的GC:長時間的GC可能導致應用響應變慢,需要關注。

  • 內存泄漏:如果老年代的內存持續增長,可能存在內存泄漏。


5、發現潛在問題和性能瓶頸

  • 內存分配率:如果內存分配率持續高于GC回收率,可能導致內存不足。
  • Full GC頻率:頻繁的Full GC可能影響性能,需要優化。
  • 內存碎片:如果老年代的內存使用不連續,可能導致內存碎片問題。

通過分析GC日志,我們可以對應用的內存使用情況有一個清晰的了解,并據此進行性能調優。在實際的生產環境中,還可以使用專業的監控工具來自動化這一過程,及時發現并解決潛在的問題。


四、結語


以上內容涵蓋了GC的常見知識,但Java GC為主題的探討絕不止于此。比如說,JDK中還引入了全新的ZGC算法,用于低延遲處理;G1作為一種優秀的分代實現,如何工作;怎樣有效地配置GC參數…等等,這些都是值得我們去學習和思考的重要話題。


本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/13943.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/13943.shtml
英文地址,請注明出處:http://en.pswp.cn/web/13943.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

SpringBoot anyline

1、定義通用處理 public interface ClickHouseBaseService extends IService<DataRow> {/*** 根據sql查詢數據庫&#xff0c;返回集合對象** param sql 執行sql* return {link DataSet} 數據集*/DataSet querys(String sql);/*** 根據sql查詢數據庫&#xff0c;返回單個…

云手機在軟件測試中的作用,為軟件測試工程師減負

針對每家企業來說&#xff0c;對于即將上線的軟件進行測試這一步驟是不可忽視的&#xff0c;這決定產品上線后的質量和口碑&#xff1b; 傳統的的真機測試可能面臨設備大量的采購&#xff0c;管理和維護的成本提高&#xff0c;現在不少企業都開始用云手機來代替真機&#xff0…

24.zabbix高可用

環境準備 準備三臺機器 主機名字IP地址軟件環境zabbix-server01192.168.111.70httpdphpkeepalivedsshpasszabbix serveragentzabbix-server02192.168.111.71httpdphpkeepalivedsshpasszabbix serveragentzabbix-agent192.168.111.80zabbix agentmysql VIP規劃&#xff1a;19…

AWS計算之Batch

AWS Batch是亞馬遜提供的一項批量計算服務&#xff0c;旨在幫助用戶高效地處理大規模的計算工作負載。AWS Batch可以自動調度、運行和監控批處理作業&#xff0c;用戶無需管理底層的計算資源&#xff0c;可以專注于編寫和提交作業。AWS Batch提供了靈活的配置選項&#xff0c;包…

《Ai-企業知識庫》-討論、構思01

阿丹&#xff1a; 經過幾天的Ai學習&#xff0c;開始對于整個大模型&#xff0c;開始有清晰的認知了。開始準備上手操作&#xff0c;編程自己去寫一些東西了。 什么是會話AI? 一個計算機程序&#xff0c;允許人類使用各種輸入方法&#xff0c;如語音&#xff0c;文字&#x…

使用STM32F103讀取TF卡并模擬U盤:使用標準庫實現

使用STM32F103讀取TF卡并模擬U盤&#xff1a;使用標準庫實現 STM32F103是一款流行的ARM Cortex-M3微控制器&#xff0c;在嵌入式系統中廣泛應用。本文將介紹如何使用STM32F103讀取TF卡&#xff0c;并將其模擬成U盤&#xff0c;讓電腦可以讀取TF卡的內容。我們將使用標準庫&…

Spring常見問題

如何理解spring屬于低侵入式設計&#xff1f; 在代碼中不需要寫明具體依賴對象&#xff0c;在運行時進行自動注入&#xff0c;降低了組件的耦合依賴的是接口&#xff0c;而接口的實現類具有拓展性 Spring IOC 實現了什么功能&#xff0c;談談你對IOC的理解。 負責創建對象&…

【云原生】K8s管理工具--Kubectl詳解(一)

一、陳述式管理 1.1、陳述式資源管理方法 kubernetes 集群管理集群資源的唯一入口是通過相應的方法調用 apiserver 的接口kubectl 是官方的 CLI 命令行工具&#xff0c;用于與 apiserver 進行通信&#xff0c;將用戶在命令行輸入的命令&#xff0c;組織并轉化為apiserver 能識…

Elasticsearch集群和Logstash、Kibana部署

1、 Elasticsearch集群部署 服務器 安裝軟件主機名IP地址系統版本配置ElasticsearchElk10.3.145.14centos7.5.18042核4GElasticsearchEs110.3.145.56centos7.5.18042核3GElasticsearchEs210.3.145.57centos7.5.18042核3G 軟件版本&#xff1a;elasticsearch-7.13.2.tar.gz 示…

ubuntu18 安裝python3.8

在Ubuntu 18.04上安裝Python 3.8可以通過以下步驟完成&#xff1a; 1.更新包列表和已安裝的包&#xff1a; sudo apt update sudo apt upgrade 2.安裝依賴項&#xff1a; sudo apt install -y software-properties-common 3.添加Python 3.8的PPA&#xff08;個人包歸檔&am…

【Docker系列】 Docker容器具體信息查詢

&#x1f49d;&#x1f49d;&#x1f49d;歡迎來到我的博客&#xff0c;很高興能夠在這里和您見面&#xff01;希望您在這里可以感受到一份輕松愉快的氛圍&#xff0c;不僅可以獲得有趣的內容和知識&#xff0c;也可以暢所欲言、分享您的想法和見解。 推薦:kwan 的首頁,持續學…

Python函數進階:四大高階函數、匿名函數、枚舉、拉鏈與遞歸詳解

系列文章目錄 Python數據類型&#xff1a;編程新手的必修課深入探索Python字符串&#xff1a;技巧、方法與實戰Python 函數基礎詳解Python正則表達式詳解&#xff1a;掌握文本匹配的魔法Python文件操作寶典&#xff1a;一步步教你玩轉文件讀寫Python面向對象基礎與魔法方法詳解…

databricks~Unity Catalog

Unity Catalog hierarchy 包含了用戶授權管理信息和元數據信息 workspace with unity catalog hierarchy unity metastore Ref: https://www.youtube.com/playlist?listPLY-V_O-O7h4fwcHcXgkR_zTLvddvE_GfC

[力扣題解] 494. 目標和

題目&#xff1a;494. 目標和 思路 01背包 轉換為01背包問題 難點在于看出可以用背包問題解決本題&#xff1b; 題目字面意思是劃分出一堆再減去另一堆&#xff0c;得到的結果想要等于target&#xff0c;設定一堆為正&#xff0c;記為left&#xff0c;另一堆為負&#xff0…

ChatGPT類大模型應用入門了解與使用

一 前言 ChatGPT大眾熱情逐漸褪去&#xff0c;但在后臺技術人的探索還處于熱火朝天狀態。如果我們生活的世界是一杯清水&#xff0c; 那類似ChatGPT的語言大模型技術的橫空出世就如滴入水杯的一滴墨汁&#xff0c;第一滴很顯眼&#xff0c;但實際上是后續墨汁慢慢擴散滲透才是…

Windows11下使用Qt5.14.2編譯QtXlsx驅動詳細步驟

原有&#xff1a;由于系統需要將QTableWidget表格中的數據導出、在Windows下最開始使用Excel.Application組件實現了導出功能&#xff0c;后面將代碼轉換到Ubuntu20.04下進行編譯&#xff0c;發現項目.pro文件中的QT axcontainer和代碼.h文件中的#include <QAxObject>跟…

基于圖鳥UI的資訊名片模版開發與應用

一、引言 在前端技術日新月異的今天&#xff0c;快速、高效、美觀的UI組件庫和模板成為了開發者們關注的焦點。圖鳥UI作為一款集成了基礎布局元素、配色體系、圖標icon和精選組件的UI框架&#xff0c;為前端開發者提供了極大的便利。本文將以圖鳥UI為基礎&#xff0c;探討基于…

接口測試工具有哪些,哪些比較火

接口測試工具可以幫助開發人員和測試人員更高效地進行接口測試&#xff0c;以下是一些常用的接口測試工具&#xff1a; 1. **Postman** Postman 是一款廣受歡迎的接口測試工具&#xff0c;它提供了豐富的功能和直觀的用戶界面&#xff0c;幫助開發人員和測試人員輕松進行 API…

如何讓外網訪問內網服務?

隨著互聯網的快速發展&#xff0c;越來越多的企業和個人需要將內網服務暴露給外網用戶訪問。由于安全和隱私等因素的考慮&#xff0c;直接將內網服務暴露在外網是非常不安全的做法。如何讓外網用戶安全訪問內網服務成為了一個重要的問題。 在這個問題上&#xff0c;天聯公司提供…

golang rune類型解析,與byte,string對比,以及應用

Golang中的rune類型是一個32位的整數類型(int32)&#xff0c;它是用來表示Unicode碼點的。rune類型的值可以是任何合法的Unicode碼點&#xff0c;它通常用來處理字符串中的單個字符。 在Golang中&#xff0c;字符常量使用單引號來表示&#xff0c;例如 a。使用單引號表示的字符…