程式執行環境 FAQ

綜合 更新於 Oct 9, 2026

一個程式怎麼被載入、在記憶體裡怎麼擺、又怎麼被執行 —— 系統與語言內部機制面試常問的 那些概念。

1) 行程(process)與執行緒(thread)

行程 Process 執行緒 Thread
定義 一個正在執行的程式實例 行程內部的一個執行單位
記憶體 有自己隔離的位址空間 共用行程的 heap 與程式碼
擁有 程式碼、heap、檔案 handle、PCB 自己的 stack、暫存器、程式計數器
溝通 IPC(pipe、socket、共享記憶體) 共享記憶體(便宜,但需要同步)
成本 建立/切換都貴 建立/切換都輕
失敗影響 崩潰只影響該行程 一條掛掉可能拖垮整個行程

重點

  • 執行緒共用 heap → 需要同步機制(鎖、atomic)來避免資料競態。每條執行緒有 自己的 stack。
  • 行程之間的 context switch(要換位址空間、刷 TLB)比同一行程內執行緒之間的切換貴。
  • 並行(concurrency)=多個任務在重疊的時間區間內各自推進; 平行(parallelism)=多個任務真的在同一瞬間跑在多顆核心上。

2) 程式的記憶體佈局

典型的行程位址空間,由低位址到高位址:

text
+------------------------+  high address
|        Stack           |  grows down ↓  (local vars, call frames, return addrs)
+------------------------+
|          ↓             |
|                        |
|          ↑             |
+------------------------+
|         Heap           |  grows up ↑    (dynamic allocation: malloc/new)
+------------------------+
|         BSS            |  uninitialized globals/statics (zeroed)
+------------------------+
|         Data           |  initialized globals/statics
+------------------------+
|         Text           |  program code (read-only, executable)
+------------------------+  low address
  • Stack:快、自動、後進先出。每次函式呼叫推入一個 frame,返回就彈出。大小有 上限 → 太深或無窮遞迴會造成 stack overflow。
  • Heap:手動或由 GC 管理,生命週期是動態的,較慢,而且會碎片化。 記憶體洩漏就住在這裡(還可達但用不到,或是沒有釋放的記憶體)。
  • Text/Data/BSS:載入時就固定了。

Stack 與 Heap(面試必考)

Stack Heap
配置 自動(依作用域) 明確配置/GC
速度 非常快(移一下指標) 較慢
生命週期 到函式返回為止 到被釋放/不可達為止
大小 小、固定 大、有彈性
執行緒 每條執行緒一個 共用

3) 一個程式是怎麼被載入與執行的

text
Source code
   │  compile / assemble
   ▼
Object files ──link──► Executable (or bytecode / script)
   │
   ▼  OS loader
Loaded into memory: text, data segments mapped; stack & heap set up
   │
   ▼
Entry point (e.g. main) → CPU fetch-decode-execute cycle
  1. 編譯/建置:原始碼 → 機器碼或 bytecode。連結(linking)負責解析符號 (靜態連結=直接包進去;動態連結=載入或執行時透過共享函式庫解析)。
  2. 載入:OS 的 loader 建立位址空間、映射各段、設好 stack、載入動態函式庫, 然後跳到程式入口點。
  3. 執行:CPU 跑 fetch–decode–execute 循環;OS 排程器把執行緒分時排到核心上; 系統呼叫(syscall)則跨進 kernel mode 去做 I/O 之類的事。

4) 編譯式、直譯式與 JIT

模式 做法 優點 缺點
編譯式(AOT) 執行前先把原始碼編成原生機器碼 啟動與執行都快,不需要 runtime 綁平台、建置循環較慢
直譯式 執行時邊讀邊執行指令 可攜、開發循環快、有彈性 執行較慢
JIT 先直譯 bytecode,執行時再把熱點編成原生碼 接近原生的速度+可攜性 有暖機成本、記憶體用量較高
  • Java 把原始碼編成 bytecode(.class),再由 JVM 執行。 JVM 一開始是直譯,同時收集執行剖析資料,之後由 JIT 編譯器(C1/C2)把熱門 方法編成原生碼,並套用積極的最佳化(inline、迴圈展開、逃逸分析)。
  • 這也是為什麼 Java 要跑一段「暖機」時間才會到達最高吞吐量。

5) 垃圾回收基礎

自動記憶體管理:回收 heap 上已經不可達的物件。

  • 可達性:只要能從 GC root(stack 上的參考、static 欄位、活著的執行緒) 走到的物件就是活的。其他都是垃圾。
  • 世代假說:大部分物件都很早死。所以 heap 分成新生代(Eden + survivor)與 老年代。
    • Minor GC 收新生代(頻繁、便宜)。
    • Major/full GC 收老年代(較少、較貴)。
  • 常見演算法:mark-and-sweep、mark-compact、copying collector。
  • **Stop-the-world(STW)**會在 GC(的某些階段)凍結應用執行緒;現代的收集器 (G1、ZGC、Shenandoah)把停頓時間壓到最小。
  • 取捨:GC 消滅了一整類 bug(use-after-free、double-free),代價是吞吐量、 額外記憶體開銷,以及停頓時間不好預測。

沒有 GC 的語言(C、C++、Rust)靠手動管理或所有權機制,用安全性與心力換取控制權與 可預測的延遲。


6) Runtime 執行環境

Runtime 是程式執行期間提供各種服務的軟體層:記憶體管理、排程、標準函式庫,以及 對平台的抽象。

  • JVM(Java):載入 bytecode、驗證、JIT 編譯,並管理 GC 與執行緒。 「一次撰寫,到處執行」—— bytecode 在各家 JVM 實作之間是可攜的。
  • 語言 VM/直譯器:它們負責解析與執行。其中受管理的那些還會帶自己的記憶體 管理 —— 但那是實作的性質,不是「身為直譯器」就有的(嵌在宿主程式裡的 Lua 或 C 直譯器可能完全沒有)。而會不會同時做 JIT 則因實作而異:V8(JavaScript)會,而且很積極; CPython 在標準版本裡不會 —— 它直譯 bytecode,並用參考計數加上環收集器 回收記憶體(3.13 附了一個實驗性的 JIT,而 PyPy 是有 JIT 的另一個實作)。
  • OS 也是一種 runtime:提供行程、執行緒、虛擬記憶體與系統呼叫。
  • 容器:把應用程式+它的 runtime+依賴一起打包,讓它在各種環境下的執行一致 (靠 namespace/cgroup 隔離,不是完整的虛擬機)。

關鍵想法:同一個程式在不同 runtime 下可以有不同表現(GC 參數、JIT 暖機、 可用的核心數與記憶體),所以效能測試一定要在具代表性的環境裡跑。