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

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

กลไก

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

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

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

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

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

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

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

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

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

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

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

  • กระบวนการที่ไม่แสดง: กระบวนการที่ทำงานอยู่เบื้องหลังโดยไม่แสดง UI (เช่น บริการเบื้องหลังหรือตัวรับสัญญาณการออกอากาศ) เนื่องจากกระบวนการเหล่านี้ไม่ได้แสดงคอมโพเนนต์ 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 จริงที่ระบบปฏิบัติการเข้าถึงได้หลังจากหักการแบ่งหน่วยความจำของฮาร์ดแวร์และเฟิร์มแวร์แล้ว การแบ่งหน่วยความจำจะสงวน 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

ขีดจำกัดพื้นที่สว็อป (memory.swap.max) ใน MiB ที่ใช้กับกระบวนการที่แสดง

swapNotVisible

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

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

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

RAM จริงเป้าหมาย ช่วง MemTotal [floor, ceiling)
(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 ที่ติดตั้งไว้ล่วงหน้า ตัวจำกัดหน่วยความจำไม่รองรับรายการที่อนุญาตเพื่อยกเว้นแอปที่เฉพาะเจาะจง ซึ่งช่วยให้มั่นใจได้ถึงการจัดการหน่วยความจำที่เป็นธรรมในทุกแอป

  • ไม่มี 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 จะยกเว้น 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

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

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