นี่เป็นเรื่องร้องเรียนมายาวนานกับ Java แต่ก็ไม่มีความหมายส่วนใหญ่และมักขึ้นอยู่กับการดูข้อมูลที่ผิด การใช้ถ้อยคำตามปกติคือ "Hello World บน Java ใช้เวลา 10 เมกะไบต์! ทำไมต้องใช้มัน?" นี่เป็นวิธีที่จะทำให้ Hello World บน JVM 64- บิตอ้างว่าใช้เวลามากกว่า 4 กิกะไบต์ ... อย่างน้อยโดยการวัดในรูปแบบเดียว
java -Xms1024m -Xmx4096m com.example.Hello
วิธีต่างๆในการวัดความจำ
บน Linux คำสั่งtopให้หมายเลขหน่วยความจำที่แตกต่างกันหลายหมายเลข นี่คือสิ่งที่กล่าวเกี่ยวกับตัวอย่าง Hello World:
PID ผู้ใช้ PR NI VIRT RES SHR S% CPU% MEM TIME + คำสั่ง
2120 kgregory 20 0 4373m 15m 7152 S 0 0.2 0: 00.10 java
- VIRT เป็นพื้นที่หน่วยความจำเสมือน: ผลรวมของทุกสิ่งในแผนที่หน่วยความจำเสมือน (ดูด้านล่าง) มันไม่มีความหมายส่วนใหญ่ยกเว้นเมื่อไม่มี (ดูด้านล่าง)
- RES คือขนาดชุดการมีถิ่นที่อยู่: จำนวนหน้าที่อยู่ใน RAM ในปัจจุบัน ในเกือบทุกกรณีนี่เป็นหมายเลขเดียวที่คุณควรใช้เมื่อพูดว่า "ใหญ่เกินไป" แต่ก็ยังไม่ค่อยดีนักโดยเฉพาะเมื่อพูดถึง Java
- SHR คือจำนวนหน่วยความจำภายในที่ใช้ร่วมกับกระบวนการอื่น สำหรับกระบวนการ Java สิ่งนี้ จำกัด เฉพาะไลบรารีที่ใช้ร่วมกันและ JARfiles ที่แมปหน่วยความจำ ในตัวอย่างนี้ฉันมีกระบวนการ Java เพียงกระบวนการเดียวเท่านั้นดังนั้นฉันจึงสงสัยว่า 7k เป็นผลมาจากไลบรารีที่ใช้โดยระบบปฏิบัติการ
- SWAP จะไม่เปิดใช้งานตามค่าเริ่มต้นและไม่ได้แสดงไว้ที่นี่ มันแสดงถึงปริมาณของหน่วยความจำเสมือนที่เป็นอยู่ในปัจจุบันมีถิ่นที่อยู่ในดิสก์หรือไม่ก็เป็นจริงในพื้นที่แลกเปลี่ยน ระบบปฏิบัติการดีมากเกี่ยวกับการเก็บเพจที่ใช้งานอยู่ใน RAM และการรักษาเพียงอย่างเดียวสำหรับการแลกเปลี่ยนคือ (1) ซื้อหน่วยความจำเพิ่มเติมหรือ (2) ลดจำนวนกระบวนการดังนั้นจึงเป็นการดีที่สุดที่จะไม่สนใจหมายเลขนี้
สถานการณ์สำหรับ Windows Task Manager นั้นซับซ้อนกว่าเล็กน้อย ภายใต้ Windows XP จะมีคอลัมน์ "การใช้หน่วยความจำ" และ "ขนาดหน่วยความจำเสมือน" แต่เอกสารอย่างเป็นทางการเงียบในสิ่งที่พวกเขาหมายถึง Windows Vista และ Windows 7 เพิ่มคอลัมน์อื่น ๆ และพวกเขากำลังจริงเอกสาร สิ่งเหล่านี้การวัด "ชุดการทำงาน" มีประโยชน์มากที่สุด มันประมาณสอดคล้องกับผลรวมของ RES และ SHR บน Linux
ทำความเข้าใจกับแผนที่หน่วยความจำเสมือน
หน่วยความจำเสมือนที่ใช้โดยกระบวนการคือผลรวมของทุกสิ่งที่อยู่ในแผนที่หน่วยความจำของกระบวนการ ซึ่งรวมถึงข้อมูล (เช่น Java heap) แต่ยังรวมถึงไลบรารีที่แบ่งใช้และไฟล์ที่แม็พหน่วยความจำทั้งหมดที่ใช้โดยโปรแกรม บน Linux คุณสามารถใช้คำสั่งpmapเพื่อดูทุกสิ่งที่แมปลงในพื้นที่กระบวนการ (จากที่นี่ไปหมดฉันจะอ้างถึง Linux เท่านั้นเพราะมันคือสิ่งที่ฉันใช้ฉันแน่ใจว่ามีเครื่องมือที่เทียบเท่าสำหรับ windows) นี่คือข้อความที่ตัดตอนมาจากแผนที่หน่วยความจำของโปรแกรม "Hello World" แมปหน่วยความจำทั้งหมดมีความยาวเกิน 100 บรรทัดและไม่ใช่เรื่องแปลกที่จะมีรายการหนึ่งพันบรรทัด
0000000040000000 36K rx-- /usr/local/java/jdk-1.6-x64/bin/java
0000000040108000 8K rwx-- /usr/local/java/jdk-1.6-x64/bin/java
0000000040eba000 676K rwx-- [anon]
00000006fae00000 21248K rwx-- [anon]
00000006fc2c0000 62720K rwx-- [anon]
0000000700000000 699072K rwx-- [anon]
000000072aab0000 2097152K rwx-- [anon]
00000007aaab0000 349504K rwx-- [anon]
00000007c0000000 1048576K rwx-- [anon]
...
00007fa1ed00d000 1652K r-xs- /usr/local/java/jdk-1.6-x64/jre/lib/rt.jar
...
00007fa1ed1d3000 1024K rwx-- [anon]
00007fa1ed2d3000 4K ----- [anon]
00007fa1ed2d4000 1024K rwx-- [anon]
00007fa1ed3d4000 4K ----- [anon]
...
00007fa1f20d3000 164K rx-- /usr/local/java/jdk-1.6-x64/jre/lib/amd64/libjava.so
00007fa1f20fc000 1020K ----- /usr/local/java/jdk-1.6-x64/jre/lib/amd64/libjava.so
00007fa1f21fb000 28K rwx-- /usr/local/java/jdk-1.6-x64/jre/lib/amd64/libjava.so
...
00007fa1f34aa000 1576K rx-- /lib/x86_64-linux-gnu/libc-2.13.so
00007fa1f3634000 2044K ----- /lib/x86_64-linux-gnu/libc-2.13.so
00007fa1f3833000 16K rx-- /lib/x86_64-linux-gnu/libc-2.13.so
00007fa1f3837000 4K rwx-- /lib/x86_64-linux-gnu/libc-2.13.so
...
คำอธิบายอย่างรวดเร็วของรูปแบบ: แต่ละแถวเริ่มต้นด้วยที่อยู่หน่วยความจำเสมือนของกลุ่ม ตามด้วยขนาดเซ็กเมนต์สิทธิ์และแหล่งที่มาของเซ็กเมนต์ รายการสุดท้ายซึ่งจะมีทั้งไฟล์หรือ "อานนท์" ซึ่งบ่งชี้ว่าบล็อกของหน่วยความจำที่จัดสรรผ่านmmap
เริ่มจากด้านบนเรามี
- ตัวโหลด JVM (เช่นโปรแกรมที่เรียกใช้เมื่อคุณพิมพ์
java
) นี่มันเล็กมาก สิ่งที่ทำก็คือโหลดในไลบรารีที่แบ่งใช้ซึ่งเก็บรหัส JVM จริงไว้
- กลุ่มบล็อกอานนท์ที่เก็บฮีป Java และข้อมูลภายในไว้ นี่คือ Sun JVM ดังนั้นกองจึงถูกแบ่งออกเป็นหลายชั่วอายุซึ่งแต่ละหน่วยเป็นหน่วยความจำของตัวเอง โปรดทราบว่า JVM จัดสรรพื้นที่หน่วยความจำเสมือนตาม
-Xmx
ค่า สิ่งนี้อนุญาตให้มีกองที่ต่อเนื่องกัน -Xms
ค่าใช้ภายในเพื่อพูดเท่าไหร่ของกองคือ "ในการใช้งาน" เมื่อโปรแกรมเริ่มต้นและการเก็บขยะเป็นทริกเกอร์ขีด จำกัด ที่เดินเข้ามาใกล้
- JARfile ที่แม็พหน่วยความจำในกรณีนี้ไฟล์ที่เก็บ "คลาส JDK" เมื่อหน่วยความจำแมป JAR คุณสามารถเข้าถึงไฟล์ที่อยู่ในนั้นได้อย่างมีประสิทธิภาพ (เทียบกับการอ่านตั้งแต่เริ่มต้นแต่ละครั้ง) Sun JVM จะทำการแมป JAR ทั้งหมดบน classpath หากรหัสแอปพลิเคชันของคุณจำเป็นต้องเข้าถึง JAR คุณยังสามารถแมปหน่วยความจำได้
- ข้อมูลสำหรับแต่ละเธรดสำหรับสองเธรด บล็อก 1M เป็นเธรดสแต็ก ฉันไม่มีคำอธิบายที่ดีสำหรับบล็อก 4k แต่ @ericsoe ระบุว่าเป็น "การ์ดบล็อก": มันไม่มีสิทธิ์ในการอ่าน / เขียนดังนั้นจะทำให้เกิดข้อผิดพลาดในส่วนหากเข้าถึงและ JVM จะจับและแปล
StackOverFlowError
ไปยัง สำหรับแอปจริงคุณจะเห็นหลายสิบถ้าไม่นับหลายร้อยรายการเหล่านี้ซ้ำผ่านแผนที่หน่วยความจำ
- หนึ่งในไลบรารีแบบแบ่งใช้ที่มีรหัส JVM จริง มีหลายสิ่งเหล่านี้
- ไลบรารีแบบแบ่งใช้สำหรับไลบรารีมาตรฐาน C นี่เป็นเพียงหนึ่งในหลายสิ่งที่ JVM โหลดซึ่งไม่ได้เป็นส่วนหนึ่งของ Java อย่างเคร่งครัด
ห้องสมุดที่ใช้ร่วมกันนั้นมีความน่าสนใจเป็นพิเศษ: ห้องสมุดที่ใช้ร่วมกันแต่ละแห่งมีอย่างน้อยสองส่วน: ส่วนอ่านอย่างเดียวที่มีรหัสห้องสมุดและส่วนอ่านเขียนซึ่งมีข้อมูลต่อกระบวนการทั่วโลกสำหรับห้องสมุด (ฉันไม่ทราบว่า กลุ่มที่ไม่มีการอนุญาตคือฉันได้เห็นเฉพาะบน x64 Linux) ส่วนของไลบรารีแบบอ่านอย่างเดียวสามารถแบ่งใช้ระหว่างกระบวนการทั้งหมดที่ใช้ไลบรารี ตัวอย่างเช่นlibc
มีพื้นที่หน่วยความจำเสมือน 1.5M ที่สามารถแชร์ได้
เมื่อขนาดหน่วยความจำเสมือนมีความสำคัญ
แผนที่หน่วยความจำเสมือนมีหลายสิ่งหลายอย่าง บางส่วนเป็นแบบอ่านอย่างเดียวบางส่วนถูกแชร์และบางส่วนถูกจัดสรร แต่ไม่เคยแตะ (เช่นกอง 4Gb เกือบทั้งหมดในตัวอย่างนี้) แต่ระบบปฏิบัติการนั้นฉลาดพอที่จะโหลดเฉพาะสิ่งที่ต้องการเท่านั้นดังนั้นขนาดหน่วยความจำเสมือนจึงไม่เกี่ยวข้องอย่างมาก
ขนาดหน่วยความจำเสมือนที่มีความสำคัญคือถ้าคุณกำลังทำงานบนระบบปฏิบัติการ 32 บิตซึ่งคุณสามารถจัดสรร 2Gb (หรือในบางกรณี 3Gb) ของพื้นที่ที่อยู่ของกระบวนการเท่านั้น ในกรณีที่คุณกำลังเผชิญกับทรัพยากรที่หายากและอาจต้องทำการแลกเปลี่ยนเช่นการลดขนาดฮีปของคุณเพื่อให้แม็พหน่วยความจำแมปไฟล์ขนาดใหญ่หรือสร้างเธรดจำนวนมาก
แต่เนื่องจากเครื่อง 64 บิตนั้นเป็นที่แพร่หลายฉันไม่คิดว่ามันจะนานก่อนที่ Virtual Memory Size จะเป็นสถิติที่ไม่เกี่ยวข้องอย่างสมบูรณ์
ขนาดชุดที่อยู่อาศัยมีความสำคัญเมื่อใด
Resident Set size คือส่วนของพื้นที่หน่วยความจำเสมือนที่อยู่ใน RAM จริง หาก RSS ของคุณเติบโตเป็นส่วนสำคัญของหน่วยความจำกายภาพทั้งหมดของคุณอาจถึงเวลาที่ต้องเริ่มกังวล หาก RSS ของคุณโตขึ้นเพื่อใช้หน่วยความจำกายภาพทั้งหมดของคุณและระบบของคุณเริ่มทำการแลกเปลี่ยนมันเป็นเวลาที่ดีที่จะเริ่มกังวล
แต่ RSS ก็ทำให้เข้าใจผิดโดยเฉพาะอย่างยิ่งในเครื่องที่มีน้ำหนักเบา ระบบปฏิบัติการไม่ได้ใช้ความพยายามอย่างมากในการเรียกคืนหน้าที่ใช้โดยกระบวนการ มีประโยชน์เล็กน้อยที่จะได้รับจากการทำเช่นนั้นและความเป็นไปได้ของความผิดพลาดของหน้ากระดาษที่มีราคาแพงหากกระบวนการสัมผัสหน้าในอนาคต ดังนั้นสถิติ RSS อาจมีหน้าเว็บจำนวนมากที่ไม่ได้ใช้งานอยู่
บรรทัดล่าง
นอกจากว่าคุณจะแลกเปลี่ยนไม่ต้องกังวลมากเกินไปเกี่ยวกับสถิติหน่วยความจำต่างๆที่บอกคุณ ด้วยข้อแม้ที่ RSS ที่เพิ่มขึ้นเรื่อย ๆ อาจบ่งบอกถึงการรั่วไหลของหน่วยความจำบางอย่าง
ด้วยโปรแกรม Java สิ่งสำคัญคือการใส่ใจกับสิ่งที่เกิดขึ้นในกอง จำนวนพื้นที่ทั้งหมดที่ใช้เป็นสิ่งสำคัญและมีขั้นตอนบางอย่างที่คุณสามารถทำได้เพื่อลดพื้นที่ดังกล่าว สำคัญกว่าคือระยะเวลาที่คุณใช้ในการรวบรวมขยะและส่วนใดของกองที่จะถูกรวบรวม
การเข้าถึงดิสก์ (เช่นฐานข้อมูล) มีราคาแพงและหน่วยความจำราคาถูก หากคุณสามารถแลกเปลี่ยนหนึ่งสำหรับอื่น ๆ ทำเช่นนั้น