การใช้งานหน่วยความจำเสมือนจาก 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.