मेमोरी लिमिटर

Android 17 और इसके बाद के वर्शन में, मेमोरी लिमिटर की सुविधा शामिल है. यह एक सिस्टम सर्विस है, जो Linux cgroup v2 का इस्तेमाल करके, ऐप्लिकेशन प्रोसेस की मेमोरी के इस्तेमाल की निगरानी करती है और उसे सीमित करती है. मेमोरी लिमिटर, अलग-अलग ऐप्लिकेशन को सिस्टम की ज़रूरत से ज़्यादा मेमोरी इस्तेमाल करने से रोकता है. इससे, मेमोरी पर पड़ने वाला कुल दबाव कम होता है. साथ ही, ज़रूरी फ़ोरग्राउंड और कैश मेमोरी में सेव की गई प्रोसेस के लिए, कम मेमोरी होने पर उन्हें बंद होने से बचाया जा सकता है.

मैकेनिज़्म

मेमोरी लिमिटर, प्रोसेस के लाइफ़साइकल इवेंट और स्थिति में होने वाले बदलावों को ट्रैक करने के लिए, ऐक्टिविटी मैनेजर सर्विस (एएमएस) के साथ इंटिग्रेट होता है. मेमोरी लिमिटर, Linux कर्नल cgroup v2 के क्रम के हिसाब से, मेमोरी की सीमाओं को लागू करता है.

मेमोरी लिमिटर के साथ काम करने के लिए, डिवाइस के कर्नल में cgroup v2 और memory कंट्रोलर चालू होना चाहिए. यह सर्विस, खास तौर पर इन एट्रिब्यूट पर निर्भर करती है:

memory.high
यह एक सॉफ़्ट लिमिट है. इस सीमा से ज़्यादा मेमोरी इस्तेमाल होने पर, प्रोसेस को थ्रॉटल किया जाता है. साथ ही, कर्नल उस प्रोसेस से पेज को वापस पाने की कोशिश करता है.
memory.swap.max
यह स्वैप स्पेस (जैसे, ZRAM) की उस ज़्यादा से ज़्यादा सीमा को तय करता है जिसका इस्तेमाल प्रोसेस कर सकती है.

ऐप्लिकेशन पर असर

मेमोरी लिमिटर का असर उन ऐप्लिकेशन पर नहीं पड़ता जो अपनी मेमोरी की सीमाओं के अंदर काम करते हैं.

जब कोई ऐप्लिकेशन, memory.high की सीमा को पार कर जाता है, तो कर्नल उस ऐप्लिकेशन की साफ़ फ़ाइल-बैक मेमोरी को हटा देता है. साथ ही, उसकी इस्तेमाल न की जा रही एनॉनिमस मेमोरी को स्वैप कर देता है, ताकि ऐप्लिकेशन सीमा के अंदर रहे. पेज को हटाने और स्वैप करने की इस गतिविधि की वजह से, ऐप्लिकेशन को अस्थायी तौर पर थ्रॉटल किया जा सकता है. हालांकि, यह चलता रहता है.

कुछ मामलों में, जैसे कि अनकंट्रोल मेमोरी लीक होने पर, ऐप्लिकेशन स्वैप स्पेस खत्म होने के बाद भी एनॉनिमस मेमोरी को असाइन करता रहता है. ऐसे में, ऐप्लिकेशन मेमोरी को असाइन नहीं कर पाता और बंद हो जाता है.

प्रोसेस की निगरानी करना

मेमोरी लिमिटर, डिफ़ॉल्ट रूप से ऐप्लिकेशन प्रोसेस (यूआईडी >= 10000) की निगरानी करता है. सिस्टम की स्थिरता बनाए रखने के लिए, कोर सिस्टम प्रोसेस को निगरानी से छूट दी जाती है.

मेमोरी लिमिटर, प्रोसेस की दिखने की स्थिति के आधार पर मेमोरी की सीमाएं तय करता है:

  • दिखने वाली प्रोसेस: ऐसी प्रोसेस जो उपयोगकर्ता को दिखने वाला यूज़र इंटरफ़ेस (यूआई) होस्ट करती हैं. जैसे, फ़िलहाल फ़ोकस में मौजूद ऐक्टिविटी या चालू इंटरैक्शन. यूज़र इंटरफ़ेस (यूआई) दिखाने के लिए, रेंडरिंग और ग्राफ़िक्स पाइपलाइन के लिए ज़्यादा वर्किंग सेट की ज़रूरत होती है. इसलिए, दिखने वाली प्रोसेस को ज़्यादा मेमोरी की सीमा मिलती है.

  • नहीं दिखने वाली प्रोसेस: ऐसी प्रोसेस जो यूज़र इंटरफ़ेस (यूआई) दिखाए बिना, बैकग्राउंड में काम करती हैं. जैसे, बैकग्राउंड में चलने वाली सेवाएं या ब्रॉडकास्ट रिसीवर. ये यूज़र इंटरफ़ेस (यूआई) कॉम्पोनेंट को रेंडर नहीं करती हैं. इसलिए, ये ज़्यादा पाबंदियों वाली सीमा के तहत काम करती हैं.

यहां दी गई टेबल में, प्रोसेस की अलग-अलग स्थितियों को मेमोरी की सीमा के हिसाब से कैटगरी में बांटा गया है:

प्रोसेस की स्थितिमेमोरी की सीमा
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 और इसके बाद के वर्शन में, मेमोरी लिमिटर, सिस्टम पार्टिशन पर प्लैटफ़ॉर्म की डिफ़ॉल्ट कॉन्फ़िगरेशन फ़ाइल उपलब्ध कराता है. साथ ही, vendor पार्टिशन पर डिवाइस के हिसाब से, वैकल्पिक तौर पर बदलाव करने की सुविधा भी देता है:

  • सिस्टम की डिफ़ॉल्ट कॉन्फ़िगरेशन: /system/etc/memory-limiter-config.xml
  • वेंडर के हिसाब से बदलाव (ज़रूरी नहीं): /vendor/etc/memory-limiter-config.xml

मेमोरी लिमिटर, सबसे पहले /vendor/etc/memory-limiter-config.xml की जांच करता है. अगर यह फ़ाइल मौजूद है, तो यह वेंडर के कॉन्फ़िगरेशन का इस्तेमाल करता है. ऐसा न होने पर, यह /system/etc/memory-limiter-config.xml में मौजूद सिस्टम के डिफ़ॉल्ट कॉन्फ़िगरेशन पर वापस आ जाता है.

एक्सएमएल फ़ॉर्मैट

कॉन्फ़िगरेशन फ़ाइल, memory-limiter-config.xsd में तय किए गए स्कीमा के मुताबिक होती है. इस फ़ाइल में, उपलब्ध मेमोरी के हिसाब से क्रम में लगाए गए, कई लिमिट सेट तय किए जाते हैं. सर्विस, डिवाइस में उपलब्ध रैम के आधार पर, सबसे ज़्यादा मैच करने वाला लिमिट सेट चुनती है. मेमोरी की सभी वैल्यू, मेबिबाइट (एमआईबी) में तय की जाती हैं.

<MemoryLimiterConfig>
  <version>1</version>
  <configList>
    <!-- RAM minimums account for MemTotal excluding carve-outs. -->
    <limitSet>
      <!-- Limits for 16GB RAM device (MemTotal >= 13GiB): 10G/5G/5G/5G -->
      <minimumRequiredMemTotal>13312</minimumRequiredMemTotal>
      <memVisible>10240</memVisible>
      <memNotVisible>5120</memNotVisible>
      <swapVisible>5120</swapVisible>
      <swapNotVisible>5120</swapNotVisible>
    </limitSet>
    <limitSet>
      <!-- Limits for 12GB RAM device (MemTotal >= 10GiB): 8G/4G/4G/4G -->
      <minimumRequiredMemTotal>10240</minimumRequiredMemTotal>
      <memVisible>8192</memVisible>
      <memNotVisible>4096</memNotVisible>
      <swapVisible>4096</swapVisible>
      <swapNotVisible>4096</swapNotVisible>
    </limitSet>
    <limitSet>
      <!-- Limits for 8GB RAM device (MemTotal >= 6.5GiB): 5G/3G/3G/3G -->
      <minimumRequiredMemTotal>6656</minimumRequiredMemTotal>
      <memVisible>5120</memVisible>
      <memNotVisible>3072</memNotVisible>
      <swapVisible>3072</swapVisible>
      <swapNotVisible>3072</swapNotVisible>
    </limitSet>
    <limitSet>
      <!-- Limits for 6GB RAM device (MemTotal >= 4.5GiB): 4G/2G/2G/2G -->
      <minimumRequiredMemTotal>4608</minimumRequiredMemTotal>
      <memVisible>4096</memVisible>
      <memNotVisible>2048</memNotVisible>
      <swapVisible>2048</swapVisible>
      <swapNotVisible>2048</swapNotVisible>
    </limitSet>
    <limitSet>
      <!-- Limits for 4GB RAM device (MemTotal >= 3GiB): 2G/1G/1G/1G -->
      <minimumRequiredMemTotal>3072</minimumRequiredMemTotal>
      <memVisible>2048</memVisible>
      <memNotVisible>1024</memNotVisible>
      <swapVisible>1024</swapVisible>
      <swapNotVisible>1024</swapNotVisible>
    </limitSet>
  </configList>
</MemoryLimiterConfig>
version
यह एक पॉज़िटिव इंटीजर है, जो कॉन्फ़िगरेशन के वर्शन की पहचान करता है. इसकी वैल्यू 1 होनी चाहिए.
minimumRequiredMemTotal

सिस्टम का वह कम से कम MemTotal (एमआईबी में), जिसकी ज़रूरत इस लिमिट सेट को लागू करने के लिए होती है. सर्विस, इसकी तुलना /proc/meminfo में कर्नल की ओर से रिपोर्ट की गई, उपलब्ध कुल मेमोरी से करती है.

मेमोरी कार्व-आउट के बारे में जानकारी: कर्नल की MemTotal वैल्यू, हार्डवेयर और फ़र्मवेयर मेमोरी कार्व-आउट घटाने के बाद, ऑपरेटिंग सिस्टम के लिए उपलब्ध फ़िज़िकल DRAM को दिखाती है. कार्व-आउट, रैम को खास हार्डवेयर कॉम्पोनेंट के लिए रिज़र्व करते हैं. जैसे, जीपीयू, बेसबैंड मॉडेम, कैमरा आईएसपी, सुरक्षित एक्ज़ीक्यूशन एनवायरमेंट, और हाइपरवाइज़र. आम तौर पर, ये 500 एमआईबी से लेकर 1 जीबी से ज़्यादा फ़िज़िकल मेमोरी का इस्तेमाल करते हैं.

MemTotal, डिवाइस के विज्ञापन में दिखाई गई फ़िज़िकल DRAM से कम होता है. minimumRequiredMemTotal की थ्रेशोल्ड में इन कार्व-आउट के लिए मार्जिन शामिल होता है. उदाहरण के लिए, 16 जीबी वाले डिवाइसों के लिए कॉन्फ़िगरेशन में, minimumRequiredMemTotal की वैल्यू 16384 (16 जीबी) के बजाय 13312 (13 जीबी) तय की जाती है. वहीं, 12 जीबी वाले डिवाइसों के लिए, इसकी वैल्यू 10240 (10 जीबी) तय की जाती है. इससे यह पक्का होता है कि हर डिवाइस, बोर्ड-लेवल के कार्व-आउट में होने वाले बदलावों के बावजूद, अपने तय किए गए टियर से मैच करे.

memVisible

यह दिखने वाली प्रोसेस पर लागू होने वाली, सॉफ़्ट मेमोरी लिमिट (memory.high) है. इसकी वैल्यू एमआईबी में होती है.

memNotVisible

यह नहीं दिखने वाली प्रोसेस पर लागू होने वाली, सॉफ़्ट मेमोरी लिमिट (memory.high) है. इसकी वैल्यू एमआईबी में होती है.

swapVisible

यह दिखने वाली प्रोसेस पर लागू होने वाली, स्वैप स्पेस लिमिट (memory.swap.max) है. इसकी वैल्यू एमआईबी में होती है.

swapNotVisible

यह नहीं दिखने वाली प्रोसेस पर लागू होने वाली, स्वैप स्पेस लिमिट (memory.swap.max) है. इसकी वैल्यू एमआईबी में होती है.

प्लैटफ़ॉर्म की स्टैंडर्ड सीमाएं

Android 17 और इसके बाद के वर्शन में, प्लैटफ़ॉर्म, /system/etc/memory-limiter-config.xml में, फ़िज़िकल रैम के सामान्य टियर के लिए, मेमोरी और स्वैप की स्टैंडर्ड सीमाएं तय करता है:

टारगेट फ़िज़िकल रैम ज़रूरी
MemTotal
(minimumRequiredMemTotal)
दिखने वाली प्रोसेस के लिए मेमोरी की सीमा
(memVisible)
नहीं दिखने वाली प्रोसेस के लिए मेमोरी की सीमा
(memNotVisible)
दिखने वाली प्रोसेस के लिए स्वैप की सीमा
(swapVisible)
नहीं दिखने वाली प्रोसेस के लिए स्वैप की सीमा
(swapNotVisible)
16 जीबी 13,312 एमआईबी (13 जीबी) 10,240 एमआईबी (10 जीबी) 5,120 एमआईबी (5 जीबी) 5,120 एमआईबी (5 जीबी) 5,120 एमआईबी (5 जीबी)
12 जीबी 10,240 एमआईबी (10 जीबी) 8,192 एमआईबी (8 जीबी) 4,096 एमआईबी (4 जीबी) 4,096 एमआईबी (4 जीबी) 4,096 एमआईबी (4 जीबी)
8 जीबी 6,656 एमआईबी (6.5 जीबी) 5,120 एमआईबी (5 जीबी) 3,072 एमआईबी (3 जीबी) 3,072 एमआईबी (3 जीबी) 3,072 एमआईबी (3 जीबी)
6 जीबी 4,608 एमआईबी (4.5 जीबी) 4,096 एमआईबी (4 जीबी) 2,048 एमआईबी (2 जीबी) 2,048 एमआईबी (2 जीबी) 2,048 एमआईबी (2 जीबी)
4 जीबी 3,072 एमआईबी (3 जीबी) 2,048 एमआईबी (2 जीबी) 1,024 एमआईबी (1 जीबी) 1,024 एमआईबी (1 जीबी) 1,024 एमआईबी (1 जीबी)

मेमोरी की सीमा से जुड़े सिद्धांत

मेमोरी लिमिटर का कॉन्फ़िगरेशन, प्लैटफ़ॉर्म के इन सिद्धांतों के आधार पर डिज़ाइन किया गया है:

  • इकोसिस्टम में एकरूपता और ऐप्लिकेशन की कंपैटिबिलिटी: सभी डिवाइसों पर मेमोरी की सीमाएं एक जैसी होने से, Android इकोसिस्टम में ऐप्लिकेशन की परफ़ॉर्मेंस का अनुमान लगाया जा सकता है. ऐप्लिकेशन, मेमोरी की स्टैंडर्ड उम्मीदों के हिसाब से डेवलप और टेस्ट किए जाते हैं. प्लैटफ़ॉर्म की सीमाओं को एक जैसा बनाए रखने से, अनचाहे थ्रॉटलिंग या समय से पहले बंद होने की समस्याओं से बचा जा सकता है.

  • संसाधनों का आनुपातिक बंटवारा: प्लैटफ़ॉर्म की सीमाएं, फ़िज़िकल रैम की क्षमता के हिसाब से तय की जाती हैं:

    • दिखने वाली प्रोसेस: ऐक्टिव यूज़र इंटरफ़ेस (यूआई), कंपोज़िशन, और रेंडरिंग वर्कलोड को सपोर्ट करने के लिए, कुल फ़िज़िकल रैम का करीब 1/2 से 2/3 हिस्सा असाइन किया जाता है.
    • नहीं दिखने वाली प्रोसेस: बैकग्राउंड में चलने वाले टास्क के लिए, कुल फ़िज़िकल रैम का करीब 1/4 से 1/3 हिस्सा असाइन किया जाता है.
  • सभी पर लागू होना: सीमाएं, डिवाइस पर मौजूद सभी ऐप्लिकेशन प्रोसेस (यूआईडी >= 10000) पर एक जैसी लागू होती हैं. इनमें, पहले से इंस्टॉल किए गए सिस्टम और ओईएम ऐप्लिकेशन भी शामिल हैं. मेमोरी लिमिटर, खास ऐप्लिकेशन को छूट देने के लिए, अनुमति वाली सूचियों के साथ काम नहीं करता. इससे, सभी ऐप्लिकेशन के लिए मेमोरी का सही तरीके से इस्तेमाल किया जा सकता है.

  • रनटाइम क्वेरी एपीआई मौजूद नहीं हैं: 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 सब-कमांड, किसी खास यूआईडी या सभी प्रोसेस को अस्थायी तौर पर, मेमोरी की सीमा से बाहर रखता है. यह परफ़ॉर्मेंस बेंचमार्किंग, स्ट्रेस टेस्टिंग या मेमोरी के व्यवहार की जांच करने के लिए काम का है:

# 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 सब-कमांड, प्रोसेस आईडी (पीआईडी) के हिसाब से, किसी खास प्रोसेस के लिए कैलकुलेट की गई सीमाओं को बाइट में, कस्टम ऐब्सलूट वैल्यू के साथ बदलता है. वैल्यू एक इंटीजर होनी चाहिए. हालांकि, इसमें एमआईबी दिखाने के लिए MB सफ़िक्स या जीबी दिखाने के लिए GB सफ़िक्स शामिल किया जा सकता है:

# 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

मैन्युअल तौर पर किए गए बदलाव, सिर्फ़ उस खास प्रोसेस इंस्टेंस के लाइफ़टाइम के लिए लागू होते हैं. अगर प्रोसेस रीस्टार्ट होती है, तो यह अपनी स्थिति के आधार पर, स्टैंडर्ड सीमाओं पर वापस आ जाती है.

मैन्युअल तौर पर किया गया बदलाव, सिस्टम की फ़िज़िकल सीमाओं से ज़्यादा नहीं हो सकता.