Java Virtual Machine - คู่มือฉบับย่อ

JVM เป็นข้อกำหนดและสามารถมีการใช้งานที่แตกต่างกันได้ตราบเท่าที่เป็นไปตามข้อกำหนด รายละเอียดสามารถพบได้ในลิงค์ด้านล่าง -https://docs.oracle.com

Oracle มีการใช้งาน JVM ของตัวเอง (เรียกว่า HotSpot JVM) IBM มีของตัวเอง (เช่น J9 JVM เป็นต้น)

การดำเนินการที่กำหนดไว้ในข้อมูลจำเพาะจะได้รับด้านล่าง (ที่มา - ข้อกำหนดของ Oracle JVM ดูลิงค์ด้านบน) -

  • รูปแบบไฟล์ 'class'
  • ประเภทข้อมูล
  • ประเภทและค่าดั้งเดิม
  • ประเภทและค่าอ้างอิง
  • พื้นที่ข้อมูลรันไทม์
  • Frames
  • การเป็นตัวแทนของวัตถุ
  • เลขคณิตลอยตัว
  • วิธีพิเศษ
  • Exceptions
  • สรุปชุดคำสั่ง
  • ไลบรารีคลาส
  • การออกแบบสาธารณะการใช้งานส่วนตัว

JVM เป็นเครื่องเสมือนคอมพิวเตอร์นามธรรมที่มี ISA ของตัวเองหน่วยความจำสแต็กฮีป ฯลฯ ทำงานบนโฮสต์ OS และกำหนดความต้องการทรัพยากร

สถาปัตยกรรมของ HotSpot JVM 3 แสดงไว้ด้านล่าง -

เอ็นจิ้นการดำเนินการประกอบด้วยตัวรวบรวมขยะและคอมไพเลอร์ JIT JVM มีสองรสชาติ -client and server. ทั้งสองใช้รหัสรันไทม์เดียวกัน แต่แตกต่างกันที่สิ่งที่ใช้ JIT เราจะเรียนรู้เพิ่มเติมในภายหลัง ผู้ใช้สามารถควบคุมสิ่งที่รสชาติจะใช้โดยการระบุธง JVM -clientหรือ-Server JVM ของเซิร์ฟเวอร์ได้รับการออกแบบมาสำหรับแอ็พพลิเคชัน Java ที่ใช้งานได้ยาวนานบนเซิร์ฟเวอร์

JVM มาในเวอร์ชัน 32b และ 64b ผู้ใช้สามารถระบุเวอร์ชันที่จะใช้โดยใช้ -d32 หรือ -d64 ในอาร์กิวเมนต์ VM รุ่น 32b สามารถรองรับหน่วยความจำได้สูงสุด 4G ด้วยแอปพลิเคชั่นที่สำคัญที่ต้องดูแลชุดข้อมูลขนาดใหญ่ในหน่วยความจำเวอร์ชัน 64b จึงตอบสนองความต้องการ

JVM จัดการกระบวนการโหลดการเชื่อมโยงและการเตรียมใช้งานคลาสและอินเทอร์เฟซในลักษณะไดนามิก ในระหว่างกระบวนการโหลดไฟล์JVM finds the binary representation of a class and creates it.

ในระหว่างกระบวนการเชื่อมโยงไฟล์ loaded classes are combined into the run-time state of the JVM so that they can be executed during the initialization phase. JVM โดยทั่วไปใช้ตารางสัญลักษณ์ที่เก็บไว้ในพูลค่าคงที่รันไทม์สำหรับกระบวนการเชื่อมโยง การเริ่มต้นประกอบด้วยจริงexecuting the linked classes.

ประเภทของรถตัก

BootStrapclass loader อยู่ด้านบนสุดของลำดับชั้นตัวโหลดคลาส โหลดคลาส JDK มาตรฐานในไดเร็กทอรีlibของ JRE

Extension class loader อยู่ตรงกลางของลำดับชั้นของคลาสตัวโหลดและเป็นชายด์ในทันทีของตัวโหลดคลาส bootstrap และโหลดคลาสในไดเร็กทอรี lib \ ext ของ JRE

Applicationตัวโหลดคลาสอยู่ที่ด้านล่างของลำดับชั้นของคลาสตัวโหลดและเป็นลูกในทันทีของตัวโหลดคลาสแอ็พพลิเคชัน มันโหลดไหและคลาสที่ระบุโดยCLASSPATH ENV ตัวแปร.

การเชื่อมโยง

กระบวนการเชื่อมโยงประกอบด้วยสามขั้นตอนต่อไปนี้ -

Verification- ดำเนินการโดยโปรแกรมตรวจสอบ Bytecode เพื่อให้แน่ใจว่าไฟล์. class ที่สร้างขึ้น (Bytecode) นั้นถูกต้อง หากไม่เป็นเช่นนั้นข้อผิดพลาดจะเกิดขึ้นและกระบวนการเชื่อมโยงจะหยุดลง

Preparation - หน่วยความจำถูกจัดสรรให้กับตัวแปรคงที่ทั้งหมดของคลาสและจะเริ่มต้นด้วยค่าเริ่มต้น

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

การเริ่มต้น

นี่เป็นขั้นตอนสุดท้ายของกระบวนการโหลดคลาส ตัวแปรคงถูกกำหนดค่าดั้งเดิมและดำเนินการบล็อกแบบคงที่

ข้อมูลจำเพาะของ JVM กำหนดพื้นที่ข้อมูลรันไทม์ที่จำเป็นในระหว่างการทำงานของโปรแกรม บางส่วนถูกสร้างขึ้นในขณะที่ JVM เริ่มทำงาน อื่น ๆ เป็นแบบโลคัลสำหรับเธรดและถูกสร้างขึ้นเฉพาะเมื่อเธรดถูกสร้างขึ้น (และถูกทำลายเมื่อเธรดถูกทำลาย) เหล่านี้แสดงไว้ด้านล่าง -

ลงทะเบียน PC (Program Counter)

เป็นโลคัลสำหรับแต่ละเธรดและมีแอดเดรสของคำสั่ง JVM ที่เธรดกำลังดำเนินการอยู่

ซ้อนกัน

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

กอง

มีการใช้ร่วมกันระหว่างเธรดทั้งหมดและมีอ็อบเจ็กต์ข้อมูลเมตาของคลาสอาร์เรย์ ฯลฯ ที่สร้างขึ้นระหว่างรันไทม์ สร้างขึ้นเมื่อ JVM เริ่มทำงานและถูกทำลายเมื่อ JVM ปิดตัวลง คุณสามารถควบคุมจำนวนฮีปความต้องการ JVM ของคุณจากระบบปฏิบัติการโดยใช้แฟล็กบางอย่าง (เพิ่มเติมในภายหลัง) ต้องใช้ความระมัดระวังไม่ให้ใช้หน่วยความจำน้อยเกินไปหรือมากเกินไปเนื่องจากมีผลต่อประสิทธิภาพที่สำคัญ นอกจากนี้ GC ยังจัดการพื้นที่นี้และกำจัดสิ่งของที่ตายแล้วอย่างต่อเนื่องเพื่อเพิ่มพื้นที่ว่าง

พื้นที่วิธีการ

พื้นที่รันไทม์นี้เป็นเรื่องปกติสำหรับเธรดทั้งหมดและถูกสร้างขึ้นเมื่อ JVM เริ่มทำงาน มันเก็บโครงสร้างต่อคลาสเช่นพูลคงที่ (เพิ่มเติมในภายหลัง) รหัสสำหรับตัวสร้างและวิธีการข้อมูลวิธีการ ฯลฯ JLS ไม่ได้ระบุว่าพื้นที่นี้จำเป็นต้องเก็บขยะหรือไม่ดังนั้นการใช้งาน JVM อาจเลือกที่จะเพิกเฉยต่อ GC นอกจากนี้อาจขยายหรือไม่ก็ได้ตามความต้องการของแอปพลิเคชัน JLS ไม่ได้มอบอำนาจใด ๆ เกี่ยวกับเรื่องนี้

Run-Time Constant Pool

JVM รักษาโครงสร้างข้อมูลต่อคลาส / ต่อชนิดที่ทำหน้าที่เป็นตารางสัญลักษณ์ (หนึ่งในหลายบทบาท) ในขณะที่เชื่อมโยงคลาสที่โหลด

สแต็ควิธีดั้งเดิม

เมื่อเธรดเรียกใช้เมธอดแบบเนทีฟเธรดจะเข้าสู่โลกใหม่ที่โครงสร้างและข้อ จำกัด ด้านความปลอดภัยของเครื่องเสมือน Java ไม่ขัดขวางเสรีภาพอีกต่อไป เมธอดดั้งเดิมสามารถเข้าถึงพื้นที่ข้อมูลรันไทม์ของเครื่องเสมือนได้ (ขึ้นอยู่กับอินเทอร์เฟซเมธอดดั้งเดิม) แต่ยังสามารถทำอย่างอื่นที่ต้องการได้อีกด้วย

การเก็บขยะ

JVM จัดการวงจรชีวิตทั้งหมดของอ็อบเจ็กต์ใน Java เมื่อสร้างออบเจ็กต์แล้วนักพัฒนาไม่จำเป็นต้องกังวลอีกต่อไป ในกรณีที่วัตถุตาย (นั่นคือไม่มีการอ้างอิงอีกต่อไป) วัตถุจะถูกขับออกจากฮีปโดย GC โดยใช้อัลกอริทึมแบบใดแบบหนึ่งเช่น Serial GC, CMS, G1 เป็นต้น

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

ต้องขอบคุณ GC การรั่วไหลของหน่วยความจำนั้นหายากมากใน Java แต่ก็สามารถเกิดขึ้นได้ เราจะเห็นในบทต่อ ๆ ไปว่าจะสร้างการรั่วไหลของหน่วยความจำใน Java

ในบทนี้เราจะเรียนรู้เกี่ยวกับคอมไพเลอร์ JIT และความแตกต่างระหว่างภาษาที่คอมไพล์และตีความ

เรียบเรียงเทียบกับภาษาที่ตีความ

ภาษาเช่น C, C ++ และ FORTRAN เป็นภาษาที่คอมไพล์ รหัสของพวกเขาจะถูกส่งเป็นรหัสไบนารีที่กำหนดเป้าหมายไปที่เครื่องต้นแบบ ซึ่งหมายความว่าโค้ดระดับสูงจะถูกคอมไพล์เป็นรหัสไบนารีพร้อมกันโดยคอมไพเลอร์แบบสแตติกที่เขียนขึ้นโดยเฉพาะสำหรับสถาปัตยกรรมพื้นฐาน ไบนารีที่สร้างขึ้นจะไม่ทำงานบนสถาปัตยกรรมอื่นใด

ในทางกลับกันภาษาที่ตีความเช่น Python และ Perl สามารถทำงานบนเครื่องใดก็ได้ตราบเท่าที่มีล่ามที่ถูกต้อง มันไปทีละบรรทัดเหนือโค้ดระดับสูงโดยแปลงเป็นรหัสไบนารี

โดยทั่วไปโค้ดที่ตีความจะช้ากว่าโค้ดที่คอมไพล์ ตัวอย่างเช่นพิจารณาการวนซ้ำ การตีความจะแปลงรหัสที่สอดคล้องกันสำหรับการวนซ้ำแต่ละครั้ง ในทางกลับกันโค้ดที่คอมไพล์แล้วจะทำให้การแปลเป็นเพียงแบบเดียว นอกจากนี้เนื่องจากล่ามมองเห็นเพียงบรรทัดเดียวในแต่ละครั้งพวกเขาจึงไม่สามารถดำเนินการโค้ดที่สำคัญใด ๆ เช่นการเปลี่ยนลำดับการดำเนินการของคำสั่งเช่นคอมไพเลอร์

เราจะดูตัวอย่างของการเพิ่มประสิทธิภาพดังกล่าวด้านล่าง -

Adding two numbers stored in memory. เนื่องจากการเข้าถึงหน่วยความจำสามารถใช้ CPU ได้หลายรอบคอมไพเลอร์ที่ดีจะออกคำแนะนำในการดึงข้อมูลจากหน่วยความจำและดำเนินการเพิ่มเติมเมื่อมีข้อมูลเท่านั้น จะไม่รอและในระหว่างนี้ให้ดำเนินการตามคำแนะนำอื่น ๆ ในทางกลับกันจะไม่มีการเพิ่มประสิทธิภาพดังกล่าวในระหว่างการตีความเนื่องจากล่ามไม่ทราบรหัสทั้งหมดในช่วงเวลาใดเวลาหนึ่ง

แต่ภาษาที่ตีความสามารถทำงานบนเครื่องใดก็ได้ที่มีล่ามที่ถูกต้องของภาษานั้น

Java คอมไพล์หรือตีความ?

ชวาพยายามหาพื้นกลาง เนื่องจาก JVM อยู่ระหว่างคอมไพเลอร์ javac และฮาร์ดแวร์พื้นฐานคอมไพเลอร์ javac (หรือคอมไพเลอร์อื่น ๆ ) จึงรวบรวมโค้ด Java ใน Bytecode ซึ่งเข้าใจโดย JVM เฉพาะแพลตฟอร์ม จากนั้น JVM จะรวบรวม Bytecode ในรูปแบบไบนารีโดยใช้การคอมไพล์ JIT (Just-in-time) ตามที่โค้ดดำเนินการ

ฮอตสปอต

ในโปรแกรมทั่วไปมีโค้ดเพียงส่วนเล็ก ๆ ที่ถูกเรียกใช้งานบ่อยครั้งและบ่อยครั้งที่โค้ดนี้มีผลต่อประสิทธิภาพของแอปพลิเคชันทั้งหมดอย่างมาก ส่วนของโค้ดดังกล่าวเรียกว่าHotSpots.

หากบางส่วนของโค้ดถูกเรียกใช้งานเพียงครั้งเดียวการคอมไพล์จะเป็นการเสียความพยายามและการตีความ Bytecode จะเร็วกว่า แต่ถ้าส่วนนั้นเป็นส่วนที่ร้อนและมีการดำเนินการหลายครั้ง JVM จะคอมไพล์แทน ตัวอย่างเช่นหากมีการเรียกเมธอดหลายครั้งรอบพิเศษที่จะต้องใช้ในการคอมไพล์โค้ดจะถูกหักล้างด้วยไบนารีที่เร็วขึ้นที่สร้างขึ้น

ยิ่งไปกว่านั้นยิ่ง JVM รันเมธอดเฉพาะหรือลูปมากเท่าไหร่ข้อมูลก็จะรวบรวมเพื่อทำการปรับให้เหมาะสมมากขึ้นเพื่อให้สร้างไบนารีได้เร็วขึ้น

ให้เราพิจารณารหัสต่อไปนี้ -

for(int i = 0 ; I <= 100; i++) {
   System.out.println(obj1.equals(obj2)); //two objects
}

หากตีความรหัสนี้ล่ามจะอนุมานสำหรับการวนซ้ำแต่ละครั้งที่คลาสของ obj1 เนื่องจากแต่ละคลาสใน Java มีเมธอด .equals () ซึ่งขยายจากคลาส Object และสามารถแทนที่ได้ ดังนั้นแม้ว่า obj1 จะเป็นสตริงสำหรับการวนซ้ำแต่ละครั้งการหักจะยังคงทำได้

ในทางกลับกันสิ่งที่จะเกิดขึ้นจริงคือ JVM จะสังเกตเห็นว่าสำหรับการวนซ้ำแต่ละครั้ง obj1 เป็นของคลาส String ดังนั้นจึงสร้างโค้ดที่สอดคล้องกับเมธอด .equals () ของคลาส String โดยตรง ดังนั้นจึงไม่จำเป็นต้องมีการค้นหาและโค้ดที่คอมไพล์แล้วจะทำงานได้เร็วขึ้น

พฤติกรรมแบบนี้จะเกิดขึ้นได้ก็ต่อเมื่อ JVM รู้ว่าโค้ดทำงานอย่างไร ดังนั้นจึงต้องรอก่อนที่จะรวบรวมบางส่วนของโค้ด

ด้านล่างนี้เป็นอีกตัวอย่างหนึ่ง -

int sum = 7;
for(int i = 0 ; i <= 100; i++) {
   sum += i;
}

ล่ามสำหรับแต่ละลูปจะดึงค่าของ 'sum' จากหน่วยความจำเพิ่ม 'I' เข้าไปและเก็บกลับเข้าไปในหน่วยความจำ การเข้าถึงหน่วยความจำเป็นการดำเนินการที่มีราคาแพงและโดยปกติจะใช้ CPU หลายรอบ เนื่องจากโค้ดนี้ทำงานหลายครั้งจึงเป็น HotSpot JIT จะรวบรวมโค้ดนี้และทำการเพิ่มประสิทธิภาพดังต่อไปนี้

สำเนา 'ผลรวม' ในเครื่องจะถูกเก็บไว้ในรีจิสเตอร์เฉพาะสำหรับเธรดเฉพาะ การดำเนินการทั้งหมดจะทำกับค่าในรีจิสเตอร์และเมื่อลูปเสร็จสิ้นค่าจะถูกเขียนกลับไปยังหน่วยความจำ

จะเกิดอะไรขึ้นถ้าเธรดอื่นกำลังเข้าถึงตัวแปรด้วย เนื่องจากการอัปเดตกำลังดำเนินการกับสำเนาภายในของตัวแปรโดยเธรดอื่นพวกเขาจะเห็นค่าเก่า จำเป็นต้องมีการซิงโครไนซ์เธรดในกรณีเช่นนี้ พื้นฐานของการซิงค์เบื้องต้นคือการประกาศ 'ผลรวม' เป็นค่าความผันผวน ตอนนี้ก่อนที่จะเข้าถึงตัวแปรเธรดจะล้างรีจิสเตอร์ในเครื่องและดึงค่าจากหน่วยความจำ หลังจากเข้าถึงแล้วค่าจะถูกเขียนลงในหน่วยความจำทันที

ด้านล่างนี้คือการเพิ่มประสิทธิภาพทั่วไปบางส่วนที่ทำโดยคอมไพเลอร์ JIT -

  • วิธีการ inlining
  • การกำจัดรหัสตาย
  • Heuristics สำหรับการเพิ่มประสิทธิภาพไซต์การโทร
  • พับคงที่

JVM รองรับการรวบรวมห้าระดับ -

  • Interpreter
  • C1 พร้อมการเพิ่มประสิทธิภาพเต็มรูปแบบ (ไม่มีการทำโปรไฟล์)
  • C1 พร้อมการเรียกและตัวนับขอบหลัง (การทำโปรไฟล์แบบเบา)
  • C1 พร้อมโปรไฟล์แบบเต็ม
  • C2 (ใช้ข้อมูลการทำโปรไฟล์จากขั้นตอนก่อนหน้า)

ใช้ -Xint หากคุณต้องการปิดใช้งานคอมไพเลอร์ JIT ทั้งหมดและใช้เฉพาะตัวแปล

ไคลเอนต์เทียบกับเซิร์ฟเวอร์ JIT

ใช้ -client และ -server เพื่อเปิดใช้งานโหมดต่างๆ

ไคลเอนต์คอมไพเลอร์ (C1) เริ่มคอมไพล์โค้ดเร็วกว่าคอมไพเลอร์เซิร์ฟเวอร์ (C2) ดังนั้นเมื่อถึงเวลาที่ C2 เริ่มการรวบรวม C1 จะรวบรวมส่วนของโค้ดแล้ว

แต่ในขณะที่รอ C2 จะสร้างโปรไฟล์ให้รหัสรู้มากกว่าที่ C1 ทำ ดังนั้นเวลาที่รอหากสามารถใช้การชดเชยด้วยการปรับให้เหมาะสมเพื่อสร้างไบนารีที่เร็วกว่ามาก จากมุมมองของผู้ใช้การแลกเปลี่ยนจะอยู่ระหว่างเวลาเริ่มต้นของโปรแกรมและเวลาที่โปรแกรมทำงาน หากเวลาเริ่มต้นเป็นเบี้ยประกันภัยควรใช้ C1 หากคาดว่าแอปพลิเคชันจะทำงานเป็นเวลานาน (โดยทั่วไปของแอปพลิเคชันที่ติดตั้งบนเซิร์ฟเวอร์) ควรใช้ C2 เนื่องจากสร้างโค้ดที่เร็วกว่ามากซึ่งจะชดเชยเวลาเริ่มต้นที่เพิ่มขึ้นอย่างมาก

สำหรับโปรแกรมเช่น IDEs (NetBeans, Eclipse) และโปรแกรม GUI อื่น ๆ เวลาเริ่มต้นเป็นสิ่งสำคัญ NetBeans อาจใช้เวลาหนึ่งนาทีหรือนานกว่านั้นในการเริ่มต้น มีการรวบรวมคลาสหลายร้อยคลาสเมื่อเริ่มโปรแกรมเช่น NetBeans ในกรณีเช่นนี้คอมไพเลอร์ C1 เป็นตัวเลือกที่ดีที่สุด

โปรดทราบว่า C1 มีสองเวอร์ชัน - 32b and 64b. C2 เข้ามาเท่านั้น64b.

การรวบรวมฉัตร

ในเวอร์ชันเก่าบน Java ผู้ใช้สามารถเลือกหนึ่งในตัวเลือกต่อไปนี้ -

  • ล่าม (-Xint)
  • C1 (- ลูกค้า)
  • C2 (- เซิร์ฟเวอร์)

มาใน Java 7 โดยใช้คอมไพเลอร์ C1 ในการเริ่มต้นและเมื่อโค้ดร้อนขึ้นให้เปลี่ยนไปใช้ C2 สามารถเปิดใช้งานด้วยตัวเลือก JVM ต่อไปนี้: -XX: + TieredCompilation ค่าเริ่มต้นคือset to false in Java 7, and to true in Java 8.

จากห้าชั้นของการรวบรวมการรวบรวมแบบฉัตรใช้ 1 -> 4 -> 5.

บนเครื่อง 32b สามารถติดตั้ง JVM เวอร์ชัน 32b เท่านั้น บนเครื่อง 64b ผู้ใช้สามารถเลือกได้ระหว่างรุ่น 32b และ 64b แต่มีความแตกต่างบางประการที่อาจส่งผลต่อการทำงานของแอปพลิเคชัน Java ของเรา

หากแอปพลิเคชัน Java ใช้หน่วยความจำน้อยกว่า 4G เราควรใช้ 32b JVM แม้ในเครื่อง 64b เนื่องจากการอ้างอิงหน่วยความจำในกรณีนี้จะเป็นเพียง 32b และการจัดการจะมีราคาถูกกว่าการจัดการที่อยู่ 64b ในกรณีนี้ JVM 64b จะทำงานได้แย่ลงแม้ว่าเราจะใช้ OOPS (ตัวชี้วัตถุธรรมดา) ก็ตาม การใช้ OOPS JVM สามารถใช้ที่อยู่ 32b ใน 64b JVM อย่างไรก็ตามการจัดการข้อมูลเหล่านี้จะช้ากว่าการอ้างอิง 32b จริงเนื่องจากการอ้างอิงเนทีฟพื้นฐานจะยังคงเป็น 64b

หากแอปพลิเคชันของเราใช้หน่วยความจำมากกว่า 4G เราจะต้องใช้เวอร์ชัน 64b เนื่องจากการอ้างอิง 32b สามารถระบุหน่วยความจำได้ไม่เกิน 4G เราสามารถติดตั้งทั้งสองเวอร์ชันในเครื่องเดียวกันและสามารถสลับระหว่างเวอร์ชันได้โดยใช้ตัวแปร PATH

ในบทนี้เราจะเรียนรู้เกี่ยวกับ JIT Optimisations

วิธีการ Inlining

ในเทคนิคการเพิ่มประสิทธิภาพนี้คอมไพลเลอร์จะตัดสินใจแทนที่การเรียกฟังก์ชันของคุณด้วยเนื้อความของฟังก์ชัน ด้านล่างนี้เป็นตัวอย่างเดียวกัน -

int sum3;

static int add(int a, int b) {
   return a + b;
}

public static void main(String…args) {
   sum3 = add(5,7) + add(4,2);
}

//after method inlining
public static void main(String…args) {
   sum3 = 5+ 7 + 4 + 2;
}

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

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

ไม่สามารถเข้าถึงได้และการกำจัดรหัสตาย

รหัสที่ไม่สามารถเข้าถึงได้คือรหัสที่ไม่สามารถเข้าถึงได้ด้วยขั้นตอนการดำเนินการที่เป็นไปได้ เราจะพิจารณาตัวอย่างต่อไปนี้ -

void foo() {
   if (a) return;
   else return;
   foobar(a,b); //unreachable code, compile time error
}

รหัสตายยังเป็นรหัสที่ไม่สามารถเข้าถึงได้ แต่คอมไพเลอร์จะคายข้อผิดพลาดออกมาในกรณีนี้ แต่เราได้รับคำเตือนแทน แต่ละบล็อกของโค้ดเช่นตัวสร้างฟังก์ชันลองจับถ้าในขณะที่ ฯลฯ มีกฎของตัวเองสำหรับโค้ดที่ไม่สามารถเข้าถึงได้ซึ่งกำหนดไว้ใน JLS (ข้อกำหนดภาษา Java)

พับคงที่

หากต้องการทำความเข้าใจแนวคิดการพับแบบคงที่โปรดดูตัวอย่างด้านล่าง

final int num = 5;
int b = num * 6; //compile-time constant, num never changes
//compiler would assign b a value of 30.

วงจรชีวิตของอ็อบเจ็กต์ Java ถูกจัดการโดย JVM เมื่อโปรแกรมสร้างอ็อบเจ็กต์ขึ้นมาแล้วเราไม่จำเป็นต้องกังวลเกี่ยวกับอายุการใช้งานที่เหลือ JVM จะค้นหาวัตถุที่ไม่ได้ใช้งานอีกต่อไปโดยอัตโนมัติและเรียกคืนหน่วยความจำจากฮีป

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

คุณไม่สามารถยกเลิกการจัดสรรอ็อบเจ็กต์โดยใช้โปรแกรมใน Java ได้เช่นเดียวกับที่ทำได้ในภาษาที่ไม่ใช่ GC เช่น C และ C ++ ดังนั้นคุณไม่สามารถมีการอ้างอิงห้อยใน Java อย่างไรก็ตามคุณอาจมีการอ้างอิงที่เป็นโมฆะ (การอ้างอิงที่อ้างถึงพื้นที่ของหน่วยความจำที่ JVM จะไม่เก็บวัตถุ) เมื่อใดก็ตามที่ใช้การอ้างอิง null JVM จะพ่น NullPointerException

โปรดทราบว่าแม้ว่าจะไม่ค่อยพบการรั่วไหลของหน่วยความจำในโปรแกรม Java เนื่องจาก GC แต่ก็เกิดขึ้นได้ เราจะสร้างการรั่วไหลของความทรงจำในตอนท้ายของบทนี้

GC ต่อไปนี้ใช้ใน JVM สมัยใหม่

  • ตัวเก็บอนุกรม
  • ตัวรวบรวมปริมาณงาน
  • ตัวรวบรวม CMS
  • นักสะสม G1

แต่ละอัลกอริทึมข้างต้นทำงานเหมือนกัน - ค้นหาวัตถุที่ไม่ได้ใช้งานอีกต่อไปและเรียกคืนหน่วยความจำที่ครอบครองในฮีป หนึ่งในวิธีการที่ไร้เดียงสาในการนี้คือการนับจำนวนการอ้างอิงที่แต่ละออบเจ็กต์มีและทำให้เป็นอิสระทันทีที่จำนวนการอ้างอิงเปลี่ยนเป็น 0 (เรียกอีกอย่างว่าการนับการอ้างอิง) ทำไมไร้เดียงสาขนาดนี้ พิจารณารายการที่เชื่อมโยงแบบวงกลม แต่ละโหนดจะมีการอ้างอิงถึง แต่อ็อบเจ็กต์ทั้งหมดไม่ได้รับการอ้างอิงจากที่ใดก็ตามและควรเป็นอิสระ

JVM ไม่เพียง แต่ปลดปล่อยหน่วยความจำเท่านั้น แต่ยังรวมหน่วยความจำขนาดเล็กเข้าด้วยกันเป็นหน่วยความจำที่ใหญ่ขึ้น สิ่งนี้ทำเพื่อป้องกันการกระจายตัวของหน่วยความจำ

ในข้อสังเกตง่ายๆอัลกอริทึม GC ทั่วไปทำกิจกรรมต่อไปนี้ -

  • ค้นหาวัตถุที่ไม่ได้ใช้
  • ปลดปล่อยความทรงจำที่พวกเขาครอบครองในกอง
  • การรวมชิ้นส่วน

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

การรวมหน่วยความจำ

การสาธิตการรวมหน่วยความจำอย่างง่ายแสดงไว้ด้านล่าง

ส่วนที่แรเงาเป็นวัตถุที่ต้องการปลดปล่อย แม้ว่าจะเรียกคืนพื้นที่ทั้งหมดแล้วเราสามารถจัดสรรออบเจ็กต์ที่มีขนาดสูงสุด = 75Kb เท่านั้น นี่คือแม้ว่าเราจะมีพื้นที่ว่าง 200Kb ตามที่แสดงด้านล่าง

JVM ส่วนใหญ่แบ่งกองออกเป็นสามรุ่น - the young generation (YG), the old generation (OG) and permanent generation (also called tenured generation). อะไรคือสาเหตุเบื้องหลังความคิดเช่นนั้น?

การศึกษาเชิงประจักษ์พบว่าวัตถุส่วนใหญ่ที่สร้างขึ้นมีอายุการใช้งานสั้นมาก -

ที่มา

https://www.oracle.com

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

เราจะดูตัวอย่างง่ายๆ คลาส String ใน Java ไม่เปลี่ยนรูป ซึ่งหมายความว่าทุกครั้งที่คุณต้องเปลี่ยนเนื้อหาของอ็อบเจกต์ String คุณต้องสร้างอ็อบเจกต์ใหม่ทั้งหมด ให้เราสมมติว่าคุณทำการเปลี่ยนแปลงสตริง 1,000 ครั้งในลูปดังที่แสดงในโค้ดด้านล่าง -

String str = “G11 GC”;

for(int i = 0 ; i < 1000; i++) {
   str = str + String.valueOf(i);
}

ในแต่ละลูปเราสร้างออบเจ็กต์สตริงใหม่และสตริงที่สร้างขึ้นระหว่างการทำซ้ำก่อนหน้านี้จะไร้ประโยชน์ (นั่นคือไม่ได้อ้างอิงโดยการอ้างอิงใด ๆ ) อายุการใช้งานของวัตถุนั้นเป็นเพียงการวนซ้ำเพียงครั้งเดียว - GC จะถูกรวบรวมในเวลาไม่นาน วัตถุอายุสั้นดังกล่าวจะถูกเก็บไว้ในพื้นที่ของคนรุ่นใหม่ของกอง กระบวนการเก็บรวบรวมสิ่งของจากคนรุ่นใหม่เรียกว่าการเก็บขยะเล็กน้อยและมักจะทำให้โลกหยุดชะงัก

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

ที่นี่เราจะเห็นข้อดีของการออกแบบรุ่นดังกล่าว คนรุ่นใหม่เป็นเพียงส่วนเล็ก ๆ ของกองและได้รับการเติมเต็มอย่างรวดเร็ว แต่การประมวลผลจะใช้เวลาน้อยกว่าเวลาที่ใช้ในการประมวลผลฮีปทั้งหมด ดังนั้นการหยุดชั่วคราว 'stop-theworld' ในกรณีนี้จึงสั้นกว่ามากแม้ว่าจะบ่อยกว่าก็ตาม เราควรตั้งเป้าหมายที่จะหยุดให้สั้นลงมากกว่าการหยุดที่นานกว่าเสมอแม้ว่าอาจจะบ่อยกว่าก็ตาม เราจะพูดถึงเรื่องนี้โดยละเอียดในส่วนต่อไปของบทช่วยสอนนี้

คนรุ่นใหม่แบ่งออกเป็นสองช่อง - eden and survivor space. วัตถุที่รอดชีวิตในระหว่างการสะสมของอีเดนจะถูกย้ายไปยังอวกาศผู้รอดชีวิตและผู้ที่รอดชีวิตจากอวกาศผู้รอดชีวิตจะถูกย้ายไปยังคนรุ่นเก่า คนรุ่นใหม่ถูกบดอัดในขณะที่รวบรวม

เมื่อวัตถุถูกเคลื่อนย้ายไปยังคนรุ่นเก่ามันก็จะเต็มไปด้วยในที่สุดและจะต้องถูกรวบรวมและบดอัด อัลกอริทึมที่แตกต่างกันใช้แนวทางที่แตกต่างกัน บางคนหยุดเธรดของแอปพลิเคชัน (ซึ่งนำไปสู่การหยุด 'หยุดโลก' ที่ยาวนานเนื่องจากคนรุ่นเก่ามีขนาดค่อนข้างใหญ่เมื่อเทียบกับคนรุ่นใหม่) ในขณะที่บางคนทำไปพร้อมกันในขณะที่เธรดแอปพลิเคชันทำงาน กระบวนการนี้เรียกว่า GC เต็มรูปแบบ นักสะสมสองคนดังกล่าวคือCMS and G1.

ตอนนี้ให้เราวิเคราะห์อัลกอริทึมเหล่านี้โดยละเอียด

GC แบบอนุกรม

เป็น GC เริ่มต้นบนเครื่องระดับไคลเอนต์ (เครื่องประมวลผลเดี่ยวหรือ 32b JVM, Windows) โดยทั่วไปแล้ว GC จะมีการทำงานแบบมัลติเธรดเป็นจำนวนมาก แต่ GC แบบอนุกรมไม่ใช่ มีเธรดเดียวในการประมวลผลฮีปและจะหยุดเธรดแอ็พพลิเคชันเมื่อใดก็ตามที่มีการทำ GC รองหรือ GC หลัก เราสามารถสั่งให้ JVM ใช้ GC นี้ได้โดยระบุแฟล็ก:-XX:+UseSerialGC. หากเราต้องการให้ใช้อัลกอริทึมที่แตกต่างกันให้ระบุชื่ออัลกอริทึม โปรดทราบว่าคนรุ่นเก่าถูกบีบอัดอย่างเต็มที่ในช่วง GC ที่สำคัญ

ปริมาณงาน GC

GC นี้เป็นค่าเริ่มต้นบน JVM 64b และเครื่องหลาย CPU ไม่เหมือนกับ GC แบบอนุกรมคือใช้เธรดหลายชุดในการประมวลผลคนรุ่นใหม่และรุ่นเก่า ด้วยเหตุนี้ GC จึงเรียกอีกอย่างว่าparallel collector. เราสามารถสั่งให้ JVM ของเราใช้ตัวรวบรวมนี้โดยใช้แฟล็ก:-XX:+UseParallelOldGC หรือ -XX:+UseParallelGC(สำหรับ JDK 8 เป็นต้นไป) เธรดของแอ็พพลิเคชันหยุดทำงานในขณะที่ทำการรวบรวมขยะหลักหรือรอง เช่นเดียวกับตัวสะสมอนุกรมมันกระชับคนรุ่นใหม่ในช่วง GC ที่สำคัญ

ทรูพุต GC รวบรวม YG และ OG เมื่อ eden เต็มแล้วตัวเก็บรวบรวมจะขับอ็อบเจ็กต์ที่มีชีวิตออกจากมันลงใน OG หรือช่องว่างผู้รอดชีวิต (SS0 และ SS1 ในแผนภาพด้านล่าง) วัตถุที่ตายแล้วจะถูกทิ้งเพื่อเพิ่มพื้นที่ว่างที่พวกเขาครอบครอง

ก่อน GC ของ YG

หลังจาก GC ของ YG

ในระหว่าง GC เต็มตัวรวบรวมทรูพุตจะล้าง YG, SS0 และ SS1 ทั้งหมด หลังจากการดำเนินการ OG จะมีเฉพาะวัตถุที่มีชีวิตเท่านั้น เราควรสังเกตว่าตัวรวบรวมทั้งสองตัวข้างต้นหยุดเธรดของแอปพลิเคชันในขณะที่ประมวลผลฮีป ซึ่งหมายความว่า 'โลกหยุดยาว' หยุดชั่วคราวในช่วง GC ที่สำคัญ สองอัลกอริทึมถัดไปมีเป้าหมายที่จะกำจัดพวกมันโดยต้องเสียทรัพยากรฮาร์ดแวร์มากขึ้น -

นักสะสม CMS

ย่อมาจาก 'มาร์คกวาดพร้อมกัน' หน้าที่ของมันคือใช้เธรดพื้นหลังเพื่อสแกนผ่านคนรุ่นเก่าเป็นระยะ ๆ และกำจัดวัตถุที่ตายแล้ว แต่ในช่วงที่มี GC เล็กน้อยเธรดของแอปพลิเคชันจะหยุดทำงาน อย่างไรก็ตามการหยุดชั่วคราวมีค่อนข้างน้อย สิ่งนี้ทำให้ CMS เป็นตัวรวบรวมการหยุดชั่วคราวต่ำ

ตัวรวบรวมนี้ต้องการเวลา CPU เพิ่มเติมเพื่อสแกนผ่านฮีปขณะรันเธรดแอ็พพลิเคชัน นอกจากนี้เธรดพื้นหลังจะรวบรวมฮีปและไม่ทำการบดอัดใด ๆ อาจทำให้กองกลายเป็นกระจัดกระจาย เมื่อผ่านไประยะหนึ่ง CMS จะหยุดเธรดแอปพลิเคชันทั้งหมดและกระชับฮีปโดยใช้เธรดเดียว ใช้อาร์กิวเมนต์ JVM ต่อไปนี้เพื่อบอกให้ JVM ใช้ตัวรวบรวม CMS -

“XX:+UseConcMarkSweepGC -XX:+UseParNewGC” เป็นอาร์กิวเมนต์ JVM เพื่อบอกให้ใช้ตัวรวบรวม CMS

ก่อน GC

หลังจาก GC

โปรดทราบว่ากำลังดำเนินการรวบรวมพร้อมกัน

G1 GC

อัลกอริทึมนี้ทำงานโดยการแบ่งฮีปออกเป็นหลายภูมิภาค เช่นเดียวกับตัวรวบรวม CMS จะหยุดเธรดของแอ็พพลิเคชันในขณะที่ทำ GC รองและใช้เธรดพื้นหลังเพื่อประมวลผลรุ่นเก่าในขณะที่ยังคงเธรดของแอ็พพลิเคชันไว้ เนื่องจากมีการแบ่งคนรุ่นเก่าออกเป็นภูมิภาคจึงทำให้กระชับขณะเคลื่อนย้ายสิ่งของจากภูมิภาคหนึ่งไปยังอีกภูมิภาคหนึ่ง ดังนั้นการแยกส่วนจึงต่ำที่สุด คุณสามารถใช้แฟล็ก:XX:+UseG1GCเพื่อบอกให้ JVM ของคุณใช้อัลกอริทึมนี้ เช่นเดียวกับ CMS ก็ต้องใช้เวลา CPU มากขึ้นในการประมวลผลฮีปและรันเธรดแอปพลิเคชันพร้อมกัน

อัลกอริทึมนี้ได้รับการออกแบบมาเพื่อประมวลผลฮีปที่ใหญ่ขึ้น (> 4G) ซึ่งแบ่งออกเป็นภูมิภาคต่างๆ ภูมิภาคเหล่านั้นบางส่วนประกอบด้วยคนรุ่นใหม่และส่วนที่เหลือประกอบด้วยคนรุ่นเก่า YG ถูกล้างโดยใช้แบบดั้งเดิม - เธรดแอปพลิเคชันทั้งหมดจะหยุดลงและวัตถุทั้งหมดที่ยังมีชีวิตอยู่สำหรับคนรุ่นเก่าหรือพื้นที่ผู้รอดชีวิต

โปรดทราบว่าอัลกอริทึม GC ทั้งหมดแบ่งฮีปออกเป็น YG และ OG และใช้ STWP เพื่อล้าง YG กระบวนการนี้มักจะเร็วมาก

ในบทที่แล้วเราได้เรียนรู้เกี่ยวกับ Generational Gcs ต่างๆ ในบทนี้เราจะพูดถึงวิธีการปรับ GC

ขนาดกอง

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

นอกจากนี้การเพิ่มขนาดฮีปยังมีโทษรุนแรงต่อระบบปฏิบัติการพื้นฐาน การใช้การเพจระบบปฏิบัติการทำให้โปรแกรมแอปพลิเคชันของเราเห็นหน่วยความจำมากกว่าที่มีอยู่จริง ระบบปฏิบัติการจัดการสิ่งนี้โดยใช้พื้นที่ swap บางส่วนบนดิสก์คัดลอกส่วนที่ไม่ใช้งานของโปรแกรมลงในนั้น เมื่อจำเป็นต้องใช้ระบบปฏิบัติการจะคัดลอกกลับจากดิสก์ไปยังหน่วยความจำ

สมมติว่าเครื่องมีหน่วยความจำ 8G และ JVM เห็นหน่วยความจำเสมือน 16G JVM จะไม่รู้ว่าในระบบมีเพียง 8G เท่านั้น มันจะขอ 16G จาก OS และเมื่อได้รับหน่วยความจำนั้นก็จะใช้งานต่อไป ระบบปฏิบัติการจะต้องสลับข้อมูลจำนวนมากเข้าและออกและนี่เป็นผลเสียอย่างมากต่อระบบ

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

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

คุณสามารถควบคุมขนาดของการร้องขอหน่วยความจำโดย JVM โดยใช้สองแฟล็ก -

  • -XmsN - ควบคุมหน่วยความจำเริ่มต้นที่ร้องขอ

  • -XmxN - ควบคุมหน่วยความจำสูงสุดที่สามารถร้องขอได้

ค่าดีฟอลต์ของแฟล็กทั้งสองนี้ขึ้นอยู่กับระบบปฏิบัติการพื้นฐาน ตัวอย่างเช่นสำหรับ JVM 64b ที่ทำงานบน MacOS, -XmsN = 64M และ -XmxN = ขั้นต่ำ 1G หรือ 1 ใน 4 ของหน่วยความจำฟิสิคัลทั้งหมด

โปรดทราบว่า JVM สามารถปรับระหว่างสองค่าโดยอัตโนมัติ ตัวอย่างเช่นหากสังเกตเห็นว่ามี GC เกิดขึ้นมากเกินไปก็จะเพิ่มขนาดหน่วยความจำต่อไปตราบเท่าที่มีขนาดต่ำกว่า -XmxN และบรรลุเป้าหมายประสิทธิภาพที่ต้องการ

หากคุณทราบว่าแอปพลิเคชันของคุณต้องการหน่วยความจำเท่าใดคุณสามารถตั้งค่า -XmsN = -XmxN ในกรณีนี้ JVM ไม่จำเป็นต้องหาค่า "ที่เหมาะสม" ของฮีปดังนั้นกระบวนการ GC จึงมีประสิทธิภาพมากขึ้นเล็กน้อย

ขนาดรุ่น

คุณสามารถตัดสินใจได้ว่าคุณต้องการจัดสรรฮีปจำนวนเท่าใดให้กับ YG และจำนวนเท่าใดที่คุณต้องการจัดสรรให้กับ OG ค่าทั้งสองนี้มีผลต่อประสิทธิภาพของแอปพลิเคชันของเราในลักษณะต่อไปนี้

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

ด้านล่างนี้คือแฟล็กที่คุณสามารถใช้เพื่อตั้งค่าเหล่านี้ -

  • -XX:NewRatio=N: อัตราส่วนของ YG ต่อ OG (ค่าเริ่มต้น = 2)

  • -XX:NewSize=N: ขนาดเริ่มต้นของ YG

  • -XX:MaxNewSize=N: ขนาดสูงสุดของ YG

  • -XmnN: ตั้งค่า NewSize และ MaxNewSize เป็นค่าเดียวกันโดยใช้แฟล็กนี้

ขนาดเริ่มต้นของ YG ถูกกำหนดโดยค่าของ NewRatio ตามสูตรที่กำหนด -

(total heap size) / (newRatio + 1)

เนื่องจากค่าเริ่มต้นของ newRatio คือ 2 สูตรด้านบนจึงให้ค่าเริ่มต้นของ YG เท่ากับ 1/3 ของขนาดฮีปทั้งหมด คุณสามารถลบล้างค่านี้ได้ตลอดเวลาโดยระบุขนาดของ YG อย่างชัดเจนโดยใช้แฟล็ก NewSize แฟล็กนี้ไม่มีค่าเริ่มต้นใด ๆ และหากไม่ได้ตั้งค่าไว้อย่างชัดเจนขนาดของ YG จะคำนวณต่อไปโดยใช้สูตรด้านบน

Permagen และ Metaspace

Permagen และ metaspace เป็นพื้นที่ฮีปที่ JVM เก็บข้อมูลเมตาของคลาส ช่องว่างนี้เรียกว่า 'permagen' ใน Java 7 และใน Java 8 เรียกว่า 'metaspace' ข้อมูลนี้ถูกใช้โดยคอมไพลเลอร์และรันไทม์

คุณสามารถควบคุมขนาดของ Permagen โดยใช้แฟล็กต่อไปนี้: -XX: PermSize=N และ -XX:MaxPermSize=N. ขนาดของ Metaspace สามารถควบคุมได้โดยใช้:-XX:Metaspace- Size=N และ -XX:MaxMetaspaceSize=N.

มีความแตกต่างบางประการในการจัดการ permagen และ metaspace เมื่อไม่ได้ตั้งค่าแฟล็ก ตามค่าเริ่มต้นทั้งสองมีขนาดเริ่มต้นเริ่มต้น แต่ในขณะที่ metaspace สามารถครอบครองฮีปได้มากเท่าที่จำเป็น แต่ Permagen สามารถครอบครองได้ไม่เกินค่าเริ่มต้นเริ่มต้น ตัวอย่างเช่น 64b JVM มีพื้นที่ฮีพ 82M เป็นขนาดของเปอร์มาเจนสูงสุด

โปรดทราบว่าเนื่องจาก metaspace สามารถใช้หน่วยความจำได้ไม่ จำกัด จำนวนเว้นแต่จะระบุว่าไม่เป็นเช่นนั้นอาจมีข้อผิดพลาดหน่วยความจำไม่เพียงพอ GC เต็มจะเกิดขึ้นเมื่อใดก็ตามที่มีการปรับขนาดภูมิภาคเหล่านี้ ดังนั้นในระหว่างการเริ่มต้นหากมีการโหลดคลาสจำนวนมาก metaspace สามารถปรับขนาดได้เรื่อย ๆ ส่งผลให้ GC เต็มทุกครั้ง ดังนั้นจึงต้องใช้เวลานานสำหรับแอปพลิเคชันขนาดใหญ่ในการเริ่มต้นในกรณีที่ขนาด metaspace เริ่มต้นต่ำเกินไป เป็นความคิดที่ดีที่จะเพิ่มขนาดเริ่มต้นเนื่องจากจะช่วยลดเวลาในการเริ่มต้นระบบ

แม้ว่า Permagen และ metaspace จะเก็บข้อมูลเมตาของคลาสไว้ แต่ก็ไม่ถาวรและ GC จะเรียกคืนช่องว่างเช่นเดียวกับในกรณีของวัตถุ โดยทั่วไปจะเป็นกรณีของเซิร์ฟเวอร์แอปพลิเคชัน เมื่อใดก็ตามที่คุณทำการปรับใช้ใหม่กับเซิร์ฟเวอร์ข้อมูลเมตาเก่าจะต้องได้รับการล้างข้อมูลเนื่องจากตัวโหลดคลาสใหม่จะต้องใช้พื้นที่ พื้นที่นี้เป็นอิสระโดย GC

เราจะพูดคุยเกี่ยวกับแนวคิดการรั่วไหลของหน่วยความจำใน Java ในบทนี้

รหัสต่อไปนี้สร้างการรั่วไหลของหน่วยความจำใน Java -

void queryDB() {
   try{
      Connection conn = ConnectionFactory.getConnection();
      PreparedStatement ps = conn.preparedStatement("query"); // executes a
      SQL
      ResultSet rs = ps.executeQuery();
      while(rs.hasNext()) {
         //process the record
      }
   } catch(SQLException sqlEx) {
      //print stack trace
   }
}

ในโค้ดด้านบนเมื่อเมธอดออกไปเรายังไม่ได้ปิดอ็อบเจ็กต์การเชื่อมต่อ ดังนั้นการเชื่อมต่อทางกายภาพจะยังคงเปิดอยู่ก่อนที่ GC จะถูกทริกเกอร์และมองว่าออบเจ็กต์การเชื่อมต่อไม่สามารถเข้าถึงได้ ตอนนี้มันจะเรียกวิธีสุดท้ายบนวัตถุการเชื่อมต่ออย่างไรก็ตามอาจไม่สามารถใช้งานได้ ดังนั้นวัตถุจะไม่ถูกเก็บรวบรวมในรอบนี้

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