การใช้งานหน่วยความจำเสมือนจาก Java ภายใต้ Linux ใช้หน่วยความจำมากเกินไป


259

ฉันมีปัญหากับแอปพลิเคชัน Java ที่ทำงานภายใต้ Linux

เมื่อฉันเปิดแอปพลิเคชันโดยใช้ขนาดฮีพสูงสุดเริ่มต้น (64 MB) ฉันเห็นการใช้แอพพลิเคชั่นท็อปที่จัดสรรหน่วยความจำเสมือน 240 MB ให้กับแอปพลิเคชัน สิ่งนี้สร้างปัญหาบางอย่างกับซอฟต์แวร์อื่น ๆ บนคอมพิวเตอร์ซึ่งค่อนข้าง จำกัด ทรัพยากร

หน่วยความจำเสมือนที่สำรองไว้จะไม่ถูกใช้เท่าที่ฉันเข้าใจเพราะเมื่อเราไปถึงขีด จำกัด ฮีปและการOutOfMemoryErrorโยน ฉันรันแอปพลิเคชันเดียวกันภายใต้ windows และฉันเห็นว่าขนาดหน่วยความจำเสมือนและขนาดฮีปมีความคล้ายคลึงกัน

มีอยู่หรือไม่ว่าฉันสามารถกำหนดค่าหน่วยความจำเสมือนที่ใช้สำหรับกระบวนการ Java ภายใต้ Linux ได้หรือไม่

แก้ไข 1 : ปัญหาไม่ใช่ฮีป ปัญหาคือถ้าฉันตั้งค่า Heap เป็น 128 MB ตัวอย่างเช่น Linux ยังคงจัดสรรหน่วยความจำเสมือน 210 MB ซึ่งไม่จำเป็นเลยทีเดียว **

แก้ไข 2 : การใช้ulimit -vช่วยให้ จำกัด จำนวนหน่วยความจำเสมือน หากขนาดที่ตั้งไว้ต่ำกว่า 204 MB แอปพลิเคชันจะไม่ทำงานแม้ว่าจะไม่ต้องการ 204 MB เพียง 64 MB ดังนั้นฉันต้องการที่จะเข้าใจว่าทำไมจาวาต้องการหน่วยความจำเสมือนมาก สิ่งนี้สามารถเปลี่ยนแปลงได้?

แก้ไข 3 : มีแอปพลิเคชั่นอื่น ๆ อีกมากมายที่ทำงานในระบบซึ่งฝังอยู่ และระบบจะมีการ จำกัด หน่วยความจำเสมือน (จากความเห็นรายละเอียดที่สำคัญ)


ทำไมคุณถึงเกี่ยวข้องกับการใช้งานหน่วยความจำเสมือน? หากคุณต้องการเป็นกังวลให้ดูที่การใช้หน่วยความจำในเครื่องและอ่านคำสั่งต่อไปนี้: free, ps, top
— basszero

2
มีแอปพลิเคชั่นอื่น ๆ ที่ใช้งานในระบบซึ่งฝังอยู่ และระบบจะมีการ จำกัด หน่วยความจำเสมือน
— Mario Ortegón

ahhhh ปีศาจอยู่ในรายละเอียด
— basszero

คุณกำลังใช้งาน Java ประเภทใด IIRC, มาตรฐาน bog (ไม่ใช่ OpenJDK) ฟรี Sun JRE ไม่ได้รับอนุญาตสำหรับการใช้งานแบบฝัง
— Tom Hawtin - tackline

ฉันคิดว่าฉันพลาดการใช้ชิ้นส่วน "ฝังตัว" ... มันเป็นหน่วยความจำ จำกัด และฮาร์ดแวร์มีการปรับแต่ง แต่ก็ยังคงเป็นคอมพิวเตอร์มาตรฐาน
— Mario Ortegón

คำตอบ:


630

นี่เป็นเรื่องร้องเรียนมายาวนานกับ 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 สิ่งสำคัญคือการใส่ใจกับสิ่งที่เกิดขึ้นในกอง จำนวนพื้นที่ทั้งหมดที่ใช้เป็นสิ่งสำคัญและมีขั้นตอนบางอย่างที่คุณสามารถทำได้เพื่อลดพื้นที่ดังกล่าว สำคัญกว่าคือระยะเวลาที่คุณใช้ในการรวบรวมขยะและส่วนใดของกองที่จะถูกรวบรวม

การเข้าถึงดิสก์ (เช่นฐานข้อมูล) มีราคาแพงและหน่วยความจำราคาถูก หากคุณสามารถแลกเปลี่ยนหนึ่งสำหรับอื่น ๆ ทำเช่นนั้น


9
คุณควรคำนึงถึงว่าส่วนของหน่วยความจำที่เปลี่ยนไปในขณะนี้หายไปจากการวัด RES ดังนั้นคุณอาจมีค่า RES ต่ำ แต่เพียงเพราะแอปพลิเคชันไม่ได้ใช้งานและมีการสลับฮีปไปยังดิสก์เป็นจำนวนมาก Java ทำหน้าที่ที่แย่มากในการแลกเปลี่ยน: ในแต่ละ GC ส่วนใหญ่ฮีปจะถูกเดินและคัดลอกดังนั้นหากฮีปของคุณส่วนใหญ่อยู่ในการแลกเปลี่ยน GC จะต้องโหลดมันทั้งหมดลงในหน่วยความจำหลัก
— jrudolph

1
คำตอบที่ดี kdgregory! ฉันทำงานในสภาพแวดล้อมแบบฝังโดยใช้ CF ที่ไม่มีพื้นที่สว็อป ดังนั้นตามคำตอบของคุณค่า VIRT, SWAP และ nFLT ทั้งหมดของฉันนั้นมาจากไฟล์ที่แม็พหน่วยความจำ ... ซึ่งตอนนี้สมเหตุสมผลแล้วที่จะเหมียว คุณรู้หรือไม่ว่าค่า SWAP เป็นตัวแทนของหน้าเว็บที่ยังไม่ได้โหลดลงในหน่วยความจำหรือหน้าเว็บที่เปลี่ยนไปจากหน่วยความจำหรือไม่หรือทั้งสองอย่าง? เราจะทราบได้อย่างไรว่ามีการโจมตีอย่างรุนแรง (แผนที่ต่อเนื่องจากนั้นสลับออก)
— Jeach

2
@Jeach - ฉันรู้สึกประหลาดใจที่มีการรายงานการแลกเปลี่ยนดังนั้นให้บูต "การเดินทาง Linux" ของฉัน (thumb drive ที่มี Ubuntu 10.04 และไม่มีการสลับ) เมื่อฉันเปิดใช้งานคอลัมน์ "SWAP" ด้านบนฉันเห็น Eclipse มี 509m เมื่อฉันดูPMapแล้วพื้นที่เสมือนทั้งหมดคือ 650m ดังนั้นฉันจึงสงสัยว่ารูป "SWAP" แสดงถึงหน้าทั้งหมดบนดิสก์ไม่ใช่เฉพาะที่ไม่ได้อยู่ในหน่วยความจำ
— kdgregory

2
สำหรับคำถามที่สองของคุณ: หากคุณอ่านหน้าอย่างต่อเนื่องจากแฟลชการ์ดเวลารอคอยของ IO ของคุณ (แสดงในบทสรุปของอันดับสูงสุดเป็น "% wa") ควรจะสูง อย่างไรก็ตามระวังว่าสิ่งนี้จะสูงสำหรับกิจกรรมใด ๆ โดยเฉพาะอย่างยิ่งการเขียน (สมมติว่าโปรแกรมของคุณทำ)
— kdgregory

1
> บล็อก 1M เป็นเธรดสแต็ก ฉันไม่รู้ว่าจะเกิดอะไรขึ้นกับบล็อก 4K บล็อก 4K - ซึ่งถูกทำเครื่องหมายว่าไม่มีสิทธิ์อ่านหรือเขียน - น่าจะเป็นบล็อกป้องกัน บนสแต็กล้นพื้นที่นี้ถูกเข้าถึงซึ่งก่อให้เกิดข้อผิดพลาดซึ่ง JVM สามารถจัดการได้โดยการสร้าง Java StackOverflowException นี่เป็นวิธีที่ถูกกว่าการตรวจสอบตัวชี้สแต็กในการเรียกใช้แต่ละวิธี พื้นที่คุ้มครองที่ไม่มีการตั้งค่าการอนุญาตสามารถเห็นได้ในบริบทอื่น ๆ
— eriksoe

38

มีปัญหาที่ทราบเกี่ยวกับ Java และ glibc> = 2.10 (รวมถึง Ubuntu> = 10.04, RHEL> = 6)

การรักษาคือการตั้งค่า env นี้ ตัวแปร:

export MALLOC_ARENA_MAX=4

หากคุณใช้ Tomcat คุณสามารถเพิ่มTOMCAT_HOME/bin/setenv.shไฟล์นี้ได้

สำหรับ Docker ให้เพิ่มส่วนนี้ใน Dockerfile

ENV MALLOC_ARENA_MAX=4

มีบทความ IBM เกี่ยวกับการตั้งค่า MALLOC_ARENA_MAX https://www.ibm.com/developerworks/community/blogs/kevgrig/entry/linux_glibc_2_10_rhel_6_malloc_may_show_excessive_virtual_memory_usage?lang=th

โพสต์บล็อกนี้พูดว่า

หน่วยความจำถิ่นที่อยู่ได้รู้จักกันในลักษณะที่คล้ายกับการรั่วไหลของหน่วยความจำหรือการกระจายตัวของหน่วยความจำ

นอกจากนี้ยังมีข้อผิดพลาด JDK ที่เปิดอยู่ JDK -8193521 "glibc เปลืองหน่วยความจำด้วยการกำหนดค่าเริ่มต้น"

ค้นหา MALLOC_ARENA_MAX ใน Google หรือ SO เพื่อการอ้างอิงเพิ่มเติม

คุณอาจต้องการปรับแต่งตัวเลือก malloc อื่น ๆ เพื่อปรับให้เหมาะสมสำหรับหน่วยความจำที่จัดสรรน้อย:

# tune glibc memory allocation, optimize for low fragmentation
# limit the number of arenas
export MALLOC_ARENA_MAX=2
# disable dynamic mmap threshold, see M_MMAP_THRESHOLD in "man mallopt"
export MALLOC_MMAP_THRESHOLD_=131072
export MALLOC_TRIM_THRESHOLD_=131072
export MALLOC_TOP_PAD_=131072
export MALLOC_MMAP_MAX_=65536

คำตอบนี้ช่วยฉันจริงๆในเซิร์ฟเวอร์ Ubuntu 64 บิตที่มีเซิร์ฟเวอร์ TomEE ที่ได้รับการ "ใช้งานได้นาน" ลิงก์ไปยังบทความ IBM เป็นคำอธิบายที่ลึกซึ้ง ขอบคุณอีกครั้งสำหรับคำใบ้ที่ดีนี้!
— MWiesner

1
JVM อาจรั่วไหลหน่วยความจำดั้งเดิมซึ่งนำไปสู่อาการที่คล้ายกัน ดูstackoverflow.com/a/35610063/166062 อินสแตนซ์ GZIPInputStream และ GZIPOutputStream ที่ไม่เปิดเผยอาจเป็นแหล่งของการรั่วไหลเช่นกัน
— Lari Hotari

3
มีข้อบกพร่องของ JVM ใน Java 8 ซึ่งส่งผลให้เกิดการเติบโตของหน่วยความจำดั้งเดิมที่ไม่ได้ จำกัด : bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8164293 - หากสิ่งนี้ส่งผลกระทบต่อคุณการใช้MALLOC_ARENA_MAXอาจทำให้การเติบโตของหน่วยความจำของคุณช้าลง แต่ไม่ใช่ แก้ปัญหาทั้งหมด
— outofcoffee เมื่อ

@LariHotari ชื่นชมความพยายามของคุณอย่างมากในการชี้ glibc และเวอร์ชัน redhat
— Sam

2
Java 8u131 มี bugfix backported สำหรับข้อผิดพลาด JVM ที่เกี่ยวข้อง JDK-8164293 bugs.openjdk.java.net/browse/JDK-8178124
— Lari Hotari

9

จำนวนหน่วยความจำที่จัดสรรสำหรับกระบวนการ Java ค่อนข้างตรงกับสิ่งที่ฉันคาดหวัง ฉันมีปัญหาคล้าย ๆ กันในการใช้งานจาวาในระบบที่ จำกัด การรันแอปพลิเคชันใด ๆ ที่มีขีด จำกัด VM ตามอำเภอใจหรือในระบบที่ไม่มีการแลกเปลี่ยนในปริมาณที่เพียงพอ ดูเหมือนจะเป็นธรรมชาติของแอพที่ทันสมัยหลายแห่งที่ไม่ได้ออกแบบมาเพื่อใช้กับระบบที่ จำกัด ทรัพยากร

คุณมีตัวเลือกเพิ่มเติมอีกสองสามข้อที่คุณสามารถลองและ จำกัด ขอบเขตหน่วยความจำของ JVM ของคุณ นี่อาจลดขนาดหน่วยความจำเสมือน:

-XX: ReservedCodeCacheSize = 32m ขนาดแคชโค้ดสำรอง (เป็นไบต์) - ขนาดแคชโค้ดสูงสุด [Solaris 64- บิต, amd64 และ - เซิร์ฟเวอร์ x86: 48m; ใน 1.5.0_06 และก่อนหน้านี้ Solaris 64 บิตและ and64: 1024m]

-XX: MaxPermSize = ขนาด 64m ของรุ่นถาวร [5.0 และใหม่กว่า: 64 บิต VM จะถูกปรับสัดส่วนให้ใหญ่ขึ้น 30%; 1.4 amd64: 96m 1.3.1 - ลูกค้า: 32m.]

นอกจากนี้คุณควรตั้งค่า -Xmx (ขนาดฮีพสูงสุด) เป็นค่าใกล้เคียงกับการใช้หน่วยความจำสูงสุดจริงของแอปพลิเคชันของคุณ ฉันเชื่อว่าพฤติกรรมเริ่มต้นของ JVM ยังคงเพิ่มขนาดฮีปเป็นสองเท่าในแต่ละครั้งที่ขยายไปจนถึงสูงสุด หากคุณเริ่มต้นด้วยฮีป 32M และแอปของคุณสูงถึง 65M ฮีปก็จะเติบโต 32M -> 64M -> 128M

คุณอาจลองทำเช่นนี้เพื่อให้ VM มีความก้าวร้าวน้อยลงเกี่ยวกับการเพิ่มจำนวนฮีป:

-XX: MinHeapFreeRatio = 40 เปอร์เซ็นต์ขั้นต่ำของฮีปฟรีหลังจาก GC เพื่อหลีกเลี่ยงการขยายตัว

นอกจากนี้จากสิ่งที่ฉันจำได้จากการทดลองเมื่อไม่กี่ปีที่ผ่านมาจำนวนห้องสมุดท้องถิ่นที่โหลดมีผลกระทบอย่างมากต่อรอยเท้าขั้นต่ำ กำลังโหลด java.net.Socket เพิ่มมากกว่า 15M ถ้าฉันจำได้ถูกต้อง (และฉันอาจจะไม่)


7

Sun JVM ต้องการหน่วยความจำจำนวนมากสำหรับ HotSpot และแมปในไลบรารีรันไทม์ในหน่วยความจำที่ใช้ร่วมกัน

หากหน่วยความจำเป็นปัญหาให้พิจารณาใช้ JVM อื่นที่เหมาะสมสำหรับการฝัง IBM มี j9 และมี Open Source "jamvm" ซึ่งใช้ไลบรารี class class ของ GNU นอกจากนี้ Sun ยังมี Squeak JVM ที่ทำงานบน SunSPOTS ดังนั้นจึงมีทางเลือกอื่น


เป็นตัวเลือกในการปิดใช้งานฮอตสปอตหรือไม่?
— Mario Ortegón

บางที ตรวจสอบตัวเลือกบรรทัดคำสั่งสำหรับ JVM ที่คุณใช้
— Thorbjørn Ravn Andersen

3

เพียงแค่คิดว่า แต่คุณอาจจะตรวจสอบอิทธิพลของตัวเลือกulimit -v

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


นั่นคือปัญหาของฉัน My Heap ตั้งไว้ที่ 64M แต่ linux สงวน 204MB หากฉันตั้งค่า ulimit ต่ำกว่า 204 แสดงว่าแอปพลิเคชันไม่ทำงานเลย
— Mario Ortegón

สิ่งที่น่าสนใจ: การตั้งค่า ulimit อาจมีผลข้างเคียงที่ไม่ได้ตั้งใจสำหรับกระบวนการอื่น ๆ โดยอธิบายว่าเหตุใดแอปพลิเคชันจึงไม่สามารถรันได้
— VonC

ปัญหาดูเหมือนว่า Java ต้องการสำรองหน่วยความจำเสมือนจำนวนที่มากกว่านี้แม้ว่ามันจะไม่ใช้ก็ตาม ใน windows หน่วยความจำเสมือนที่ใช้และการตั้งค่า Xmx นั้นค่อนข้างใกล้กว่า
— Mario Ortegón

คุณลองด้วย JRockit JVM หรือไม่?
— VonC

เนื่องจากการจัดสรรหน่วยความจำของ JVM คือผลรวมของ Heap Allocation และขนาด Perm (ครั้งแรกสามารถแก้ไขได้โดยใช้ตัวเลือก -Xms และ -Xmx) คุณลองตั้งค่าบางอย่างด้วย -XX: PermSize และ -XX: MaxPermSize (ค่าเริ่มต้นจาก 32MB เป็น 64MB ขึ้นอยู่กับรุ่น JVM)?
— VonC

3

วิธีหนึ่งในการลดจำนวนฮีปของระบบที่มีทรัพยากร จำกัด อาจเป็นการเล่นด้วยตัวแปร -XX: MaxHeapFreeRatio โดยปกติจะตั้งค่าเป็น 70 และเป็นเปอร์เซ็นต์สูงสุดของกองที่ว่างก่อนที่ GC จะย่อขนาด การตั้งค่าให้ต่ำลงและคุณจะเห็นเช่น jvisualvm profiler ที่มักใช้ heice sice ขนาดเล็กสำหรับโปรแกรมของคุณ

แก้ไข: ในการตั้งค่าขนาดเล็กสำหรับ -XX: MaxHeapFreeRatio คุณต้องตั้งค่า -XX: MinHeapFreeRatio เช่น

java -XX:MinHeapFreeRatio=10 -XX:MaxHeapFreeRatio=25 HelloWorld

EDIT2: เพิ่มตัวอย่างสำหรับแอปพลิเคชันจริงที่เริ่มต้นและทำงานเดียวกันโดยมีพารามิเตอร์เริ่มต้นและอีกหนึ่งเป็น 10 และ 25 เป็นพารามิเตอร์ ฉันไม่ได้สังเกตเห็นความแตกต่างของความเร็วจริงใด ๆ แม้ว่า java ในทางทฤษฎีควรใช้เวลามากขึ้นเพื่อเพิ่มฮีปในตัวอย่างหลัง

พารามิเตอร์เริ่มต้น

ในตอนท้ายฮีปสูงสุดคือ 905 ฮีปที่ใช้คือ 378

ขั้นต่ำ 10, สูงสุด 25

ในตอนท้ายฮีปสูงสุดคือ 722 ฮีปที่ใช้คือ 378

นี่เป็นข้อผิดพลาดบางอย่างเนื่องจากแอปพลิเคชันของเราทำงานบนเดสก์ท็อปเซิร์ฟเวอร์ระยะไกลและผู้ใช้จำนวนมากอาจเรียกใช้งานได้ในครั้งเดียว


1

Java 1.4 ของ Sun มีอาร์กิวเมนต์ดังนี้เพื่อควบคุมขนาดหน่วยความจำ:

-Xmsn ระบุขนาดเริ่มต้นเป็นไบต์ของพูลการจัดสรรหน่วยความจำ ค่านี้ต้องเป็นทวีคูณของ 1024 มากกว่า 1MB ผนวกตัวอักษร k หรือ K เพื่อระบุกิโลไบต์หรือ m หรือ M เพื่อระบุเมกะไบต์ ค่าเริ่มต้นคือ 2MB ตัวอย่าง:

           -Xms6291456
           -Xms6144k
           -Xms6m

-Xmxn ระบุขนาดสูงสุดเป็นไบต์ของพูลการจัดสรรหน่วยความจำ ค่านี้ต้องมีหลาย 1024 มากกว่า 2MB ผนวกตัวอักษร k หรือ K เพื่อระบุกิโลไบต์หรือ m หรือ M เพื่อระบุเมกะไบต์ ค่าเริ่มต้นคือ 64MB ตัวอย่าง:

           -Xmx83886080
           -Xmx81920k
           -Xmx80m

http://java.sun.com/j2se/1.4.2/docs/tooldocs/windows/java.html

Java 5 และ 6 มีมากกว่านี้ ดูhttp://java.sun.com/javase/technologies/hotspot/vmoptions.jsp


1
ผมมีปัญหาไม่ได้อยู่กับขนาดของกอง แต่มีปริมาณของหน่วยความจำเสมือนที่ได้รับมอบหมายจากลินุกซ์
— มาริโอOrtegón

อ่านคำอธิบายของ kdgregory การลดขนาดฮีป "ขนาดใหม่" และพารามิเตอร์ที่กำหนดค่าได้อื่น ๆ จะลดจำนวนหน่วยความจำ REAL ที่ jvm ใช้
— พอล Tomblin

เขาอาจมีปัญหาทางกฎหมาย แอปพลิเคชั่นบางตัว (เช่นเดียวกับที่ฉันเขียน) ไฟล์ mmap ขนาด 1 GB และบางระบบมีหน่วยความจำเสมือนเพียง 2 GB บางตัวเต็มไปด้วยไลบรารีที่แชร์ และหากเป็นปัญหาเขาควรปิดการใช้งานการสุ่มตัวอย่าง DSO มีตัวเลือกใน / proc
— Zan Lynx

0

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

ถ้าอย่างนั้นคุณสามารถลอง JVM อื่น ๆ จากนั้น Sun หนึ่งด้วยหน่วยความจำขนาดเล็ก แต่ฉันไม่สามารถแนะนำที่นี่

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.