新手指南基礎學

vbcjnmkk發表於2024-08-31
  1. equels和的區別
      equals方法用於比較物件的內容是否相等,可以根據自定義的邏輯來定義相等的條件,而
    運算子用於比較物件的引用是否相等,即它們是否指向同一塊記憶體地址。equals方法是一個

例項方法,可以被所有的Java物件呼叫,而運算子可以用於比較物件的引用或基本資料型別的值。equals方法的行為可以被重寫,以適應特定的比較需求,而運算子的行為不可修改。

  1. 垃圾回收機制
      垃圾回收是一種在堆記憶體中找出哪些物件在被使用,還有哪些物件沒被使用,並且將後者回收掉的機制。所謂使用中的物件,指的是程式中還有引用的物件;而未使用中的物件,指的是程

序中已經沒有引用的物件,該物件佔用的記憶體也可以被回收掉。垃圾回收的第一步是標記。垃圾回收器此時會找出記憶體哪些在使用中,哪些不是。垃圾回收的第二步是清除,這一步會刪掉標記

出的未引用物件。記憶體分配器會保留指向可用記憶體中的引用,以分配給新的物件。垃圾回收的第三步是壓縮,為了提升效能,刪除了未引用物件後,還可以將剩下的已引用物件放在一起(壓  

縮),這樣就能更簡單快捷地分配新物件了。逐一標記和壓縮 Java 虛擬機器中的所有物件非常低效:分配的物件越多,垃圾回收需要的時間就越久。不過,根據統計,大部分的物件,其實用沒

多久就不用了。

Java 堆(Java Heap)是 JVM 所管理的記憶體中最大的一塊,堆又是垃圾收集器管理的主要區域,這裡我們主要分析一下 Java 堆的結構。

Java 堆主要分為 2 個區域-年輕代與老年代,其中年輕代又分 Eden 區和 Survivor 區,其中 Survivor 區又分 From 和 To 2 個區。可能這時候大家會有疑問,為什麼需要 Survivor 區,為什麼 Survivor 還要分 2 個區。

大多數情況下,物件會在新生代 Eden 區中進行分配。當 Eden 區沒有足夠空間進行分配時,虛擬機器會發起一次 Minor GC,Minor GC 相比 Major GC 更頻繁,回收速度也更快。

透過 Minor GC 之後,Eden 會被清空,Eden 區中絕大部分物件會被回收,而那些無需回收的存活物件,將會進到 Survivor 的 From 區(若 From 區不夠,則直接進入 Old 區)。

Survivor 區相當於是 Eden 區和 Old 區的一個緩衝,類似於我們交通燈中的黃燈。Survivor 又分為 2 個區,一個是 From 區,一個是 To 區。每次執行 Minor GC,會將 Eden 區和 From 存活的物件放到 Survivor 的 To 區(如果 To 區不夠,則直接進入 Old 區)。

之所以有 Survivor 區是因為如果沒有 Survivor 區,Eden 區每進行一次 Minor GC,存活的物件就會被送到老年代,老年代很快就會被填滿。而有很多物件雖然一次 Minor GC 沒有消滅,但其實也並不會蹦躂多久,或許第二次,第三次就需要被清除。這時候移入老年區,很明顯不是一個明智的決定。

所以,Survivor 的存在意義就是減少被送到老年代的物件,進而減少 Major GC 的發生。Survivor 的預篩選保證,只有經歷 16 次 Minor GC 還能在新生代中存活的物件,才會被送到老年代。

設定兩個 Survivor 區最大的好處就是解決記憶體碎片化。

我們先假設一下,Survivor 如果只有一個區域會怎樣。Minor GC 執行後,Eden 區被清空了,存活的物件放到了 Survivor 區,而之前 Survivor 區中的物件,可能也有一些是需要被清除的。問題來了,這時候我們怎麼清除它們?在這種場景下,我們只能標記清除,而我們知道標記清除最大的問題就是記憶體碎片,在新生代這種經常會消亡的區域,採用標記清除必然會讓記憶體產生嚴重的碎片化。因為 Survivor 有 2 個區域,所以每次 Minor GC,會將之前 Eden 區和 From 區中的存活物件複製到 To 區域。第二次 Minor GC 時,From 與 To 職責互換,這時候會將 Eden 區和 To 區中的存活物件再複製到 From 區域,以此反覆。

這種機制最大的好處就是,整個過程中,永遠有一個 Survivor space 是空的,另一個非空的 Survivor space 是無碎片的。那麼,Survivor 為什麼不分更多塊呢?比方說分成三個、四個、五個?顯然,如果 Survivor 區再細分下去,每一塊的空間就會比較小,容易導致 Survivor 區滿,兩塊 Survivor 區可能是經過權衡之後的最佳方案。

老年代佔據著 2/3 的堆記憶體空間,只有在 Major GC 的時候才會進行清理,每次 GC 都會觸發“Stop-The-World”。記憶體越大,STW 的時間也越長,所以記憶體也不僅僅是越大就越好。在記憶體擔保機制下,無法安置的物件會直接進到老年代,以下幾種情況也會進入老年代。

1)大物件,指需要大量連續記憶體空間的物件,這部分物件不管是不是“朝生夕死”,都會直接進到老年代。這樣做主要是為了避免在 Eden 區及 2 個 Survivor 區之間發生大量的記憶體複製。

2)長期存活物件,虛擬機器給每個物件定義了一個物件年齡(Age)計數器。正常情況下物件會不斷的在 Survivor 的 From 區與 To 區之間移動,物件在 Survivor 區中每經歷一次 Minor GC,年齡就增加 1 歲。當年齡增加到 15 歲時,這時候就會被轉移到老年代。當然,這裡的 15,JVM 也支援進行特殊設定。

3)動態物件年齡,虛擬機器並不重視要求物件年齡必須到 15 歲,才會放入老年區,如果 Survivor 空間中相同年齡所有物件大小的總合大於 Survivor 空間的一半,年齡大於等於該年齡的物件就可以直接進去老年區,無需等你“成年”。

這其實有點類似於負載均衡,輪詢是負載均衡的一種,保證每臺機器都分得同樣的請求。看似很均衡,但每臺機的硬體不通,健康狀況不同,我們還可以基於每臺機接受的請求數,或每臺機的響應時間等,來調整我們的負載均衡演算法。

  1. String、StringBuffer、StringBuilder的區別
    在Java中,String、StringBuffer和StringBuilder都是用於處理字串的類,但它們在效能、執行緒安全性和可變性方面存在一些區別。

String(字串): String是Java中最常用的字串類,它是不可變的(immutable)。這意味著一旦建立了一個String物件,它的值就不能被修改。每次對String的操作(例如連線、替換等)都會建立一個新的String物件。這種不可變性使得String具有執行緒安全性,適合在多執行緒環境下使用。然而,頻繁的字串操作可能會導致記憶體開銷較大,因為每次操作都會建立新的物件。

1
2
3
String str = "Hello";
str += " World"; // 建立了一個新的String物件

StringBuffer(字串緩衝區): StringBuffer是可變的(mutable)字串類,它可以進行多次修改而無需建立新的物件。StringBuffer是執行緒安全的,適用於多執行緒環境下的字串操作。它提供了多個方法用於對字串進行修改、連線、插入和刪除等操作。

1
2
3
4
5
StringBuffer sb = new StringBuffer("Hello");
sb.append(" World"); // 在原物件上進行修改,無需建立新物件

由於StringBuffer是執行緒安全的,它的執行速度相對較慢。因此,如果在單執行緒環境下進行字串操作,推薦使用StringBuilder,因為它的執行速度更快。
StringBuilder(字串構建器): StringBuilder也是可變的字串類,類似於StringBuffer,它可以進行多次修改而無需建立新的物件。StringBuilder不是執行緒安全的,因此在多執行緒環境下使用時需要進行外部同步。由於不需要額外的執行緒安全檢查,StringBuilder的執行速度相對較快。

1
2
3
StringBuilder sb = new StringBuilder("Hello");
sb.append(" World"); // 在原物件上進行修改,無需建立新物件

總結:

如果需要頻繁操作字串,並且在多執行緒環境下使用,應該使用StringBuffer。
如果需要頻繁操作字串,但在單執行緒環境下使用,應該使用StringBuilder,因為它的執行速度更快。
如果不需要頻繁操作字串,或者字串是不可變的,可以使用String。
4. 操作字串常見的類及方法
String類:String是Java中最常用的字串類,它提供了許多方法來處理字串。以下是一些示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
String str1 = "Hello";
String str2 = "World";
 
// 連線字串
String result1 = str1 + str2; // 結果為 "HelloWorld"
 
// 獲取字串長度
int length = str1.length(); // 結果為 5
 
// 檢查字串是否為空
boolean isEmpty = str1.isEmpty(); // 結果為 false
 
// 檢查字串是否包含指定字元
boolean contains = str1.contains("H"); // 結果為 true
 
// 提取子字串
String subStr = str1.substring(1, 4); // 結果為 "ell"
 
// 替換字元
String replacedStr = str1.replace("H", "J"); // 結果為 "Jello"
 
// 拆分字串
String[] parts = str1.split("l"); // 結果為 ["He", "", "o"]
 
// 轉換為大寫或小寫
String upperCase = str1.toUpperCase(); // 結果為 "HELLO"
String lowerCase = str1.toLowerCase(); // 結果為 "hello"
StringBuilder類:StringBuilder用於構建可變字串,它提供了一系列方法來進行字串的拼接和修改。以下是一些示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
StringBuilder sb = new StringBuilder();
 
// 追加字串
sb.append("Hello");
sb.append("World");
 
// 插入字串
sb.insert(5, " ");
 
// 替換字串
sb.replace(6, 11, "Java");
 
// 刪除字元
sb.deleteCharAt(5);
 
// 反轉字串
sb.reverse();
 
String result2 = sb.toString(); // 結果為 "avaJdlroW"
StringBuffer類:StringBuffer與StringBuilder類似,也用於構建可變字串。不同的是,StringBuffer是執行緒安全的,適用於多執行緒環境下的字串操作。以下是一個示例:

1
2
3
4
5
6
StringBuffer buffer = new StringBuffer();
 
buffer.append("Hello");
buffer.append("World");
 
String result3 = buffer.toString(); // 結果為 "HelloWorld"
5. Static的用法和作用
static 是Java中的一個關鍵字,可以應用於變數、方法和程式碼塊。它具有以下幾種用法和作用:

靜態變數(Static Variables):使用 static 關鍵字宣告的變數稱為靜態變數,也稱為類變數。靜態變數屬於類而不是例項,它在類載入時被初始化,並且在整個程式執行期間保持不變。靜態變數可以透過類名直接訪問,無需建立類的例項。靜態變數常用於表示在類的所有例項之間共享的資料。

1
2
3
4
5
6
7
public class MyClass {
    static int count = 0; // 靜態變數
//程式碼效果參考:https://www.xx-ph.com/sitemap/post.xml
 
    public MyClass() {
        count++; // 每次建立例項時,靜態變數 count 自增
    }
}
靜態方法(Static Methods):使用 static 關鍵字宣告的方法稱為靜態方法。靜態方法屬於類而不是例項,它可以在類載入時直接呼叫,無需建立類的例項。靜態方法只能訪問靜態變數和呼叫其他靜態方法,不能直接訪問例項變數或呼叫例項方法。

1
2
3
4
5
public class MathUtils {
    public static int add(int a, int b) { // 靜態方法
        return a + b;
    }
}
靜態程式碼塊(Static Initialization Blocks):靜態程式碼塊用於在類載入時執行一些初始化操作。它使用 static 關鍵字定義,並用花括號括起來的程式碼塊。靜態程式碼塊只執行一次,且在類的第一次使用時執行。

1
2
3
4
5
6
public class MyClass {
    static {
        // 靜態程式碼塊
        // 執行一些初始化操作
    }
}
靜態程式碼塊通常用於初始化靜態變數或執行其他與類相關的初始化操作。

靜態匯入(Static Import):靜態匯入用於在程式碼中直接使用靜態成員(變數或方法),而無需使用類名限定符。透過使用 import static 語法,可以匯入靜態成員,使其在程式碼中可直接訪問。

1
2
3
4
5
6
7
import static java.lang.Math.PI;
 
public class MyClass {
    public double calculateArea(double radius) {
        return PI * radius * radius; // 直接使用靜態變數 PI,無需使用 Math.PI
    }
}

相關文章