View Full Version : JVM و تفاوت وظیفه آن با کامپایلر
deCODER-
شنبه 23 اسفند 1393, 14:31 عصر
خسته نباشید دوستان ...
2 سوال داشتم:
1- وظیفه اصلی ماشین مجازی جاوا چیه و چه تفاوتی در وظیفه اون و کامپایلر هست؟
2- آیا زبان C و C++ هم مجهز به ماشین مجازی هستند؟
تشکر
محمد فدوی
شنبه 23 اسفند 1393, 16:40 عصر
سلام.
همونطور که گفتم کدهای جاوا توسط کامپایلر جاوا (javac) به بایتکد (فایلهایی فا فرمت class) تبدیل میشن. این فایلها حکم فایلهای قابل اجرا رو دارن و میشه توی هر سیستمی اجراشون کرد. البته نه بصورت محلی، بلکه توسط ماشین مجازی جاوا (JVM). این ماشین مجازی دقیقا میاد کد درون فایل بایتکد رو میخونه و نظیر به نظیر کد قابل فهم برای ماشین رو تولید میکنه. پس جاوا روی سیستمهایی اجرا میشه که ماشین مجازی جاوا رو داشته باشن که توی بستهی JRE موجوده. برنامهنویسا باید از بستهی JDK استفاده کنن که علاوه بر قابلیتهای JRE، کامپایلر رو هم داره.
در مورد سی\سیپلاسپلاس اول اینو بگم که این زبان فقط یه کامپایلر نداره. یعنی کامپایلرهای زیادی توسط شرکتایی مثل مایکروسافت و بورلند و... تولید شده که با سی\سیپلاسپلاس کار میکنند. مثلا یکی از کامپایلرهای معروفش ++g هست که توی بستهی gcc قرار داره... بگذریم. بهرحال گذشته از کامپایلر، سیپلاسپلاس هیچوقت روی ماشین مجازی اجرا نمیشه. بلکه کد باینری تولید شده مستقیما و بصورت محلی توسط سیستمعامل اجرا میشه و برای ماشین قابل فهمه.
موفق باشید.
-سیّد-
دوشنبه 24 فروردین 1394, 07:17 صبح
در ادامهی توضیحات خوب دوستمون، من هم دو تا نکته اضافه کنم.
یکی این که JVM خودش یه برنامه هست که به زبان ++C نوشته شده. یعنی خود JVM به زبان ++C بوده، بعد کامپایل شده و به زبان ماشین تبدیل شده (روی هر نوع ماشینی به زبان خودش، یعنی یه JVM برای ویندوز، یه دونه برای اوبونتو، یه دونه برای Mac، یه دونه برای CentOS، ...). کار این برنامه اینه که فایل class که کامپایل شدهی فایل جاوا هست رو اجرا کنه. یه جورایی مثل شبیهساز CPU میمونه، از این نظر که CPU دستورات خودش رو بلده اجرا کنه ولی سختافزاریه، JVM هم دستورات خودش رو بلده اجرا کنه ولی نرمافزاریه و روی بستر سختافزاری CPU کار میکنه.
دوم این که JVM مدتهاست که مجهز به یک ماژول بسیار قوی و پیشرفته به نام JIT هست (مخفف Just In Time).
برای توضیح نحوهی کار JIT یه مثال ساده میزنم.
به کد زیر توجه کنید:
static void f() {
long i = 0;
for (int j = 0; j < 200000000; j++) {
for (int k = 0; k < 100; k++) {
i += 2;
}
}
}
public static void main(String[] args) {
for (int i = 0; i < 7; i++) {
long t1 = System.currentTimeMillis();
f();
long t2 = System.currentTimeMillis();
System.out.println("Time: " + (t2 - t1));
}
}
یه تابع f داریم که توش دو تا حلقهی تو در تو وجود داره که به تعداد ۲۰۰ میلیون ضربدر ۱۰۰ بار (یعنی ۲۰ میلیارد بار)، یه متغیر رو با ۲ جمع میکنه.
داخل تابع main هم توی یه حلقه، این تابع رو ۷ بار صدا زدیم و هر بار زمان اجرای تابع رو محاسبه کردیم و چاپ کردیم.
من این کد رو اجرا کردم، نتیجهی نمونهاش شد این:
Time: 1604
Time: 2
Time: 0
Time: 0
Time: 0
Time: 0
Time: 0
خوب چه اتفاقی افتاد؟ دفعهی اول که این کد اجرا شد، ۱.۶ ثانیه طول کشید. اما دفعات بعد تقریباً صفر ثانیه طول کشید! (واحد عددی که چاپ میشه میلیثانیه هست)
اتفاقی که اینجا افتاد این بود که JIT درگیر ماجرا شد. توجه کنید که توی تابع f، ما بیخودی داریم مقدار اون متغیر بدبخت رو حساب میکنیم! یعنی بعد از این که حلقه تموم شد، از مقدار محاسبه شده هیچ استفادهای نمیکنیم (به خاطر همین هم محیط کدزنی شما (مثلاً eclipse یا IntelliJ یا NetBeans یا ...) به شما یک Warning نشون میده که از مقدار متغیر i هیچ استفادهای نشده).
JIT کارش اینه که کدی که اجرا میشه رو به دقت زیر نظر میگیره. هر جای کد زیاد اجرا بشه، اونجا رو دقیقاً بررسی میکنه و به شدت optimize میکنه (چون جاهایی که توی یه برنامه زیاد اجرا میشن، جاهای مهم اون هستن و اگه سرعت اجرا در اونجاها بره بالا، سرعت کل کد به شدت میره بالا).
دفعهی اولی که تابع f اجرا میشه، JIT که داره اجرای کد رو بررسی میکنه (به این «بررسی اجرای کد» میگن profile کردن کد)، میبینه که یه حلقه داریم که یک میلیارد بار اجرا شده! برای همین میره تو کارش! JIT خیلی ساده میتونه متوجه بشه که این حلقه بیخودی هست و در نتیجه کل این حلقه رو از کد شما حذف میکنه. یعنی تابع f تبدیل میشه به این:
static void f() {
}
و در نتیجه اجرای اون تقریباً هیچ زمانی نمیبره.
توی زبانهایی که به زبان ماشین کامپایل میشن (مانند ++C)، چنین چیزی به این صورت امکان نداره (البته یه کارای مشابهی میکنن ولی اینطوری نیست). یعنی یکی از مزیتهای بزرگ زبانهایی مانند جاوا که از یه VM برای اجرا استفاده میکنن اینه که میتونن کد رو در حال اجرا profile کنن و تبدیلش کنن به زبان ماشین مخصوص اون اجرا. یعنی فرض کنید یه حلقهی خاص دارید که بسته به نوع ورودی کاربر ممکنه اجرا بشه یا نشه، یه بار کدتون رو اجرا میکنید و یه جوری بهش ورودی میدید که اون حلقه اجرا میشه. در نتیجه JIT اون رو optimize میکنه. اما اگه یه بار دیگه برنامه رو از اول اجرا کنید و یه جوری بهش ورودی بدید که اون حلقه اصلاً اجرا نشده، JIT هم باهاش کاری نداره. یه مثال:
توی کد قبلی یه تغییر میدیم:
static void f(int x) {
long i = 0;
for (int j = 0; j < 200000000; j++) {
for (int k = 0; k < 100; k++) {
if (x > 3)
i += 2;
}
}
System.out.println(i);
}
public static void main(String[] args) {
for (int i = 0; i < 7; i++) {
long t1 = System.currentTimeMillis();
f(i);
long t2 = System.currentTimeMillis();
System.out.println("Time: " + (t2 - t1));
}
}
تغییری که دادیم این بود: تابع f یه ورودی میگیره، توی حلقه چک میکنه اگه ورودی بیشتر از ۳ بود اون وقت محاسباتش رو انجام میده. در نهایت هم بعد از اجرای حلقه، مقدار محاسبه شده رو چاپ میکنیم که دور نریخته باشیمش. توی main هم به این تابع شمارهی اجرا رو ورودی میدیم (یعنی دفعهی اول صفر میدیم، دفعهی دوم یک، ...). در نتیجه دفعات اول و دوم و سوم و چهارم مقدار متغیر ورودی تابع بزرگتر از ۳ نیست و در نتیجه باز اون حلقه عملاً کاری انجام نمیده. ولی دفعات پنجم و ششم و هفتم بزرگتر از ۳ میشه و اون حلقه شروع به محاسبات میکنه.
یک نمونه خروجی این برنامه:
0
Time: 237
0
Time: 237
0
Time: 0
0
Time: 0
40000000000
Time: 1575
40000000000
Time: 10
40000000000
Time: 9
دقت کنید! خیلی جالب شد! دفعهی اول و دوم یه مقدار طول کشید (۲۳۷ میلیثانیه)، اما دفعهی سوم و چهارم فهمید که اون حلقه هیچ کاری نمیکنه و حذفش کرد و در نتیجه زمان صفر شد. اما دفعهی پنجم محاسبات انجام شد و ۱.۵ ثانیه طول کشید. اینجا جاییه که زبانهایی مثل ++C نمیتونن به راحتی این کار رو با static analysis روی کد انجام بدن. البته اونا هم میتونن با trace کردن کد به این نتیجه برسن که توی دفعات اول تا چهارم چه اتفاقی میافته و بعد توی دفعات بعدی یه اتفاق دیگه میافته. اما کار راحتی نیست و ممکنه بعضی وقتها نتونن این نتیجهگیری رو بکنن.
اما جالبتر از اون، دفعات ششم و هفته هست! زمان از ۱.۵ ثانیه، تبدیل شد به ۱۰ میلیثانیه! چی شد؟ دوباره JIT درگیر شد و رفت با توجه به اجرای برنامه و مقدار پارامتر ورودی تابع f، اون حلقه رو optimize کرد. اینجا دیگه ++C کم میاره. یعنی چنین چیزی توی ++C شدنی نیست. مخصوصاً اگه ورودی تابع f رو مثلاً از کاربر دریافت کنید یا از توی یه فایل بخونید که در نتیجه برای کامپایلر قابل تشخیص نیست که بتونه optimize کنه و حتماً نیاز به بررسی کد هنگام اجرا داره.
در نهایت ۲ تا نکته بگم: یکی این که CPU ها خیلی وقته که خودشون به صورت سختافزاری کد ماشین رو profile میکنن و بر اساسش یه سری optimization خفن انجام میدن. بارزترین نمونهاش branch prediction هست که خیلی هم توی سرعت اجرای کد تأثیرگذاره (که توضیحش اینجا نمیگنجه!).
نکتهی دوم این که ++C یه گزینه داره به نام pgo (مخفف Profile-Guided Optimization) که همین کاری هست که JIT میکنه. یعنی به کمک profile کردن، کد رو optimize میکنه. روش کارش اینطوریه که شما یه بار موقع کامپایل کردن کد، یه پارامتر به کامپایلر میدید (من gcc رو میدونم این پارامتر رو داره، بقیهی کامپایلرها رو نمیدونم) و بهش میگین کد رو profile کنه. بعد کدتون رو اجرا میکنید و میذارید یه مدت اجرا بشه. بعد اجراش رو متوقف میکنید و مجدداً کد رو کامپایل میکنید، و این دفعه یه پارامتر دیگه به کامپایلر میدید و میگید از دیتای profile جمعآوری شده برای بهینهسازی کد استفاده کنه.
این روش ۲ تفاوت اصلی با روش JIT جاوا داره: یکی این که JIT جاوا به صورت خودکار این کارو میکنه و شما نباید بهش بگین! دوم این که JIT در هر بار اجرای کد، با توجه به نوع اجرای اون کد برای اون اجرا بهینهسازی میکنه. در حالی که توی روش مشابه توی ++C شما باید برای هر بار اجرای خودتون، یه بار کد رو از اول کامپایل کنید و مجدد اجرا کنید تا برای اون نوع اجرا بهینهسازی بشه!
این رو هم بگم که با توجه به همهی اینها، کسی که بتونه کدش رو توی ++C بزنه و خوب این کار رو بلد باشه انجام بده (کار سختیه!)، مطمئناً در نهایت میتونه کد سریعتری از کد جاوا تحویل بده. دلیلش هم اینه که این کارایی که گفتم همگی توسط ماشین و به صورت خودکار انجام میشن، ولی اگه یه انسان بتونه optimization انجام بده، خیلی بهتر از ماشین میتونه این کارو بکنه! البته فعلاً اینطوری هست! شاید بعداً ورق برگرده!
ببخشید تریبونم طولانی شد! :)
-سیّد-
دوشنبه 24 فروردین 1394, 10:16 صبح
چند تا لینک دربارهی JIT:
https://fa.wikipedia.org/wiki/%DA%A9%D8%A7%D9%85%D9%BE%D8%A7%DB%8C%D9%84_%D8%AF% D8%B1%D8%AC%D8%A7
https://fa.wikipedia.org/wiki/%D9%87%D8%A7%D8%AA%E2%80%8C%D8%A7%D8%B3%D9%BE%D8%A 7%D8%AA
البته من خودم واقعاً هیچی از ویکیپدیای فارسی JIT نفهمیدم!!! توصیه میکنم انگلیسیش رو بخونید:
https://en.wikipedia.org/wiki/Just-in-time_compilation
https://en.wikipedia.org/wiki/HotSpot
این نکته هم قابل ذکره که تا نسخهی ۶ جاوا، OpenJDK کلاً به درد نمیخورد! این HotSpot توی Sun JDK بود و نه توی OpenJDK. اما از نسخهی ۷ به بعد کلاً OpenJDK اومد توی بازی. یکی از دوستان میگفت که نسخهی ۷ به بعد Sun JDK از روی OpenJDK برداشته شده (یعنی base اش OpenJDK هست).
nasrin55
چهارشنبه 26 فروردین 1394, 19:36 عصر
چند تا لینک دربارهی JIT:
https://fa.wikipedia.org/wiki/%DA%A9%D8%A7%D9%85%D9%BE%D8%A7%DB%8C%D9%84_%D8%AF% D8%B1%D8%AC%D8%A7
https://fa.wikipedia.org/wiki/%D9%87%D8%A7%D8%AA%E2%80%8C%D8%A7%D8%B3%D9%BE%D8%A 7%D8%AA
البته من خودم واقعاً هیچی از ویکیپدیای فارسی JIT نفهمیدم!!! توصیه میکنم انگلیسیش رو بخونید:
https://en.wikipedia.org/wiki/Just-in-time_compilation
https://en.wikipedia.org/wiki/HotSpot
این نکته هم قابل ذکره که تا نسخهی ۶ جاوا، OpenJDK کلاً به درد نمیخورد! این HotSpot توی Sun JDK بود و نه توی OpenJDK. اما از نسخهی ۷ به بعد کلاً OpenJDK اومد توی بازی. یکی از دوستان میگفت که نسخهی ۷ به بعد Sun JDK از روی OpenJDK برداشته شده (یعنی base اش OpenJDK هست).
ممنون از توضیحات عالی تون.
کامپایلر جاوا و jvm که اینقدر باهوشه و بهینه سازی قوی داره، چرا این بهینه سازی را برای hibernateها انجام نمیده؟ وقتی در یک پروژه بزرگ که کلاس های hibernate زیاد داره و در حال کدزنی هستیم، خیلی سریع ram کم میاره و permGen میده!
JIT به صورت default کارش رو انجام میده ؟
پ
-سیّد-
چهارشنبه 26 فروردین 1394, 20:32 عصر
کامپایلر جاوا و jvm که اینقدر باهوشه و بهینه سازی قوی داره، چرا این بهینه سازی را برای hibernateها انجام نمیده؟ وقتی در یک پروژه بزرگ که کلاس های hibernate زیاد داره و در حال کدزنی هستیم، خیلی سریع ram کم میاره و permGen میده!
اولاً کارهایی که JIT میکنه، برای بهینهسازی زمان اجرا و کم کردن مصرف CPU هست، نه میزان RAM استفاده شده (هر چند که تغییراتی که ایجاد میکنه احتمالاً توی کم شدن میزان RAM مصرفی هم تأثیر داره، ولی فکر نمیکنم زیاد باشه).
ثانیاً performance یه برنامهی جاوا به خیلی عوامل بستگی داره. مثلاً کدی که با reflection زیاد کار میکنه (مثل IDE های جاوایی: eclipse و intellij و netbeans و ...) از permgen خیلی استفاده میکنه، ولی کد معمولی خیلی با permgen کاری نداره.
این که گفتید رو مطمئن نیستم درست متوجه شدم یا نه. شما میفرمایید که موقع نوشتن کد توی IDE با مشکل permgen برخورد میکنید؟ یا موقع اجرای کدی که نوشتید؟
JIT به صورت default کارش رو انجام میده ؟
بله به صورت پیشفرض فعال هست. میتونید با دادن پارامتر
-Xint به JVM، اون رو غیر فعال کنید. البته این کار رو فقط برای تست بکنید! توی سیستم واقعی اگه JIT رو غیر فعال کنید، سرعت سیستم به شدت پایین میاد.
این صفحه هم بد نیست (جواب دوم رو هم ببینید):
http://stackoverflow.com/questions/9457405/how-to-check-if-the-jit-compiler-is-off-in-java
nasrin55
پنج شنبه 27 فروردین 1394, 08:58 صبح
در حین کار و کد زدن در Netbeans خیلی خطای permGen داده میشه، یعنی ممکنه در حین اجرا نهایی روی سیستم کاربر همین اشکال را داشته باشه و باعث کند شدن اجرا سیستم بشه؟ یا فقط در حین برنامه نویسی این خطاها زیاده؟
نمی دونم این تعداد زیاد permgen طبیعی است یا نه، در حالتی که تعداد کلاس های hibernate زیاد است. راهی است که در حین کدزدن، سرعت نت بینز رو افزایش داد، افزایش ram مورد نیاز پروژه در نت بینز چه از طریق option و چه از طریق تغییر فایل های config اون ، بهبود چندانی نداشت.
-سیّد-
پنج شنبه 27 فروردین 1394, 11:19 صبح
در حین کار و کد زدن در Netbeans خیلی خطای permGen داده میشه، یعنی ممکنه در حین اجرا نهایی روی سیستم کاربر همین اشکال را داشته باشه و باعث کند شدن اجرا سیستم بشه؟ یا فقط در حین برنامه نویسی این خطاها زیاده؟
نه ارتباطی به هم ندارن. این که سرعت اجرای کد شما چطوری باشه، کاملاً به کد شما بر میگرده. در واقع الان Netbeans هم یه کد جاوا هست که داره روی سیستم شما اجرا میشه. خود Netbeans رو فرض کنید مثلاً با eclipse نوشتن، ممکنه اون موقع که داشتن مینوشتنش مشکلی با eclipse نداشتن، ولی برنامهی خودشون (Netbeans) یه جوری شده که الان روی سیستم شما کند اجرا میشه.
نمی دونم این تعداد زیاد permgen طبیعی است یا نه، در حالتی که تعداد کلاس های hibernate زیاد است. راهی است که در حین کدزدن، سرعت نت بینز رو افزایش داد، افزایش ram مورد نیاز پروژه در نت بینز چه از طریق option و چه از طریق تغییر فایل های config اون ، بهبود چندانی نداشت.
همونطور که قبلاً گفتم، برنامههایی که از Reflection خیلی استفاده میکنن (مثل برنامههایی که سیستم plugin دارن، مثل همین IDE ها) خیلی با PermGen کار میکنن.
یه مقدار دربارهی PermGen توضیح بدم:
حافظه توی JVM به دو بخش اصلی تقسیم میشه: Heap و Non-Heap.
هر شیء که توی برنامهی جاوا new میشه، داخل Heap قرار میگیره. اما بخشهای دیگهای هم هستن که داخل Heap قرار نمیگیرن، مثل اطلاعات کلاسها و توابع، و همچنین خود bytecode جاوا که داره اجرا میشه.
Heap خودش به ۳ بخش تقسیم میشه:
Eden یا New
Survivor
Old
برای این که راحتتر متوجه بشید، با یه مثال عددی جلو میرم. فرض کنید حافظه رو به این صورت تخصیص دادیم:
New = 1g
Survivor = 128m
Old = 3g
هر شیء که ساخته میشه، در داخل بخش New ساخته میشه. وقتی که ظرفیت بخش New به انتها نزدیک میشه (مثلاً ۷۵٪ اش پر میشه، یعنی اینجا میشه ۷۵۰ مگابایت)، فرایند Garbage Collection روی این بخش انجام میشه. یه تعدادی از اشیاء از GC جون سالم به در میبرن! یعنی هنوز زمان پاک شدنشون نرسیده و در حال استفاده هستن. ولی معمولاً تعداد زیادی از اشیاء garbage شدن و جمعآوری میشن. مثلاً فرض کنید توی مثال ما، از ۷۵۰ مگابایتی که اشیاء اشغال کردن، ۵۰ مگاباتش زنده میمونه و بقیه جمعآوری میشه. این اشیاء که زنده میمونن، به حافظهی دوم، یعنی Survivor منتقل میشن. پس در انتهای هر بار فرایند gc روی بخش New، کل این بخش خالی میشه و احتمالاً یه مقداری به بخش Survivor منتقل میشه.
دفعهی بعد که GC میخواد اجرا بشه، روی فضای Survivor هم اجرا میشه و در نتیجه احتمالاً باز از اون ۵۰ مگاباتی که دفعهی قبل اومدن توی این فضا، یه مقداریشون جمعآوری میشن. اونایی که زنده میمونن، عمرشون میشه ۲ (یعنی در طول ۲ بار اجرای gc زنده موندن).
این کار هی تکرار میشه، تا جایی که به یه عدد مشخص (مثلاً فرض کنید ۸) میرسه. یعنی مثلاً ۸ بار فرایند gc اجرا میشه و یه تعداد شیء همچنان زنده میمونن که عمرشون هست ۸ (دقت کنید که وقتی یه تعداد شیء عمرشون ۸ هست، یه تعداد هم عمرشون ۷ هست، یه تعداد ۶، و همینطوری الی آخر). اینهایی که عمرشون ۸ شده، JVM فرض میکنه که اشیائی هستن که قرار نیست حالا حالا ها از بین برن و در نتیجه بهشون میگه Old و از فضای Survivor ورشون میداره میبره توی فضای Old.
پس در طول زندگی JVM، فضای New دائم پر و خالی میشه، فضای Survivor آروم آروم پر میشه، و بعد از یه مدت یه مقدار خالی میشه میره توی Old، و این فرایند دیگه به صورت ثابت هست. یعنی یه سرریز دائمی از Survivor به Old داریم (چون اونایی که عمرشون ۷ بود، سری بعد که gc اجرا بشه میشن ۸ و تخلیه میشن توی Old و همینطور الی آخر).
پس همینطور که جلو میریم، Old هی پر تر و پر تر میشه. تا جایی که به یه حد خطرناک میرسه (مثلاً همون ۷۵٪). اینجا JVM یه GC روی فضای Old انجام میده که از GC های قبلی خیلی سنگینتره (چون اصولاً Old از New بزرگتره) که اسمش هست Full GC. اینجا اتفاقات بدی ممکنه بیافته. مثلاً این که در طول فرایند Full GC، به یه مدتی JVM کاملاً pause میشه، یعنی برنامهی شما رسماً هیچ کاری انجام نمیده (یعنی JVM بایت کد شما رو اجرا نمیکنه). خوب توی این فاصله ممکنه هر اتفاق بدی بیافته، مثلاً اگه به یه جا Connection دارید ممکنه اینجا timeout بشه (چون حتی نمیتونه به سرور متناظر heart-beat بفرسته). برای این که این اتفاقات نیافته، چند تا راه هست:
پارامترهای gc رو tune کنید که مدت زمان Full GC حتیالامکان کوتاه بشه (مثلاً برای حافظههایی در حد ۱۰ گیگ، مدت زمان اجرای Full GC رو معمولاً میشه در حد کمتر از نیم ثانیه نگه داشت).
کد رو جوری دستکاری کرد که اشیاء کمتر زنده بمونن و در نتیجه کمتر به فضای Old منتقل بشن، ولی این اصلاً کار راحتی نیست (و بعضی وقتها ممکنه امکانپذیر نباشه به خاطر ماهیت برنامه).
در نهایت میتونید از JVM های دیگه استفاده کنید. مثلاً یه JVM هست به نام Zing (http://www.yooz.ir/?v=2&st=&q=zing%20jvm&spc=0) که یه Garbage Collector داره که non-stop کار میکنه و اصلاً برنامهی شما رو متوقف نمیکنه. برای حافظههای بزرگ هم طراحی شده (مثلاً ۵۰ گیگ به بالا). و البته پولیه!
پارامترهای Xmx و Xms و Xmn هم که موقع اجرای برنامهی جاوا به دستور java داده میشه، مشخص کنندهی اندازهی فضای Heap هستن. Xmx حداکثر فضای Heap رو مشخص میکنه، Xms مقداری که همون اول توسط JVM گرفته میشه رو مشخص میکنه، و Xmn هم مقدار حافظهی New رو مشخص میکنه. در مورد اندازهی Survivor space هم یه پارامتر XX:SurvivorRatio هست که البته ممکنه توسط JVM کلاً ignore بشه، چون خودش میتونه به صورت پویا این مقادیر رو در بیاره.
البته اینجا کلللللللی ریزهکاری دیگه هم وجود داره که دیگه نگفتم! مثلاً این که واقعاً یک دونه survivor وجود نداره، دو تا وجود داره! توضیحش اینجا هست:
http://stackoverflow.com/questions/10695298/java-gc-why-two-survivor-regions
خوب اینا همهاش دربارهی Heap بود. حالا بریم سراغ بخش non-heap. اون بخشی که اطلاعات کلاسها و توابع توش ذخیره میشه، اسمش هست Permanent Generation که مخففش میشه permgen. وقتی برنامهی شما تعداد زیادی کلاس بارگذاری میکنه، مقدار زیادی از permgen رو اشغال میکنه.
برای توضیح بیشتر میتونید این صفحهی ویکیپدیا رو ببینید:
https://en.wikipedia.org/wiki/Java_virtual_machine
مخصوصاً این بخشش:
The permanent generation (or permgen) was used for class definitions and associated metadata prior to Java 8. Permanent generation was not part of the heap. The permanent generation was removed from Java 8.
بنابراین وقتی خطای پر شدن permgen دریافت میکنید، هر چقدر هم کل حافظه رو افزایش بدید شاید تأثیر خاصی نداشته باشه (چون معمولاً Heap هست که افزایش پیدا میکنه نه permgen).
برای افزایش میزان PermGen، پارامترهای زیر رو به JVM بدید:
-XX:PermSize=256m
-XX:MaxPermSize=256m
اینجا مقدار اولیه و حداکثر PermGen رو برابر ۲۵۶ مگابایت دادیم. شما متناظر با سیستم خودتون (RAM موجود و مقدار فعلی PermGen) این رو تغییر بدید.
برای فهمیدن مقدار فعلی PermGen هم میتونید از jconsole یا jvisualvm استفاده کنید. اگه لازمه دربارهاش توضیح بیشتری بدم بفرمایید.
mohammad_ms_ms
پنج شنبه 10 اردیبهشت 1394, 22:28 عصر
سید عزیز دستت درد نکنه
خیلی عالی بود
دقیق و فنی
نتونستم به تشکر خالی قناعت کنم
واقعا ای ولا داری
چند سوال
مقدمه : من سرولت می نویسم با نت بینز
1 - چطور برنامه بنویسیم که اشیائی که میگیریم زود پاک بشن و نابود بشند؟
2 - چرا وقتی با session زیاد کار می کنم زود حافظه heap پر میشه؟
3 - پروفایلر روی نت بینز نصب کردم، همینطور بیخود زیاد میشه حجم داخل heap زیاد میشه. چرا اینجوریه؟
4 - همونطور که گفتم خیلی از پروفایلر جزئیات مد نظرم را نگرفتم ، راهنمایی در باره پروفایلر نت بینز سراغ دارید؟
یک پیشنهاد کاس یک تاپیک مخصوص heap مدیریتش راه مینداختید.
تشکر مجدد
-سیّد-
دوشنبه 21 اردیبهشت 1394, 20:51 عصر
اول یه معذرتخواهی کنم بابت دیر شدن جواب، من مسافرت بودم، بعدش هم که برگشتم باید به index موتور رسیدگی میکردم، این شد که فرصت نشد زودتر جواب بدم.
سید عزیز دستت درد نکنه
خواهش میکنم، وظیفه اس. بالاخره ما هم از یه سری دیگه یاد گرفتیم، باید زکات علممون رو بدیم!
چند سوال
مقدمه : من سرولت می نویسم با نت بینز
1 - چطور برنامه بنویسیم که اشیائی که میگیریم زود پاک بشن و نابود بشند؟
توی جاوا به طور کلی شما کنترل زیادی روی این که اشیاء کی از بین میرن ندارید. یعنی مثل ++C نمیتونید یک شیء که ساختید رو وقتی کارتون باهاش تموم شد delete کنید. ولی چند تا کار میشه کرد:
- هر وقت کارتون با یه شیء تموم شد، هر چه زودتر ولش کنید. یعنی هر reference ای به اون شیء هست از بین ببرید.
این ول کردن reference معمولاً به طور طبیعی توی یه برنامه انجام میشه. در واقع درستتر اینه که اینجوری بگیم: باید حواستون باشه که reference های اضافی نگه ندارید. برای مثال، وقتی که دارید محتویات یه فایل رو خط به خط میخونید، به طور معمول این کار توسط یه بافر انجام میشه. یعنی اول مثلاً ۴ کیلوبایت از محتویات فایل خونده میشه (این کار توی لایههای زیری به صورت خودکار انجام میشه، مثلاً توی کلاس BufferedReader)، بعد توش میره جلو تا به اولین کاراکتر enter برسه و در نتیجه خط اول رو پیدا میکنه و به شما برمیگردونه. مثلاً این خط میشه ۱۰۰ بایت. بنابراین یه شیء buffer اون پایین ساخته شده که توسط BufferedReader نگهداری و استفاده میشه (یعنی شیء BufferedReader یک reference به این بافر نگهداری میکنه و در نتیجه garbage نیست). توی سطح بالاتر، یه String صد بایتی ساخته میشه و اطلاعات از توی بافر توی اون کپی میشن و این String به شما برگردونده میشه. حالا اینجا دو حالت هست: یا میخواین این رشته رو نگهداری کنید (مثلاً توی یه List یا یه Map) و بعداً توی اجرای برنامه ازش استفاده کنید، یا این که همینجا تحلیلش میکنید و بر اساسش یه عملیاتی رو انجام میدید (مثلاً فرض کنید تعداد خطوط فایل که بیش از ۵۰ بایت هستن رو میشمرید).
اگه حالت اول باشه، شیء String شما تا آخرین وقتی که ازش استفاده میکنید garbage نخواهد شد و همراه شما زندگی میکنه. مثلاً فرض کنید یه dictionary توی حافظه load میکنید و ازش توی برنامه استفاده میکنید. بنابراین تا آخر برنامه این اطلاعات توی حافظه خواهند موند و کاری از دست شما بر نمیاد!
اما اگه حالت دوم باشه، به محض این که به ابتدای حلقهی پردازش فایلتون برمیگردید، reference اون String رو رها میکنید و در نتیجه مستعد garbage شدن میشه. یعنی:
BufferedReader br = ...;
while(true) {
String line = br.readLine();
if (line == null)
break;
... // پردازش خط خوانده شده
}
اون متغیر line یه متغیر محلی هست (دقت کنید: reference اش یه متغیر محلی هست و توی stack قرار داره، ولی مقدارش که از توی تابع readLine برگردونده میشه و new شده، داخل heap هست. یعنی یه متغیر توی stack داریم که مقدارش هست یه اشارهگر به یه خونه از heap). به محض این که بر میگردیم به اول حلقه، این متغیر از بین میره(*) و بعدش دوباره به وجود میاد و یه مقدار جدید میگیره (که خط بعد هست). بنابراین خط قبلی تبدیل به garbage میشه.
(*) یه توضیح خیلی کوتاه: به صورت تئوری این متغیر محلی هی از بین میره و به وجود میاد، ولی توی عمل در اثر optimization هایی که انجام میشه دیگه هی نمیره و برگرده. نگران performance نباشید! :چشمک:
یه حالت دیگه هم بگم: فرض کنید متغیر line رو بیرون حلقه تعریف کرده باشید:
BufferedReader br = ...;
String line = null;
while(true) {
line = br.readLine();
if (line == null)
break;
... // پردازش خط خوانده شده
}
فرق این حالت با حالت قبلی اینه که توی این حالت متغیر بلایی سرش نمیاد! فقط reference هی رونویسی میشه و در نتیجه باز هم خط قبلی garbage میشه.
یه توضیح کلی هم در مورد gc بدم:
garbage، شیئی هست که هیچ reference زندهای بهش وجود نداشته باشه. بنابراین تا وقتی که یه شیء توسط reference های thread های زنده توی برنامهی شما قابل دسترس هست، garbage نخواهد بود و به محض این که تمام reference هاش رها بشن، به یه garbage بالقوه تبدیل میشه. دقت کنید که این garbage بالقوه، بلافاصله توسط jvm جمعآوری نمیشه و فضای اشغالشدهاش آزاد نمیشه. پس کی میشه؟ وقتی که garbage collection بعدی انجام بشه. همونطور که قبلاً اشاره کردم، فرایند garbage collection به صورت خودکار وقتی اجرا میشه که حافظهی eden شما نزدیک پر شدن باشه (یا Full GC وقتی که حافظهی Old رو به پر شدن هست اجرا میشه). اما اجرای فرایند gc، یه دلیل دیگه هم ممکنه داشته باشه: درخواست کاربر به صورت explicit از jvm برای اجرای gc. این کار توسط فراخوانی تابع System.gc انجام میشه. اگه javadoc مربوط به این تابع رو بخونید، میبینید که نوشته که این فقط یک درخواست از jvm هست و jvm هر وقت که خودش صلاح بدونه این کار رو انجام میده. بنابراین jvm هیچ الزامی نداره که هر وقت این تابع رو فراخوانی کردید، همون موقع بره و gc رو شروع کنه.
اما یه توصیه بهتون میکنم: هیچ وقت، یعنی دقیقاً هییییییییییییییییییچ وقت از این تابع استفاده نکنید! یعنی ما از این تابع هیچ خیری ندیدیم!
فراخوانی این تابع نه تنها تقریباً هیچ فایدهای نداره، بلکه بسیار هم به ضرر برنامهی شما هست. فراخوانیهای پیاپی این تابع به شدت اجرای برنامه رو کند میکنه. توی همون مثالی که بالاتر زدم میتونیم این ماجرا رو تست کنیم:
package ir.me;
public class Test2 {
static void f(int x) {
long i = 0;
for (int j = 0; j < 10; j++) {
for (int k = 0; k < 2000000000; k++) {
if (x > 3)
i += 2;
}
if (x >= 7)
System.gc();
}
System.out.println(i);
}
public static void main(String[] args) {
for (int i = 0; i < 9; i++) {
long t1 = System.currentTimeMillis();
f(i);
long t2 = System.currentTimeMillis();
System.out.println("Time: " + (t2 - t1));
}
}
}
تفاوت اصلی این برنامه با قبلی اینه که تعداد اجرای حلقهی اصلی رو به ۹ بار افزایش دادم، و همچنین یه مقدار تغییر توی عددها دادم که زمانها قابل تحمل بشه! این یه نمونه اجراش:
0
Time: 8
0
Time: 3
0
Time: 0
0
Time: 1
40000000000
Time: 981
40000000000
Time: 1000
40000000000
Time: 25109
40000000000
Time: 25160
40000000000
Time: 25450
خوب اثر فراخوانی System.gc رو ملاحظه میفرمایید!!!
در ضمن فراخوانی این تابع کلاً jit رو نابود میکنه! من همین برنامه رو چندین بار عدداش رو بالا و پایین کردم و اجرا کردم، و کلاً منفجر شد! :لبخند:
البته حواستون باشه که روی micro-benchmark ها به صورت کامل حساب نکنید. این موضوع که تابع System.gc بسیار سیستم رو کند میکنه، موضوعیه که من به تجربه طی چندین سال دریافتم و با یه micro-benchmark ساده متوجهش نشدم!
خوب حالا که این تابع اینقدر بد و به درد نخور هست(!)، یه پارامتر متناظرش برای JVM گذاشتن:
-XX:+DisableExplicitGC
اگه این پارامتر رو به jvm بدید، کلاً توجهی به فراخوانی System.gc نخواهد کرد. توصیه میکنم اگه دارید کار مهمی با جاوا انجام میدید، حتماً از این پارامتر استفاده کنید! دلیلش هم اینه که شاید خودتون توی برنامهتون تابع System.gc رو فراخوانی نکنید، ولی نمیتونید مطمئن باشید که توی کتابخونهها و فریمورکهایی که استفاده میکنید هم این تابع فراخوانی نشده باشه.
- تا جایی که لازم نیست، از Object Pool ها و Cache استفاده نکنید.
توجه کنید: گفتم تا جایی که لازم نیست. Object Pool ها (مثل Connection Pool ها برای کار با پایگاههای داده) و Cache دو مفهوم بسیار مهم و مورد نیاز توی برنامههای بزرگ هستن و خیلی به بالا بردن performance کمک میکنن. اما مشکل اینجاس که jvm خیلی با اشیائی که توی حافظه جا خوش میکنن حال نمیکنه! و این دو مفهوم، دو تا از جاهایی هستن که اشیاء رو به مدت طولانی توی حافظه نگه میدارن.
بنابراین نتیجهگیری اخلاقی میشه این: اگه لازم ندارید، از Object Pool و Cache استفاده نکنید!
- تا جایی که میتونید شیء بیخودی نسازید.
درسته که توی جاوا همه چیز (به جز primitive ها) توی heap ساخته میشن و مجبورید new کنید، ولی کسی مجبورتون نکرده برای انجام یه کار ساده صد تا شیء new کنید!
توجه کنید! یه وقتهایی دارید الکی شیء میسازید، ولی نه توسط new کردن مستقیم، بلکه از طریق فراخوانی یه تابع که مال شما نیست و داخلش شیء ساخته میشه. یه مثال بسیار مبتلا بهش توابع String هست. کلاس String یه کلاس Immutable هست، یعنی پس از ساخته شدن، به هیچ وجه به شما اجازه نمیده بهش دست بزنید. بنابراین وقتی که مثلاً تابع substring رو روش فراخوانی میکنید، یه شیء String جدید new میکنه و به شما برمیگردونه. و همینطور در مورد بقیهی توابع String مثل replace و غیره.
یا مثلاً وقتی میخواین یه رشته رو توسط space جدا کنید، چی کار میکنید؟
String s = ...
String[] splitted = s.split(" ");
تابع split رو اگه کد توش رو نگاه کنید، میبینید که شروع میکنه به compile کردن ورودیای که بهش دادید (همون " " که اگه دقت کنید اسم ورودیاش هم regex هست)، یعنی اون رو به عنوان یه regular expression میبینه و شروع میکنه به تحلیلش. این کار علاوه بر هزینهی الکی زیادی که برای CPU داره، هزینهی الکیای هم برای Memory داره. حالا ببینید اگه این کار رو توی یه حلقه که نسبتاًً زیاد اجرا میشه انجام بدید چه بلایی سر سیستم میاد!
به جاش چی کار کنیم؟ میتونید اون regular expression رو یه بار بسازید، و بعد ازش استفاده کنید:
public static final Pattern P_SPACE = Pattern.compile(" ", Pattern.LITERAL);
...
String s = ...
String[] splitted = P_SPACE.split(s);
اینها فقط چند تا مثال بود از عملیات الکیای که حواسمون نیست و انجام میدیم.
- بعد از استفاده از یه شیء، اشارهگر اون رو برابر null قرار بدید.
البته من نمیدونم از نظر علمی و عملی این کار چقدر تأثیر داره، ولی جاهای مختلفی دیدم که این کار رو برای اطمینان میکنن. مثلاً:
void f() {
...
MyObject o = new MyObject();
...
// استفاده از o
...
// اینجا دیگه نیازی به o نداریم
o = null;
// انجام بقیهی کارها
...
...
}
توی این مثال به محض این که کارمون با شیء o تموم شد، reference اش رو رها کردیم و بعد به بقیهی کارها پرداختیم. بنابراین موقع اجرای این تابع به محض این که به این خط برسه، دیگه این شیء آماده میشه برای جمعآوری توسط gc. اگه این کار رو نمیکردیم چی میشد؟ در تئوری باید تابع تا انتهاش اجرا میشد و بعد تازه اونجا بود که reference مورد نظر رها میشد (ممکنه اجرای این تابع چندین ثانیه یا حتی دقیقه طول بکشه، مثلاً فرض کنید توی ادامهی تابع به یه سرویسی connect میشید و باهاش کار میکنید). این که گفتم از نظر تئوری، به خاطر اینه که ممکنه در اثر optimization های صورت گرفته توسط compiler و jit، این reference خودش همونجا رها بشه. ولی همیشه هر چقدر بتونید به jit راهنماییهای بیشتر و بهتری بکنید، مطمئنتر کارش رو انجام میده. یعنی اگه شما نگید، باید خودش تحلیل کنه و بفهمه که دیگه نیازی به اون reference نیست (که هیچ تضمینی وجود نداره این تحلیلش به نتیجه برسه).
- در صورت نیاز و امکان، از حافظهی Off-Heap استفاده کنید.
حافظهی Off-Heap، حافظهای هست که به شما این امکان رو میده که یه بخشی از حافظه رو در خارج از heap برای خودتون بگیرید و باهاش کار کنید. البته مشخصه که این حافظه با heap تفاوت اساسی داره: heap یه حافظهی مدیریت شده توسط jvm هست، در حالی که این حافظه، یه حافظهی خام به صورت flat هست که در اختیار شما قرار میگیره و مدیریتش با خودتون هست. البته کتابخانههایی هستن که از این حافظه استفاده میکنن، ولی کلاً یه کم خطرناکه حسن!
برای استفاده از این حافظه، میتونید به کلاس ByteBuffer مراجعه کنید. بخشی از توضیحات این کلاس:
A direct byte buffer may be created by invoking the allocateDirect factory method of this class. The buffers returned by this method typically have somewhat higher allocation and deallocation costs than non-direct buffers. The contents of direct buffers may reside outside of the normal garbage-collected heap, and so their impact upon the memory footprint of an application might not be obvious. It is therefore recommended that direct buffers be allocated primarily for large, long-lived buffers that are subject to the underlying system's native I/O operations. In general it is best to allocate direct buffers only when they yield a measureable gain in program performance.
چند تا هم پیوند میذارم که دوستان اگه علاقه داشتن بیشتر بررسی کنن:
چند تا سؤال مرتبط توی StackOverflow:
http://stackoverflow.com/questions/6091615/difference-between-on-heap-and-off-heap
http://stackoverflow.com/questions/1329926/how-to-prevent-an-object-from-getting-garbage-collected/30027374#30027374
http://stackoverflow.com/questions/9978459/java-can-we-do-our-own-memory-management
چند صفحه در terracota (این شرکت روی برنامههای جاوا با حافظههای زیاد کار میکنه، شما فرض کن بالای ۱۰۰ گیگ!):
https://terracotta.org/generated/4.2.0/html/bmm-all/index.html#page/BigMemory_Max_Documentation_Set/co-bp_tuning_off_heap_store_performance.html
http://terracotta.org/documentation/4.1/bigmemorygo/configuration/storage-options
در نهایت هم این پارامتر متناظر برای JVM:
-XX:MaxDirectMemorySize=4G
https://www-01.ibm.com/support/knowledgecenter/SSYKE2_8.0.0/com.ibm.java.lnx.80.doc/diag/appendixes/cmdline/xxmaxdirectmemorysize.html
- تابع finalize رو حتیالامکان در کلاسهاتون override نکنید!
این تابع در نگاه اول خیلی تابع خوب و به درد بخوری به نظر میرسه! ولی به شدت گول زننده هست! کار این تابع چیه؟ اینه که موقعی که یه شیء تبدیل به آشغال(!!!) شد، قبل از جمعآوری شدن توسط jvm، حتماً تابع finalize اش فراخوانی میشه.
نکته اینجاست: اگه شما این تابع رو رونویسی نکنید، خوب چیزی برای فراخوانی شدن وجود نداره و در نتیجه به محض این که شیء شما کاندید جمعآوری زبالهها از سطح jvm میشه، اولین باری که بعدش gc اجرا میشه، بلافاصله جمعآوری میشه، و خدانگهدار.
اما اگه شما این تابع رو رونویسی بکنید، وقتی که شیء شما کاندید جمعآوری زباله شد، و زمان gc بعدی فرا رسید، gc وقتی به این شیء میرسه میبینه که هنوز نمیتونه جمعش کنه، چون باید تابع finalize اش قبل از دور ریخته شدن فراخوانی بشه. پس چی کار میکنه؟ نمیشه که همینجا واسته و این تابع رو صدا بزنه! چون ممکنه اجرای finalize شیء مورد نظر چندین ثانیه طول بکشه و فرایند gc باید در اسرع وقت تموم بشه. برای همین میدتش دست یه thread دیگه به نام Finalizer (این thread توی تمام برنامههای جاوا توسط jvm همون اول اجرا میشه. میتونید با یه برنامه مثل jvisualvm اون رو توی فهرست thread های برنامهی جاواتون رو ببینید) و بدون دور ریختن شیء، به کار خودش ادامه میده. حالا تا وقتی که این جناب Finalizer این شیء رو از صفش برداره و تابع finalize اش رو فراخونی کنه و اون هم تموم بشه، این شیء زنده میمونه. ممکنه در طول این فرایند (که معلوم نیست چقدر طول بکشه)، چندین دورهی دیگه gc هم اجرا بشه و بره پی کارش و این شیء هنوز زنده باشه.
پس تفاوتش شد این:
اگه finalize نداشته باشید، در اولین اجرای gc بعد از رها شدن تمام reference های شیء، شیئتون از حافظه پاک میشه. یعنی شیء شما بعد از garbage شدن، حداکثر به مدت یک فاصله بین gc ها زنده میمونه.
اما اگه finalize داشته باشید، شیء شما حداقل به مدت ۲ دورهی gc زنده میمونه. یعنی در بهترین حالت، در gc دوم بعد از garbage شدن جمعآوری میشه.
اگه هنوز حرفمو باور نکردید، اینم چند تا reference! :چشمک:
http://stackoverflow.com/questions/2506488/when-is-the-finalize-method-called-in-java
Finalizers are unpredictable, often dangerous, and generally unnecessary. never do anything time-critical in a finalizer. never depend on a finalizer to update critical persistent state.
یعنی فحش داده به finalize! :لبخند:
http://howtodoinjava.com/2012/10/31/why-not-to-use-finalize-method-in-java/
https://www.securecoding.cert.org/confluence/display/java/MET12-J.+Do+not+use+finalizers
- در نهایت، تنظیمات gc رو tune کنید.
این کار، کار راحتی نیست! باید با پارامترهای مختلف gc بازی کنید تا بهترین حالت برای برنامهی خودتون رو پیدا کنید. البته نسخههای جدید جاوا سعی میکنن خودشون به صورت پویا این کار رو انجام بدن.
خوب، علاوه بر این که دهنم کف کرد(!!)، الان دیگه در این مورد چیزی به ذهنم نمیرسه.
2 - چرا وقتی با session زیاد کار می کنم زود حافظه heap پر میشه؟
من سالهاست که با Servlet و تکنولوژیهای مرتبطش توی جاوا خیلی کار نکردم. برای همین خیلی دقیق نمیتونم بگم. ولی دلیلش میتونه این باشه که اطلاعات session که نسبتاً مدت طولانیای باید باقی بمونن، توی حافظه نگهداری میشه و در نتیجه heap شما رو پر میکنه. به عنوان مثال توی php میدونم که اطلاعات session روی دیسک ذخیره میشه و در نتیجه حافظهای اشغال نمیکنه (البته مدل کار یه برنامهی php با یه برنامهی جاوایی web-based کلاً فرق میکنه).
3 - پروفایلر روی نت بینز نصب کردم، همینطور بیخود زیاد میشه حجم داخل heap زیاد میشه. چرا اینجوریه؟
متوجه سؤالتون نشدم. منظورتون اینه که به خاطر این که پروفایلر نصب کردین، حجم heap مصرفی الکی زیاد میشه؟ یا این که با پروفایلر دیدید که حجم heap مصرفی زیاد شده و دلیلش رو نمیدونید؟ اگه دقیقتر سؤالتون رو بگید شاید بتونم کمکی بکنم.
4 - همونطور که گفتم خیلی از پروفایلر جزئیات مد نظرم را نگرفتم ، راهنمایی در باره پروفایلر نت بینز سراغ دارید؟
من خیلی با NetBeans کار نکردم، برای همین هم در این مورد کمکی از دستم بر نمیاد.
اما به طور کلی پروفایلرهای دیگهای هم موجود هستند، مثل VisualVM خود Oracle (دستورش jvisualvm هست).
هشدار! در صورت پروفایل کردن برنامه، اجرای برنامه به شدت کند میشه، و حتی ممکنه برنامهتون بپکه! من چندین بار با این مسئله برخورد کردم که بعد از پروفایل کردن و یه کم ور رفتن با برنامه، crash کرده (البته در درصد زیادی از مواقع این اتفاق نمیافته). همچنین موقع شروع پروفایل و پایان پروفایل، ممکنه برنامهتون برای مدتی بره تو باقالیا! چون پروفایلر موقع شروع پروفایل، تمام کلاسهای load شده رو با کلاسهای خودش جایگزین میکنه (و موقع پایانش هم برعکسش رو انجام میده) و اگه تعداد کلاسهاتون زیاد باشه یه کم ناراحت میشه!
بنابراین اگه برنامهتون خیلی حیاتی هست و نباید دست بهش بخوره و اگه بلایی سرش بیاد رییستون اخراجتون میکنه(!)، بهتره اون رو پروفایل نکنید و به جاش یا یه instance دیگه از برنامه رو که خیلی حیاتی نیست پروفایل کنید (مثلاً خودتون به صورت local اجراش کنید و اون رو پروفایل کنید)، یا اگه میخواین حتماً توی یه instance خاص ببینید چه خبره (که از قضا خیلی حیاتی هم هست!)، میتونید از Sampler استفاده کنید که کارش به مراتب سبکتر هست و مشکلی هم برای برنامهتون به وجود نمیاره.
یک پیشنهاد کاس یک تاپیک مخصوص heap مدیریتش راه مینداختید.
میگم اینم فکر خوبیه ها! :لبخند:
vBulletin® v4.2.5, Copyright ©2000-1403, Jelsoft Enterprises Ltd.