java - जावा int स्मृति उपयोग




arrays memory jvm (7)

जब आप कोई सरणी पास करते हैं, तो सरणी की सामग्री को सरणी प्राप्त करने वाली विधि द्वारा संशोधित किया जा सकता है। जब आप int primitives पास करते हैं, तो उन प्राइमेटिव को उन विधिओं द्वारा संशोधित नहीं किया जा सकता है जो उन्हें प्राप्त करते हैं। यही कारण है कि कभी-कभी आप प्राइमेटिव और कभी-कभी सरणी का उपयोग कर सकते हैं।

सामान्य रूप से, जावा प्रोग्रामिंग में आप पठनीयता का पक्ष लेते हैं और इस तरह के मेमोरी ऑप्टिमाइज़ेशन को जेआईटी कंपाइलर द्वारा किया जाना चाहिए।

जबकि मैं विभिन्न प्रकार के स्मृति उपयोग पर विचार कर रहा था, मैं थोड़ी उलझन में शुरू हुआ कि जावा विधि को पारित होने पर पूर्णांक के लिए स्मृति का उपयोग कैसे करता है।

कहो, मेरे पास निम्न कोड था:

public static void main (String[] args){
     int i = 4;
     addUp(i);
}

public static int addUp(int i){
     if(i == 0) return 0;
     else return addUp(i - 1);         
}

इस उदाहरण में, मैं सोच रहा हूं कि मेरा निम्नलिखित तर्क सही था या नहीं:

  • मैंने शुरुआत में पूर्णांक i = 4 के लिए एक स्मृति बनाई है। फिर मैं इसे एक विधि में पास करता हूं। हालांकि, चूंकि primitives जावा में इंगित नहीं हैं, addUp (i == 4) में, मैं एक और पूर्णांक i = 4 बना देता हूं। फिर बाद में, एक और addUp (i == 3), addUp (i == 2) है, addUp (i == 1), addUp (i == 0) जिसमें प्रत्येक बार, जब मान इंगित नहीं किया जाता है, तो स्मृति में एक नया i मान आवंटित किया जाता है।
  • फिर एक "int i" मान के लिए, मैंने 6 पूर्णांक मान यादों का उपयोग किया है।

हालांकि, अगर मैं हमेशा इसे एक सरणी के माध्यम से पास करना था:

public static void main (String[] args){
     int[] i = {4};
     // int tempI = i[0];
     addUp(i);
}

public static int addUp(int[] i){
     if(i[0] == 0) return 0;
     else return addUp(i[0] = i[0] - 1);         
}

- चूंकि मैं आकार 1 का एक पूर्णांक सरणी बना देता हूं और उसके बाद उस ऐडअप को पास करता हूं जिसे फिर से एडअप (i [0] == 3), एडअप (i [0] == 2), एडअप (i [0] के लिए पारित किया जाएगा == 1), ऐडअप (i [0] == 0), मुझे केवल 1 पूर्णांक सरणी मेमोरी स्पेस का उपयोग करना पड़ा है और इसलिए अब तक अधिक लागत प्रभावी है। इसके अलावा, अगर मैं पहले से ही int [i] के प्रारंभिक मान को संग्रहीत करने के लिए एक int मान बनाना चाहता था, तो मेरे पास अभी भी मेरा "मूल" मान है।

फिर यह मुझे सवाल पर ले जाता है, लोग जावा विधियों में int जैसे प्राइमेटिव क्यों पास करते हैं? क्या यह उन प्राइमेटिव्स के सरणी मानों को पास करने के लिए कहीं अधिक मेमोरी कुशल नहीं है? या किसी भी तरह का पहला उदाहरण अभी भी ओ (1) स्मृति है?

और इस सवाल के शीर्ष पर, मैं विशेष रूप से int [] और int का उपयोग करने के स्मृति अंतरों को आश्चर्यचकित करता हूं, विशेष रूप से 1 के आकार के लिए। अग्रिम धन्यवाद। मैं बस जावा के साथ अधिक मेमोरी कुशल होने का सोच रहा था और यह मेरे सिर पर आया।

सारे सवालों के जवाब देने के लिए धन्यवाद! मैं अभी जल्दी सोच रहा हूं कि क्या मैं प्रत्येक कोड की बड़ी-ओह स्मृति का "विश्लेषण" करना चाहता हूं, क्या उन्हें दोनों ओ (1) माना जाएगा या क्या यह मानना ​​गलत होगा?


असल में, जब आप किसी विधि को पैरामीटर के रूप में सरणी पास करते हैं - इस सरणी का संदर्भ हुड के नीचे पारित किया जाता है। सरणी ही ढेर पर संग्रहीत है। और संदर्भ आकार में 4 या 8 बाइट्स हो सकता है (सीपीयू आर्किटेक्चर, जेवीएम कार्यान्वयन आदि के आधार पर; और भी अधिक, जेएलएस कुछ भी नहीं कहता है कि संदर्भ में संदर्भ कितना बड़ा है)।

दूसरी तरफ, आदिम int मान हमेशा केवल 4 बाइट्स का उपभोग करता है और ढेर पर रहता है।


यदि आपकी धारणा यह है कि कार्यों में पारित तर्क आवश्यक रूप से मेमोरी (जो कि रास्ते से गलत है) का उपभोग करते हैं, तो आपके दूसरे उदाहरण में जो सरणी पास करता है, सरणी के संदर्भ की एक प्रति बनाई जाती है। यह संदर्भ वास्तव में एक int से बड़ा हो सकता है, यह छोटा होने की संभावना नहीं है।


Int सरणी संदर्भ वास्तव में एक int primitive (8 बाइट बनाम 4) की तुलना में ढेर फ्रेम में और अधिक जगह लेता है। आप वास्तव में अधिक जगह का उपयोग कर रहे हैं।

लेकिन मुझे लगता है कि प्राथमिक कारण लोगों को पहला तरीका पसंद है क्योंकि यह स्पष्ट और अधिक सुगम है।

जब लोग अधिक स्याही शामिल होते हैं तो लोग वास्तव में दूसरी चीजों को बहुत करीब करते हैं।


कई चीजे:

पहली बात बाल विभाजित होगी, लेकिन जब आप जावा में एक int पास करते हैं तो आप स्टैक पर 4 बाइट आवंटित कर रहे हैं, और जब आप एक सरणी पास करते हैं (क्योंकि यह एक संदर्भ है) तो आप वास्तव में 8 बाइट आवंटित कर रहे हैं (एक x64 आर्किटेक्चर मानते हैं) ढेर, साथ ही अतिरिक्त 4 बाइट जो ढेर में int को स्टोर करते हैं।

सबसे महत्वपूर्ण बात यह है कि सरणी में रहने वाले डेटा को ढेर में आवंटित किया जाता है, जबकि सरणी के संदर्भ को स्टैक पर आवंटित किया जाता है, जब एक पूर्णांक गुजरने पर कोई ढेर आवंटन आवश्यक नहीं होता है, तो आदिम केवल स्टैक में आवंटित होता है। समय के साथ ढेर आवंटन को कम करने का मतलब यह होगा कि कचरा कलेक्टर को साफ करने के लिए कम चीजें होंगी। जबकि स्टैक-फ्रेम की सफाई छोटी है और अतिरिक्त प्रसंस्करण की आवश्यकता नहीं है।

हालांकि, यह सब moot (imho) है क्योंकि व्यवहार में जब आपके पास चर और वस्तुओं के जटिल संग्रह होते हैं तो आप उन्हें कक्षा में एक साथ समूहित करने की संभावना रखते हैं। सामान्य रूप से, आपको JVM के प्रदर्शन की हर अंतिम बूंद को निचोड़ने की कोशिश करने के बजाय पठनीयता और रखरखाव को बढ़ावा देने के लिए लिखना चाहिए। जेवीएम बहुत तेज़ है, और हमेशा मूर का कानून बैकस्टॉप के रूप में होता है।

प्रत्येक के लिए बिग-ओ का विश्लेषण करना मुश्किल होगा क्योंकि एक वास्तविक तस्वीर प्राप्त करने के लिए आपको कचरा कलेक्टर के व्यवहार में कारक बनाना होगा और यह व्यवहार दोनों JVM स्वयं और किसी भी रनटाइम (जेआईटी) पर अत्यधिक निर्भर है। ऑप्टिमाइज़ेशन जो JVM ने आपके कोड में किया है।

कृपया डोनाल्ड Knuth के बुद्धिमान शब्दों को याद रखें कि "समयपूर्व अनुकूलन सभी बुराई की जड़ है"

कोड लिखें जो माइक्रो-ट्यूनिंग से बचाता है, कोड जो पठनीयता और रखरखाव को बढ़ावा देता है, लंबे समय तक बेहतर किराया देगा।


आप यहां क्या खो रहे हैं: आपके उदाहरण में int मान stack पर नहीं, ढेर पर नहीं।

और ढेर पर मौजूद वस्तुओं की तुलना में स्टैक पर मौजूद निश्चित आकार के प्राचीन मूल्यों से निपटने के लिए यह बहुत कम ओवरहेड है!

दूसरे शब्दों में: "पॉइंटर" का उपयोग करने का अर्थ है कि आपको ढेर पर एक नई वस्तु बनाना है। सभी वस्तुओं ढेर पर रहते हैं; सरणी के लिए कोई ढेर नहीं है ! और वस्तुओं का उपयोग बंद करने के तुरंत बाद कचरा संग्रह के अधीन हो जाता है। दूसरी तरफ ढेर आते हैं और जाते हैं जैसे आप तरीकों का आह्वान करते हैं!

इसके अलावा: ध्यान रखें कि प्रोग्रामिंग भाषाएं जो हमें प्रदान करती हैं, उन्हें हमें कोड लिखने में मदद करने के लिए बनाया गया है जो पढ़ने, समझने और बनाए रखने में आसान है। आपका दृष्टिकोण मूल रूप से कुछ प्रकार के ठीक ट्यूनिंग करने के लिए है जो अधिक जटिल कोड की ओर जाता है। और ऐसा नहीं है कि जावा इस तरह की समस्याओं को हल करता है।

मतलब: जावा के साथ, वास्तविक "प्रदर्शन जादू" रनटाइम पर होता है, जब बस-इन-टाइम कंपाइलर में आता है! आप देखते हैं, जब विधि "अक्सर पर्याप्त" लागू की जाती है तो जेआईटी छोटी विधियों को कॉल इनलाइन कर सकता है। और फिर डेटा को "करीबी" रखने के लिए और भी महत्वपूर्ण हो जाता है। जैसा कि: जब डेटा ढेर पर रहता है, तो आपको मूल्य प्राप्त करने के लिए स्मृति तक पहुंच होनी पड़ सकती है। जबकि ढेर पर रहने वाले सामान - अभी भी "बंद" हो सकते हैं (जैसा कि: प्रोसेसर कैश में)। तो स्मृति उपयोग को अनुकूलित करने के लिए आपका छोटा विचार वास्तव में परिमाण के आदेशों से प्रोग्राम निष्पादन को धीमा कर सकता है। क्योंकि आज भी, प्रोसेसर कैश तक पहुंचने और मुख्य मेमोरी पढ़ने के बीच परिमाण के आदेश हैं।

लंबी कहानी छोटी: प्रदर्शन या स्मृति उपयोग के लिए ऐसे "माइक्रो-ट्यूनिंग" में शामिल होने से बचें: JVM को "सामान्य, सामान्य" उपयोग के मामलों के लिए अनुकूलित किया गया है। चालाक काम-आस-पास पेश करने के आपके प्रयासों को इसलिए आसानी से "कम अच्छे" परिणाम मिल सकते हैं।

तो - जब आप प्रदर्शन के बारे में चिंता करते हैं: जो कुछ भी कर रहा है वह करें। और यदि आप वास्तव में परवाह करते हैं - तो जानें कि JVM कैसे काम करता है । जैसा कि यह पता चला है कि मेरा ज्ञान थोड़ा पुराना है - क्योंकि टिप्पणियों का अर्थ है कि एक जेआईटी ढेर पर वस्तुओं को रेखांकित कर सकता है। उस अर्थ में: स्वच्छ, सुरुचिपूर्ण कोड लिखने पर ध्यान केंद्रित करें जो समस्या को सीधे आगे बढ़ाता है!

अंत में: यह किसी बिंदु पर परिवर्तन के अधीन है। जावा के लिए सही मूल्य मूल्य वस्तुओं को पेश करने के विचार हैं। जो मूल रूप से ढेर पर रहते हैं, ढेर नहीं। लेकिन जावा 10 से पहले होने की उम्मीद न करें। या 11. या ... (मुझे लगता है कि this यहां प्रासंगिक होगा)।


जावा में सब कुछ संदर्भ है, इसलिए जब आपके पास कुछ ऐसा है: Point pnt1 = new Point(0,0);जावा निम्न करता है:

  1. नया प्वाइंट ऑब्जेक्ट बनाता है
  2. नया प्वाइंट संदर्भ बनाता है और उस बिंदु को संदर्भित करता है (संदर्भित) पहले बनाए गए प्वाइंट ऑब्जेक्ट पर।
  3. यहां से, प्वाइंट ऑब्जेक्ट लाइफ के माध्यम से, आप उस ऑब्जेक्ट को pnt1 संदर्भ के माध्यम से एक्सेस करेंगे। तो हम कह सकते हैं कि जावा में आप इसके संदर्भ के माध्यम से ऑब्जेक्ट में हेरफेर करते हैं।

जावा संदर्भ द्वारा विधि तर्क पास नहीं करता है; यह उन्हें मूल्य से गुजरता है। मैं इस साइट से उदाहरण का उपयोग करूंगा :

public static void tricky(Point arg1, Point arg2) {
  arg1.x = 100;
  arg1.y = 100;
  Point temp = arg1;
  arg1 = arg2;
  arg2 = temp;
}
public static void main(String [] args) {
  Point pnt1 = new Point(0,0);
  Point pnt2 = new Point(0,0);
  System.out.println("X1: " + pnt1.x + " Y1: " +pnt1.y); 
  System.out.println("X2: " + pnt2.x + " Y2: " +pnt2.y);
  System.out.println(" ");
  tricky(pnt1,pnt2);
  System.out.println("X1: " + pnt1.x + " Y1:" + pnt1.y); 
  System.out.println("X2: " + pnt2.x + " Y2: " +pnt2.y);  
}

कार्यक्रम का प्रवाह:

Point pnt1 = new Point(0,0);
Point pnt2 = new Point(0,0);

दो अलग-अलग बिंदु ऑब्जेक्ट को दो अलग-अलग संदर्भों से जोड़ना।

System.out.println("X1: " + pnt1.x + " Y1: " +pnt1.y); 
System.out.println("X2: " + pnt2.x + " Y2: " +pnt2.y);
System.out.println(" ");

जैसा कि अपेक्षित आउटपुट होगा:

X1: 0     Y1: 0
X2: 0     Y2: 0

इस लाइन पर 'पास-बाय-वैल्यू' नाटक में चला जाता है ...

tricky(pnt1,pnt2);           public void tricky(Point arg1, Point arg2);

संदर्भ pnt1और pnt2कर रहे हैं मूल्य द्वारा पारित मुश्किल विधि के लिए, जिसका अर्थ है कि अब तुम्हारा संदर्भ pnt1और pnt2उनकी राशि copiesनामित arg1और arg2तो pnt1और arg1 अंक एक ही वस्तु है। ( pnt2और के लिए वही arg2)

में trickyविधि:

 arg1.x = 100;
 arg1.y = 100;

trickyविधि में अगला

Point temp = arg1;
arg1 = arg2;
arg2 = temp;

यहां, आप पहले नया tempपॉइंट संदर्भ बनाते हैं जो संदर्भ जैसे ही स्थान पर इंगित करेगा arg1। फिर आप संदर्भ के समान स्थान arg1पर बिंदु के संदर्भ ले जाते हैं arg2। अंत में एक ही जगह पर इंगितarg2 करेंगे ।temp

यहाँ से के दायरे trickyविधि चला गया है और आप संदर्भ के लिए किसी भी अधिक पहुँच नहीं है: arg1, arg2, tempलेकिन महत्वपूर्ण बात यह है कि जब आप 'जीवन में' होते हैं तो इन संदर्भों के साथ आप जो कुछ भी करते हैं, वह उस वस्तु को स्थायी रूप से प्रभावित करेगा जिस पर वे इंगित करते हैं।

तो विधि को निष्पादित करने के बाद tricky, जब आप वापस आते हैं main, तो आपके पास यह स्थिति है:

तो अब, कार्यक्रम का पूरी तरह से निष्पादन होगा:

X1: 0         Y1: 0
X2: 0         Y2: 0
X1: 100       Y1: 100
X2: 0         Y2: 0




java arrays memory jvm int