ตัวจำกัดหน่วยความจำ

Android 17 ขึ้นไปมีตัวจำกัดหน่วยความจำ ซึ่งเป็นบริการของระบบที่ตรวจสอบและจำกัดการใช้งานหน่วยความจำของกระบวนการแอปพลิเคชันโดยใช้ Linux cgroup v2 ตัวจำกัดหน่วยความจำจะป้องกันไม่ให้แอปแต่ละแอปใช้หน่วยความจำของระบบมากเกินไป ซึ่งจะช่วยลดแรงกดดันด้านหน่วยความจำโดยรวม และป้องกันการหยุดทำงานเนื่องจากหน่วยความจำไม่เพียงพอ (LMK) อย่างรุนแรงของกระบวนการที่สำคัญในเบื้องหน้าและกระบวนการที่แคชไว้

กลไก

ตัวจำกัดหน่วยความจำจะผสานรวมกับ Activity Manager Service (AMS) เพื่อติดตามเหตุการณ์ในวงจรของกระบวนการและการเปลี่ยนแปลงสถานะ ตัวจำกัดหน่วยความจำจะบังคับใช้ขีดจำกัดหน่วยความจำโดยใช้ลำดับชั้น cgroup v2 ของเคอร์เนล Linux

เคอร์เนลของอุปกรณ์ต้องเปิดใช้ cgroup v2 และตัวควบคุม memory เพื่อรองรับตัวจำกัดหน่วยความจำ บริการนี้อาศัยแอตทริบิวต์ต่อไปนี้โดยเฉพาะ

memory.high
ขีดจำกัดแบบไม่เข้มงวด เมื่อเกินขีดจำกัด กระบวนการจะถูกจำกัดและเคอร์เนลจะพยายามเรียกคืนหน้าเว็บเชิงรุกจากกระบวนการนั้น
memory.swap.max
จำกัดจำนวนพื้นที่ Swap สูงสุด (เช่น ZRAM) ที่กระบวนการสามารถใช้ได้

ผลกระทบต่อแอป

แอปที่ทำงานภายในขีดจำกัดหน่วยความจำจะไม่ได้รับผลกระทบจากตัวจำกัดหน่วยความจำ

เมื่อแอปใช้หน่วยความจำเกินขีดจำกัด memory.high เคอร์เนลจะล้างหน่วยความจำที่สำรองไว้สำหรับไฟล์ที่ไม่มีการแก้ไขของแอป และสลับหน่วยความจำที่ไม่ระบุตัวตนที่ไม่ได้ใช้งานออกเพื่อรักษาการใช้หน่วยความจำของแอปให้อยู่ภายในขีดจำกัด ผลจากการขับไล่หน้าและการสลับกิจกรรมนี้ แอปอาจพบการควบคุมการดำเนินการชั่วคราว แต่แอปจะยังคงทำงานต่อไป

ในสถานการณ์ที่รุนแรง เช่น หน่วยความจำรั่วไหลที่ควบคุมไม่ได้ซึ่งแอปยังคงจัดสรรหน่วยความจำที่ไม่ระบุชื่อหลังจากพื้นที่ Swap หมดลง แอปจะจัดสรรหน่วยความจำไม่สำเร็จและถูกสิ้นสุดการทำงาน

การตรวจสอบกระบวนการ

ตัวจำกัดหน่วยความจำจะตรวจสอบกระบวนการของแอป (UID >= 10000) โดยค่าเริ่มต้น กระบวนการหลักของระบบจะได้รับการยกเว้นเพื่อรักษาความเสถียรที่จำเป็นของระบบ

ตัวจำกัดหน่วยความจำจะกำหนดขีดจำกัดหน่วยความจำตามสถานะระดับการเข้าถึงของกระบวนการ ดังนี้

  • กระบวนการที่มองเห็นได้: กระบวนการที่โฮสต์ UI ที่ผู้ใช้มองเห็นได้ (เช่น กิจกรรมที่โฟกัสอยู่หรือการโต้ตอบที่ใช้งานอยู่) เนื่องจากการแสดง UI ต้องใช้ชุดการทำงานที่ใหญ่ขึ้นสำหรับการแสดงผลและไปป์ไลน์กราฟิก กระบวนการที่มองเห็นได้จึงได้รับขีดจำกัดหน่วยความจำที่มากขึ้น

  • กระบวนการที่มองไม่เห็น: กระบวนการที่ทำงานอยู่เบื้องหลังโดยไม่แสดง UI (เช่น บริการที่ทำงานอยู่เบื้องหลังหรือ Broadcast Receiver) เนื่องจากไม่ได้แสดงผลคอมโพเนนต์ UI จึงทำงานภายใต้ขีดจำกัดที่เข้มงวดกว่า

ตารางต่อไปนี้จะจับคู่สถานะกระบวนการที่เฉพาะเจาะจงกับการจัดประเภทขีดจำกัดหน่วยความจำ

สถานะการประมวลผลขีดจำกัดหน่วยความจำ
PERSISTENTไม่จำกัด
PERSISTENT_UIไม่จำกัด
TOPแสดง
BOUND_TOPแสดง
FOREGROUND_SERVICEไม่แสดง
BOUND_FOREGROUND_SERVICEไม่แสดง
IMPORTANT_FOREGROUNDแสดง
IMPORTANT_BACKGROUNDไม่แสดง
TRANSIENT_BACKGROUNDไม่แสดง
BACKUPไม่แสดง
SERVICEไม่แสดง
RECEIVERไม่แสดง
TOP_SLEEPINGแสดง
HEAVY_WEIGHTไม่แสดง
HOMEไม่แสดง
LAST_ACTIVITYไม่แสดง
CACHED_ACTIVITYแคชไว้
CACHED_ACTIVITY_CLIENTแคชไว้
CACHED_RECENTแคชไว้
CACHED_EMPTYแคชไว้

ในสถานะแคช ระบบจะหยุดกระบวนการทำงานชั่วคราวและเรียกคืนทรัพยากรให้ได้มากที่สุด

เมื่อกระบวนการใช้หน่วยความจำเกินขีดจำกัด memory.high ที่กำหนดไว้ ตัวจำกัดหน่วยความจำจะตรวจพบเหตุการณ์และสามารถทริกเกอร์การดำเนินการวินิจฉัย เช่น บันทึกโปรไฟล์หน่วยความจำหรือบันทึกความผิดปกติลงใน statsd

การกำหนดค่า

ใน Android 17 ขึ้นไป ตัวจำกัดหน่วยความจำจะได้รับการกำหนดค่าโดยใช้ไฟล์การกำหนดค่าแพลตฟอร์มมาตรฐานในพาร์ติชันระบบที่ /system/etc/memory-limiter-config.xml

รูปแบบ XML

ไฟล์การกำหนดค่าเป็นไปตามสคีมาที่กำหนดไว้ใน memory-limiter-config.xsd ไฟล์จะกำหนดชุดขีดจำกัดหลายชุดที่จัดเรียงตามหน่วยความจำที่พร้อมใช้งาน โดยบริการจะเลือกชุดขีดจำกัดที่ตรงกันสูงสุดตาม RAM ที่พร้อมใช้งานของอุปกรณ์ ค่าหน่วยความจำทั้งหมดจะระบุเป็นเมบิไบต์ (MiB)

<MemoryLimiterConfig>
  <version>1</version>
  <configList>
    <!-- RAM boundaries account for MemTotal excluding carve-outs. -->
    <limitSet>
      <!-- Limits for 16GB RAM device [14336, 18432): 10G/5G/5G/5G -->
      <minimumRequiredMemTotal>14336</minimumRequiredMemTotal>
      <maximumAllowedMemTotal>18432</maximumAllowedMemTotal>
      <memVisible>10240</memVisible>
      <memNotVisible>5120</memNotVisible>
      <swapVisible>5120</swapVisible>
      <swapNotVisible>5120</swapNotVisible>
    </limitSet>
    <limitSet>
      <!-- Limits for 12GB RAM device [9216, 14336): 8G/4G/4G/4G -->
      <minimumRequiredMemTotal>9216</minimumRequiredMemTotal>
      <maximumAllowedMemTotal>14336</maximumAllowedMemTotal>
      <memVisible>8192</memVisible>
      <memNotVisible>4096</memNotVisible>
      <swapVisible>4096</swapVisible>
      <swapNotVisible>4096</swapNotVisible>
    </limitSet>
    <limitSet>
      <!-- Limits for 8GB RAM device [6800, 9216): 5G/3G/3G/3G -->
      <minimumRequiredMemTotal>6800</minimumRequiredMemTotal>
      <maximumAllowedMemTotal>9216</maximumAllowedMemTotal>
      <memVisible>5120</memVisible>
      <memNotVisible>3072</memNotVisible>
      <swapVisible>3072</swapVisible>
      <swapNotVisible>3072</swapNotVisible>
    </limitSet>
    <limitSet>
      <!-- Limits for 6GB RAM device [4800, 6800): 4G/2G/2G/2G -->
      <minimumRequiredMemTotal>4800</minimumRequiredMemTotal>
      <maximumAllowedMemTotal>6800</maximumAllowedMemTotal>
      <memVisible>4096</memVisible>
      <memNotVisible>2048</memNotVisible>
      <swapVisible>2048</swapVisible>
      <swapNotVisible>2048</swapNotVisible>
    </limitSet>
    <limitSet>
      <!-- Limits for 4GB RAM device [3200, 4800): 2G/1G/1G/1G -->
      <minimumRequiredMemTotal>3200</minimumRequiredMemTotal>
      <maximumAllowedMemTotal>4800</maximumAllowedMemTotal>
      <memVisible>2048</memVisible>
      <memNotVisible>1024</memNotVisible>
      <swapVisible>1024</swapVisible>
      <swapNotVisible>1024</swapNotVisible>
    </limitSet>
  </configList>
</MemoryLimiterConfig>
version
จำนวนเต็มบวกที่ระบุเวอร์ชันการกำหนดค่า ต้องเป็น 1
minimumRequiredMemTotal

MemTotal ขั้นต่ำของระบบ (เป็น MiB, รวมค่าต่ำสุด) ที่จำเป็นสำหรับการใช้ชุดขีดจำกัดนี้ บริการจะประเมินค่านี้เทียบกับหน่วยความจำทั้งหมดที่ใช้ได้ซึ่งรายงานโดยเคอร์เนลใน /proc/meminfo

ทำความเข้าใจการแบ่งส่วนและขอบเขตของหน่วยความจำ: ค่า MemTotal ของเคอร์เนลจะแสดง DRAM ทางกายภาพที่ระบบปฏิบัติการเข้าถึงได้หลังจากหักการแบ่งส่วนหน่วยความจำของฮาร์ดแวร์และเฟิร์มแวร์ออกแล้ว Carve-out จะสำรอง RAM ไว้สำหรับคอมโพเนนต์ฮาร์ดแวร์เฉพาะ เช่น GPU, โมเด็มเบสแบนด์, ISP ของกล้อง, สภาพแวดล้อมการดำเนินการที่ปลอดภัย และไฮเปอร์ไวเซอร์ ซึ่งโดยปกติจะใช้หน่วยความจำกายภาพระหว่าง 500 MiB ถึงมากกว่า 2 GiB

เนื่องจาก MemTotal ต่ำกว่า DRAM จริงที่โฆษณาไว้ของอุปกรณ์ เกณฑ์ minimumRequiredMemTotal จึงรวมส่วนต่างสำหรับการกันหน่วยความจำเหล่านี้ไว้ด้วย ตัวอย่างเช่น การกำหนดค่าที่กำหนดเป้าหมายอุปกรณ์ขนาด 16 GB จะระบุขีดจำกัดต่ำสุดเป็น 14336 (14 GiB) แทนที่จะเป็น 16384 (16 GiB) และระดับสำหรับอุปกรณ์ขนาด 12 GB จะระบุขีดจำกัดต่ำสุดเป็น 9216 (9 GiB) วิธีนี้ช่วยให้มั่นใจได้ว่าอุปกรณ์แต่ละเครื่องจะตรงกับระดับที่ต้องการ ไม่ว่าจะมีรูปแบบการแยกส่วนระดับบอร์ดอย่างไรก็ตาม

maximumAllowedMemTotal

MemTotal สูงสุดของระบบ (เป็น MiB, เพดานพิเศษ) สำหรับชุดขีดจำกัดนี้ที่จะใช้ หาก MemTotal มากกว่าหรือเท่ากับเพดานนี้ ขีดจำกัดที่ตั้งไว้จะไม่มีผล สำหรับอุปกรณ์ที่มีหน่วยความจำ 18,432 MiB ขึ้นไปหรืออุปกรณ์ที่มีหน่วยความจำน้อยกว่า 3,200 MiB ตัวจำกัดหน่วยความจำจะไม่ใช้ขีดจำกัดใดๆ และปล่อยให้กระบวนการทำงานโดยไม่มีข้อจำกัด

memVisible

ขีดจำกัดหน่วยความจำแบบซอฟต์ (memory.high) ในหน่วย MiB ที่ใช้กับกระบวนการที่มองเห็นได้

memNotVisible

ขีดจำกัดหน่วยความจำแบบซอฟต์ (memory.high) ในหน่วย MiB ที่ใช้กับกระบวนการที่ไม่แสดง

swapVisible

ขีดจำกัดพื้นที่ Swap (memory.swap.max) ในหน่วย MiB ที่ใช้กับกระบวนการที่มองเห็นได้

swapNotVisible

ขีดจำกัดพื้นที่ Swap (memory.swap.max) ในหน่วย MiB ที่ใช้กับกระบวนการที่ไม่แสดง

ขีดจำกัดมาตรฐานของแพลตฟอร์ม

ใน Android 17 ขึ้นไป แพลตฟอร์มจะระบุขีดจำกัดหน่วยความจำมาตรฐานและขีดจำกัดสวอปใน /system/etc/memory-limiter-config.xml สำหรับระดับ RAM จริงที่พบบ่อยโดยใช้ช่วง [floor, ceiling) ดังนี้

RAM จริงเป้าหมาย ช่วง MemTotal [ต่ำสุด, สูงสุด)
(minimumRequiredMemTotal,
maximumAllowedMemTotal)
ขีดจำกัดหน่วยความจำที่มองเห็นได้
(memVisible)
ขีดจำกัดหน่วยความจำที่มองไม่เห็น
(memNotVisible)
ขีดจำกัดการสลับที่มองเห็นได้
(swapVisible)
ขีดจำกัดการสลับแบบไม่แสดง
(swapNotVisible)
16 GB [14336, 18432) MiB 10,240 MiB (10 GiB) 5,120 MiB (5 GiB) 5,120 MiB (5 GiB) 5,120 MiB (5 GiB)
12 GB [9216, 14336) MiB 8,192 MiB (8 GiB) 4,096 MiB (4 GiB) 4,096 MiB (4 GiB) 4,096 MiB (4 GiB)
8 GB [6800, 9216) MiB 5,120 MiB (5 GiB) 3,072 MiB (3 GiB) 3,072 MiB (3 GiB) 3,072 MiB (3 GiB)
6 GB [4800, 6800) MiB 4,096 MiB (4 GiB) 2,048 MiB (2 GiB) 2,048 MiB (2 GiB) 2,048 MiB (2 GiB)
4 GB [3200, 4800) MiB 2,048 MiB (2 GiB) 1,024 MiB (1 GiB) 1,024 MiB (1 GiB) 1,024 MiB (1 GiB)

หลักการจำกัดหน่วยความจำ

การกำหนดค่าตัวจำกัดหน่วยความจำได้รับการออกแบบตามหลักการของแพลตฟอร์มต่อไปนี้

  • ความสอดคล้องกันของระบบนิเวศและความเข้ากันได้ของแอป: ขีดจำกัดหน่วยความจำที่สอดคล้องกันในอุปกรณ์ต่างๆ ช่วยให้มั่นใจได้ว่าประสิทธิภาพของแอปจะคาดการณ์ได้ทั่วทั้งระบบนิเวศของ Android แอปได้รับการพัฒนาและทดสอบตามความคาดหวังด้านหน่วยความจำมาตรฐาน การรักษาขีดจำกัดของแพลตฟอร์มให้สม่ำเสมอจะช่วยหลีกเลี่ยงการควบคุมปริมาณที่ไม่คาดคิดหรือการสิ้นสุดก่อนเวลาอันควร

  • การจัดสรรทรัพยากรตามสัดส่วน: ขีดจำกัดของแพลตฟอร์มจะได้รับการปรับเทียบตามความจุ RAM จริง ดังนี้

    • กระบวนการที่มองเห็นได้: จัดสรร RAM จริงประมาณ 1/2 ถึง 2/3 ของทั้งหมดเพื่อรองรับ UI ที่ใช้งานอยู่, องค์ประกอบ และภาระงานการแสดงผล
    • กระบวนการที่ไม่แสดง: จัดสรร RAM จริงประมาณ 1/4 ถึง 1/3 ของทั้งหมดสำหรับงานเบื้องหลัง
  • การบังคับใช้แบบครอบคลุม: ขีดจำกัดจะมีผลกับกระบวนการทั้งหมดของแอปพลิเคชันในอุปกรณ์อย่างเท่าเทียมกัน (UID >= 10000) ซึ่งรวมถึงแอปของระบบและแอป OEM ที่ติดตั้งล่วงหน้า ตัวจำกัดหน่วยความจำไม่รองรับรายการที่อนุญาตเพื่อยกเว้นแอปที่เฉพาะเจาะจง เพื่อให้มั่นใจว่ามีการกำกับดูแลหน่วยความจำอย่างเป็นธรรมในแอปทั้งหมด หากต้องการกำหนดค่าขีดจำกัดขั้นต่ำในการป้องกัน memory.min ที่ปรับแต่งเองใน Daemon แบบอินเทอร์แอกทีฟที่สำคัญ และขีดจำกัดสูงสุด memory.high ที่ปรับเทียบภาระงานให้เข้มงวดยิ่งขึ้นในแอปและบริการของระบบแต่ละรายการ โปรดดูงบประมาณหน่วยความจำของระบบ

  • ไม่มี API การค้นหารันไทม์: ใน Android 17 ขึ้นไป แอปจะไม่สามารถค้นหาขีดจำกัดหน่วยความจำที่กำหนดไว้แบบเป็นโปรแกรมขณะรันไทม์ได้ ขีดจำกัดของแพลตฟอร์มมาตรฐานได้รับการกำหนดไว้โดยมีระยะเผื่อที่กว้างขวาง เพื่อให้แอปที่ทำงานอย่างเหมาะสมสามารถทำงานได้โดยไม่มีข้อจำกัดในระหว่างการใช้งานปกติ

  • การเรียกคืนเคอร์เนลและไดนามิกของการสลับ: เมื่อกระบวนการของแอปใกล้ถึงขีดจำกัด memory.high เคอร์เนล Linux จะกำหนดให้มีการเรียกคืนหน่วยความจำโดยเฉพาะที่ cgroup ของกระบวนการนั้น ซึ่งรวมถึงการทิ้งหน้าเว็บที่สำรองข้อมูลไฟล์ที่ไม่ได้ใช้งานและการสลับหน้าเว็บที่ไม่ระบุตัวตนที่ไม่ได้ใช้งานไปยัง ZRAM เพื่อรักษาความลื่นไหลของระบบโดยไม่ทำให้หน่วยความจำส่วนกลางทำงานหนักเกินไป

คำสั่ง Shell

คำสั่ง am memory-limiter ช่วยให้นักพัฒนาซอฟต์แวร์และผู้ผสานรวมระบบโต้ตอบกับบริการได้ขณะรันไทม์เพื่อการพัฒนา การทดสอบ และการแก้ไขข้อบกพร่อง ดังนี้

am memory-limiter <SUB-COMMAND>

สถานะ

คำสั่งย่อย status จะรายงานสถานะการทำงานและเมตริกที่ใช้งานอยู่ของตัวจำกัดหน่วยความจำ

adb shell am memory-limiter status

ตัวอย่างเอาต์พุตมีดังนี้

Memory limiter
  enabled                  monitoring=true          ignored=none
  visibleMem=1948MB        visibleSwap=974MB
  notVisibleMem=974MB      notVisibleSwap=487MB
  started=36               watched=36               watch-failed=0
  events=0                 processes=36             process-hwm=36

ช่องสำคัญในเอาต์พุตมีดังนี้

monitoring
ระบุว่าตัวจำกัดหน่วยความจำกำลังตรวจสอบกระบวนการอยู่หรือไม่
visibleMem และ notVisibleMem
ขีดจำกัดหน่วยความจำสัมบูรณ์ที่คำนวณได้ (memory.high) ซึ่งบังคับใช้ในปัจจุบันสำหรับสถานะระดับการมองเห็นแต่ละสถานะ
visibleSwap และ notVisibleSwap
ขีดจำกัดการสลับแบบสัมบูรณ์ที่คำนวณได้ (memory.swap.max) ซึ่งบังคับใช้อยู่ในปัจจุบันสำหรับสถานะการมองเห็นแต่ละสถานะ
events
จำนวนครั้งที่กระบวนการเกินขีดจำกัดที่กำหนดไว้
processes
จำนวนกระบวนการที่ตรวจสอบอยู่ในปัจจุบัน

ignore

คำสั่งย่อย ignore จะยกเว้น UID ที่เฉพาะเจาะจงหรือกระบวนการทั้งหมดจากการจำกัดหน่วยความจำชั่วคราว ซึ่งมีประโยชน์สำหรับการเปรียบเทียบประสิทธิภาพ การทดสอบความเครียด หรือการวิเคราะห์ลักษณะการทำงานของหน่วยความจำ ดังนี้

# Ignore a specific UID
adb shell am memory-limiter ignore 10087
# Ignore all processes (temporarily disables limiting)
adb shell am memory-limiter ignore all
# Resume normal limiting operation
adb shell am memory-limiter ignore none

ด้วยตนเอง

คำสั่งย่อย manual จะลบล้างขีดจำกัดที่คำนวณไว้สำหรับกระบวนการที่เฉพาะเจาะจงตามรหัสกระบวนการ (PID) ด้วยค่าสัมบูรณ์ที่กำหนดเองเป็นไบต์ ค่าต้องเป็นจำนวนเต็ม แต่สามารถใส่คำต่อท้าย MB เพื่อระบุ MiB หรือคำต่อท้าย GB เพื่อระบุ GiB ได้ ดังนี้

# Set a 1GiB limit for PID 1234
adb shell am memory-limiter manual 1234 1073741824
# Set a 1GiB limit for PID 1234
adb shell am memory-limiter manual 1234 1024MB
# Set a 1GiB limit for PID 1234
adb shell am memory-limiter manual 1234 1GB
# Remove the manual override for PID 1234
adb shell am memory-limiter manual 1234 none

การลบล้างด้วยตนเองจะมีผลเฉพาะตลอดอายุการใช้งานของอินสแตนซ์กระบวนการนั้นๆ เท่านั้น หากกระบวนการรีสตาร์ท ระบบจะกลับไปใช้ขีดจำกัดมาตรฐานตามสถานะของกระบวนการ

การลบล้างด้วยตนเองต้องไม่เกินขีดจำกัดของระบบจริง