การเพิ่มประสิทธิภาพกองและหน่วยความจำ
วันนี้ฉันทดสอบฟังก์ชั่นการเรียงลำดับข้อมูลอัตโนมัติของโครงการเรียงลำดับบันทึกและรูปภาพหลายหมื่นรายการในฐานข้อมูล เมื่อการดำเนินการใกล้ถึงจุดสิ้นสุด Java.lang.outofMemoryError ข้อผิดพลาดในพื้นที่ Java Heap ถูกเปิดเผย ในอดีตฉันไม่ค่อยพบข้อผิดพลาดของหน่วยความจำในการเขียนโปรแกรมเพราะ Java มีกลไกนักสะสมขยะดังนั้นฉันจึงไม่ได้ให้ความสนใจมากนัก วันนี้ฉันค้นหาข้อมูลออนไลน์และจัดเรียงตามพื้นฐานนี้
1. สแต็คและสแต็ก
สร้างขึ้นด้วยกองขยะใหม่ Garbage Collector รับผิดชอบการรีไซเคิล
1. เมื่อโปรแกรมเริ่มทำงาน JVM จะได้รับหน่วยความจำบางส่วนจากระบบปฏิบัติการซึ่งเป็นส่วนหนึ่งของหน่วยความจำฮีป หน่วยความจำฮีปมักจะจัดเรียงขึ้นไปด้านล่างของที่อยู่จัดเก็บ
2. ฮีปเป็นพื้นที่ข้อมูล "รันไทม์" และวัตถุอินสแตนซ์ในชั้นเรียนจัดสรรพื้นที่จากกอง;
3. การจัดสรรพื้นที่บนกองถูกสร้างขึ้นผ่านคำแนะนำเช่น "ใหม่" กองเป็นขนาดหน่วยความจำที่จัดสรรแบบไดนามิกและอายุการใช้งานไม่จำเป็นต้องบอกกับคอมไพเลอร์ล่วงหน้า
4. ซึ่งแตกต่างจาก C ++, Java จัดการกองและสแต็กโดยอัตโนมัติและตัวเก็บขยะสามารถรีไซเคิลหน่วยความจำกองที่ไม่ได้ใช้งานโดยอัตโนมัติอีกต่อไป
5. ข้อเสียคือเนื่องจากหน่วยความจำได้รับการจัดสรรแบบไดนามิกที่รันไทม์ความเร็วในการเข้าถึงหน่วยความจำจะช้าลง
สแต็ค - จัดเก็บประเภทพื้นฐานและประเภทอ้างอิงเร็ว
1. โครงสร้างข้อมูลครั้งแรกและจากนั้นมักจะใช้เพื่อบันทึกพารามิเตอร์และตัวแปรท้องถิ่นในวิธีการ;
2. ใน java ตัวแปรทั้งหมดของประเภทพื้นฐาน (สั้น, int, ยาว, ไบต์, ลอย, สอง, บูลีน, ถ่าน) และประเภทอ้างอิงจะถูกเก็บไว้ในสแต็ก;
3. พื้นที่อยู่อาศัยของข้อมูลในสแต็กโดยทั่วไปอยู่ในขอบเขตปัจจุบัน (พื้นที่ล้อมรอบด้วย {... };
4. ความเร็วในการเข้าถึงของสแต็กนั้นเร็วกว่าฮีปรองจากการลงทะเบียนที่อยู่ในซีพียูโดยตรงเท่านั้น
5. ข้อมูลในสแต็กสามารถแชร์ได้และการอ้างอิงหลายครั้งสามารถชี้ไปที่ที่อยู่เดียวกันได้
6. ข้อเสียคือขนาดของข้อมูลและอายุการใช้งานของสแต็กจะต้องได้รับการพิจารณาและขาดความยืดหยุ่น
2. การตั้งค่าหน่วยความจำ
1. ตรวจสอบสถานะหน่วยความจำเสมือนจริง
long maxControl = runtime.getRuntime (). maxMemory (); // รับจำนวนหน่วยความจำสูงสุดที่เครื่องเสมือนสามารถควบคุมการใช้งาน Long CurrentUs
โดยค่าเริ่มต้น maxControl = 66650112b = 63.5625m ของเครื่องเสมือน Java;
หากคุณไม่ทำอะไรเลยการใช้งานปัจจุบันที่วัดบนเครื่องของฉัน = 5177344B = 4.9375M;
2. คำสั่งเพื่อตั้งค่าขนาดหน่วยความจำ
-xms <size> ตั้งค่า Java Heap ขนาดเริ่มต้น: ตั้งค่า JVM Initialization ขนาดหน่วยความจำกอง; ค่านี้สามารถตั้งค่าเช่นเดียวกับ -xmx เพื่อหลีกเลี่ยงหน่วยความจำการแจกจ่าย JVM ทุกครั้งที่การรวบรวมขยะเสร็จสมบูรณ์
-xmx <size> ตั้งค่าขนาดจาวาสูงสุดขนาดสูงสุด: ตั้งค่าขนาดหน่วยความจำฮีปสูงสุดของ JVM;
-xmn <size>: ตั้งค่าขนาดของคนรุ่นใหม่ขนาดฮีปทั้งหมด = ขนาดของคนรุ่นใหม่ + ขนาดของรุ่นเก่า + ขนาดของรุ่นสุดท้าย
-xss <size> ตั้งค่าสแต็กเธรด Java: ตั้งค่าขนาดหน่วยความจำสแต็กของเธรด JVM;
3. การดำเนินการเฉพาะ (1) การตั้งค่าหน่วยความจำ JVM:
เปิด myeclipse (eclipse) หน้าต่างการตอบโต้-java-java-installed jres-edit-default vm อาร์กิวเมนต์
ป้อน: -xmx128m -xms64m -xmn32m -xss16m
(2) การตั้งค่าหน่วยความจำ IDE:
แก้ไขการกำหนดค่าภายใต้ -vMargs ใน myeclipse.ini (หรือ eclipse.ini ในไดเรกทอรีรากคราส):
(3) การตั้งค่าหน่วยความจำ Tomcat
เปิดโฟลเดอร์ถังขยะในไดเรกทอรีรากของ tomcat และแก้ไข catalina.bat
แก้ไขเป็น: set java_opts = -xms256m -xmx512m
3. การวิเคราะห์ข้อผิดพลาด outofMemoryError ใน java heap
เมื่อเริ่มต้น JVM หน่วยความจำฮีปจะตั้งค่าโดยพารามิเตอร์ -xms เมื่อโปรแกรมดำเนินการต่อและสร้างวัตถุเพิ่มเติม JVM เริ่มขยายหน่วยความจำฮีปเพื่อเก็บวัตถุเพิ่มเติม JVM ยังใช้ตัวเก็บขยะเพื่อรีไซเคิลหน่วยความจำ เมื่อหน่วยความจำฮีปสูงสุดตั้งค่าโดย -xmx เกือบจะถึงถ้าไม่มีหน่วยความจำอีกต่อไปสามารถจัดสรรให้กับวัตถุใหม่ JVM จะโยน java.lang.outofmemoryError และโปรแกรมจะพัง ก่อนที่จะขว้าง OutofMemoryError JVM จะพยายามเพิ่มพื้นที่ให้มากพอกับตัวเก็บขยะ แต่จะโยนข้อผิดพลาดนี้เมื่อพบว่ายังมีพื้นที่ว่างไม่เพียงพอ ในการแก้ปัญหานี้คุณจะต้องมีความชัดเจนเกี่ยวกับข้อมูลเกี่ยวกับวัตถุโปรแกรมเช่นวัตถุที่สร้างขึ้นวัตถุใดที่มีพื้นที่ว่างมากเท่าใดคุณสามารถใช้เครื่องวิเคราะห์หรือฮีปวิเคราะห์เพื่อจัดการกับข้อผิดพลาด outofMemoryError "java.lang.outofMemoryError: พื้นที่ java heap" หมายความว่ากองไม่มีพื้นที่เพียงพอและไม่สามารถขยายได้ต่อไป "java.lang.outofMemoryError: Permgen Space" หมายความว่ารุ่นถาวรเต็มและโปรแกรมของคุณไม่สามารถโหลดคลาสหรือจัดสรรสตริงได้อีกต่อไป
4. กองและขยะเก็บขยะ
เรารู้ว่าวัตถุถูกสร้างขึ้นในหน่วยความจำฮีปการรวบรวมขยะเป็นกระบวนการที่ล้างวัตถุที่ตายแล้วออกจากพื้นที่กองและส่งคืนหน่วยความจำเหล่านี้ไปยังกอง เพื่อที่จะใช้ตัวเก็บขยะกองขยะส่วนใหญ่แบ่งออกเป็นสามพื้นที่คือคนรุ่นใหม่รุ่นเก่าหรือรุ่นที่ครอบครองและพื้นที่อนุญาต รุ่นใหม่เป็นพื้นที่ที่ใช้ในการจัดเก็บวัตถุที่สร้างขึ้นใหม่และใช้เมื่อวัตถุถูกสร้างขึ้นใหม่ หากใช้เป็นเวลานานพวกเขาจะถูกย้ายไปยังรุ่นเก่า (หรือรุ่นที่ดำรงตำแหน่ง) โดยนักสะสมขยะ พื้นที่ Perm เป็นที่ที่ JVM เก็บข้อมูลเมตาเช่นคลาสวิธีการพูลสตริงและรายละเอียดระดับชั้นเรียน
5. สรุป:
1. หน่วยความจำจาวาฮีปเป็นส่วนหนึ่งของหน่วยความจำที่จัดสรรให้กับ JVM โดยระบบปฏิบัติการ
2. เมื่อเราสร้างวัตถุพวกเขาจะถูกเก็บไว้ในหน่วยความจำ Java Heap
3. เพื่ออำนวยความสะดวกในการรวบรวมขยะพื้นที่ Java Heap ถูกแบ่งออกเป็นสามพื้นที่ที่เรียกว่าคนรุ่นใหม่รุ่นเก่าหรือรุ่นที่ครอบครองและพื้นที่อนุญาต
4. คุณสามารถปรับขนาดของพื้นที่จาวาฮีปโดยใช้ตัวเลือกบรรทัดคำสั่ง JVM -xms, -xmx และ -xmn
5. คุณสามารถใช้ jconsole หรือ runtime.maxmemory (), runtime.totalmemory () และ runtime.freememory () เพื่อดูขนาดของหน่วยความจำฮีปใน Java
6. คุณสามารถใช้คำสั่ง "JMAP" เพื่อรับกองกองและใช้ "JHAT" เพื่อวิเคราะห์กองกอง
7. พื้นที่จาวาฮีปแตกต่างจากพื้นที่สแต็ก พื้นที่สแต็กใช้ในการเก็บสแต็คการโทรและตัวแปรท้องถิ่น
8. ตัวเก็บขยะ Java ใช้เพื่อเรียกคืนหน่วยความจำที่ถูกครอบครองโดยวัตถุที่ตายแล้ว (วัตถุที่ไม่ได้ใช้อีกต่อไป) และปล่อยลงในพื้นที่จาวาฮีป
9. เมื่อพบ java.lang.outofMemoryError คุณไม่ต้องกังวล บางครั้งคุณเพียงแค่ต้องเพิ่มพื้นที่กอง แต่ถ้ามันเกิดขึ้นบ่อยครั้งคุณต้องดูว่ามีการรั่วไหลของหน่วยความจำในโปรแกรม Java หรือไม่
10. ใช้เครื่องมือวิเคราะห์การถ่ายโอนข้อมูล Profiler และกองเพื่อดูพื้นที่จาวาฮีปและคุณสามารถดูว่ามีการจัดสรรหน่วยความจำให้กับแต่ละวัตถุเท่าใด
คำอธิบายโดยละเอียดเกี่ยวกับการจัดเก็บสแต็ก
Java Stack Storage มีลักษณะดังต่อไปนี้:
1. ต้องกำหนดขนาดข้อมูลและวงจรชีวิตในสแต็ก
ตัวอย่างเช่นการจัดเก็บของประเภทพื้นฐาน: int a = 1; ตัวแปรนี้มีค่าตามตัวอักษร A คือการอ้างอิงถึงประเภท int ซึ่งชี้ไปที่ค่าตัวอักษรที่ 3 เนื่องจากขนาดและอายุการใช้งานของข้อมูลตัวอักษรเหล่านี้ค่าตัวอักษรเหล่านี้จะถูกกำหนดไว้ในบล็อกโปรแกรมและหลังจากบล็อกของโปรแกรมออกค่าตัวอักษรจะหายไป)
2. ข้อมูลที่มีอยู่ในสแต็กสามารถแชร์ได้
(1) การจัดเก็บข้อมูลประเภทพื้นฐาน:
ชอบ:
int a = 3; int b = 3;
คอมไพเลอร์ประมวลผลครั้งแรก int a = 3; ก่อนอื่นมันจะสร้างการอ้างอิงไปยังตัวแปร A ในสแต็กจากนั้นดูว่ามีที่อยู่ที่มีค่าตามตัวอักษร 3 หรือไม่หากไม่พบมันจะเปิดที่อยู่ที่มีค่าตามตัวอักษร 3 แล้วชี้ไปที่ที่อยู่ 3 จากนั้นประมวลผล int b = 3; หลังจากสร้างตัวแปรอ้างอิงของ B เนื่องจากมีค่าตามตัวอักษร 3 ในสแต็กแล้ว B จะชี้ไปที่ที่อยู่ของ 3 ด้วยวิธีนี้ A และ B ทั้งสองชี้ไปที่ 3 ในเวลาเดียวกัน
หมายเหตุ: การอ้างอิงที่แท้จริงนี้แตกต่างจากวัตถุคลาส สมมติว่าการอ้างอิงของวัตถุคลาสสองตัวชี้ไปที่วัตถุในเวลาเดียวกันหากตัวแปรอ้างอิงวัตถุหนึ่งเปลี่ยนสถานะภายในของวัตถุตัวแปรอ้างอิงวัตถุอื่น ๆ จะสะท้อนการเปลี่ยนแปลงนี้ทันที แต่การปรับเปลี่ยนค่าผ่านการอ้างอิงตามตัวอักษรจะไม่ทำให้ค่าอื่นมีการเปลี่ยนแปลงตามนั้น ดังในตัวอย่างข้างต้นหลังจากเรากำหนดค่าของ A และ B ให้ A = 4; จากนั้น B จะไม่เท่ากับ 4 หรือเท่ากับ 3 ภายในคอมไพเลอร์เมื่อพบ A = 4 มันจะค้นหาอีกครั้งว่ามีค่าตัวอักษร 4 ในสแต็ก ถ้าไม่เปิดที่อยู่อีกครั้งเพื่อจัดเก็บค่า 4; หากมีอยู่แล้วชี้ไปที่ที่อยู่นี้โดยตรง ดังนั้นการเปลี่ยนแปลงของมูลค่า A จะไม่ส่งผลกระทบต่อค่า b
(2) การจัดเก็บข้อมูลบรรจุภัณฑ์:
คลาสที่ห่อประเภทข้อมูลพื้นฐานที่สอดคล้องกันเช่นจำนวนเต็มสองเท่าสตริง ฯลฯ ข้อมูลคลาสทั้งหมดเหล่านี้มีอยู่ในฮีป Java ใช้คำสั่งใหม่ () เพื่อแสดงคอมไพเลอร์และสร้างขึ้นอย่างมีชีวิตชีวาตามความจำเป็นในการรันไทม์ดังนั้นจึงมีความยืดหยุ่นมากขึ้น แต่ข้อเสียคือต้องใช้เวลามากขึ้น
ตัวอย่างเช่น: ใช้สตริงเป็นตัวอย่าง
สตริงเป็นข้อมูลบรรจุภัณฑ์พิเศษ นั่นคือมันสามารถสร้างได้ในรูปแบบของสตริง str = สตริงใหม่ ("abc"); หรือสามารถสร้างได้ในรูปแบบของสตริง str = "abc"; อดีตคือกระบวนการสร้างคลาสที่ได้มาตรฐานนั่นคือใน Java ทุกอย่างเป็นวัตถุและวัตถุเป็นตัวอย่างของคลาสทั้งหมดที่สร้างขึ้นในรูปแบบของใหม่ () บางคลาสใน Java เช่นคลาส DateFormat สามารถส่งคืนคลาสที่สร้างขึ้นใหม่ผ่านวิธี GetInstance () ของชั้นเรียนซึ่งดูเหมือนจะละเมิดหลักการนี้ จริงๆแล้วมันไม่ใช่กรณี คลาสนี้ใช้รูปแบบ Singleton เพื่อส่งคืนอินสแตนซ์ของคลาส แต่อินสแตนซ์นี้ถูกสร้างขึ้นภายในคลาสผ่านใหม่ () ซึ่งซ่อนรายละเอียดนี้จากภายนอก
ถ้าเช่นนั้นทำไมอินสแตนซ์ไม่ได้สร้างผ่าน new () ใน string str = "abc"; มันเป็นการละเมิดหลักการข้างต้นหรือไม่? จริงๆแล้วไม่มี
เกี่ยวกับงานภายในของ String Str = "ABC" Java แปลงคำสั่งนี้เป็นขั้นตอนต่อไปนี้:
. ก่อนกำหนดตัวแปรอ้างอิงวัตถุชื่อ Str ไปยังคลาสสตริง: สตริง Str;
ข. ค้นหาว่ามีที่อยู่ที่มีค่า "ABC" ในสแต็กหรือไม่ ถ้าไม่เปิดที่อยู่ด้วยค่าตัวอักษร "ABC" จากนั้นสร้างวัตถุใหม่ o ของคลาสสตริงและชี้ค่าสตริงของ O ไปยังที่อยู่นี้และจดบันทึกวัตถุอ้างอิง o ถัดจากที่อยู่นี้ในสแต็ก หากมีที่อยู่ที่มีค่า "ABC" ให้ค้นหาวัตถุ O และส่งคืนที่อยู่ของ O.
ค. ชี้ไปที่ที่อยู่ของ Object O.
เป็นที่น่าสังเกตว่าโดยปกติค่าสตริงในคลาสสตริงจะถูกเก็บไว้โดยตรง แต่ในสถานการณ์เช่น string str = "abc";, ค่าสตริงของมันมีการอ้างอิงถึงข้อมูลที่มีอยู่ในสแต็ก (เช่น: สตริง str = "abc"; ทั้งที่เก็บสแต็กและที่เก็บฮีป)
เพื่อแสดงให้เห็นถึงปัญหานี้ได้ดีขึ้นเราสามารถตรวจสอบได้ผ่านรหัสต่อไปนี้
string str1 = "abc"; string str2 = "abc"; System.out.println (str1 == str2); //จริง
(ค่าความจริงจะถูกส่งคืนเฉพาะในกรณีที่การอ้างอิงทั้งสองชี้ไปที่วัตถุเดียวกันคือ str1 และ str2 ชี้ไปที่วัตถุเดียวกัน)
ผลการวิจัยแสดงให้เห็นว่า JVM สร้างการอ้างอิงสองครั้ง STR1 และ STR2 แต่มีเพียงวัตถุเดียวเท่านั้นที่ถูกสร้างขึ้นและการอ้างอิงทั้งสองชี้ไปที่วัตถุนี้
string str1 = "abc"; string str2 = "abc"; str1 = "bcd"; System.out.println (str1 + "," + str2); // bcd, abc system.out.println (str1 == str2); //เท็จ
ซึ่งหมายความว่าการเปลี่ยนแปลงในการกำหนดส่งผลให้เกิดการเปลี่ยนแปลงในการอ้างอิงของวัตถุคลาส STR1 ชี้ไปที่วัตถุใหม่อื่นในขณะที่ STR2 ยังคงชี้ไปที่วัตถุดั้งเดิม ในตัวอย่างข้างต้นเมื่อเราเปลี่ยนค่าของ STR1 เป็น "BCD" JVM พบว่าไม่มีที่อยู่ในการจัดเก็บค่าในสแต็กดังนั้นจึงเปิดที่อยู่นี้และสร้างวัตถุใหม่ที่มีค่าสตริงชี้ไปที่ที่อยู่นี้
ในความเป็นจริงคลาสสตริงได้รับการออกแบบให้เป็นคลาสที่ไม่เปลี่ยนรูป หากคุณต้องการเปลี่ยนค่าคุณสามารถทำได้ แต่ JVM สร้างวัตถุใหม่อย่างเงียบ ๆ ตามค่าใหม่ที่รันไทม์ (ไม่สามารถเปลี่ยนแปลงได้ตามหน่วยความจำดั้งเดิม) จากนั้นส่งคืนที่อยู่ของวัตถุนี้ไปยังการอ้างอิงของคลาสดั้งเดิม แม้ว่ากระบวนการสร้างนี้จะเป็นไปโดยอัตโนมัติอย่างสมบูรณ์ แต่ก็ต้องใช้เวลามากขึ้นหลังจากทั้งหมด ในสภาพแวดล้อมที่มีความไวต่อความต้องการเวลามากขึ้นมันจะมีผลข้างเคียงบางอย่าง
string str1 = "abc"; string str2 = "abc"; str1 = "bcd"; string str3 = str1; System.out.println (STR3); // bcd string str4 = "bcd"; System.out.println (str1 == str4); //จริง
การอ้างอิงถึงวัตถุ STR3 ชี้ไปที่วัตถุที่ชี้ไปที่ StR1 โดยตรง (โปรดทราบว่า STR3 ไม่ได้สร้างวัตถุใหม่) หลังจากที่ STR1 มีการเปลี่ยนแปลงค่าแล้วให้สร้าง String Reference Str4 และชี้ไปที่วัตถุใหม่ที่สร้างโดย STR1 การแก้ไขค่า จะพบได้ว่าเวลานี้ STR4 ไม่ได้สร้างวัตถุใหม่ดังนั้นจึงตระหนักถึงการแบ่งปันข้อมูลในสแต็กอีกครั้ง
string str1 = สตริงใหม่ ("abc"); string str2 = "abc"; System.out.println (str1 == str2); //เท็จมีการอ้างอิงสองรายการ มีการสร้างวัตถุสองชิ้น การอ้างอิงทั้งสองชี้ไปที่วัตถุที่แตกต่างกันสองชิ้น
string str1 = "abc"; string str2 = สตริงใหม่ ("abc"); System.out.println (str1 == str2); //เท็จมีการอ้างอิงสองรายการ มีการสร้างวัตถุสองชิ้น การอ้างอิงทั้งสองชี้ไปที่วัตถุที่แตกต่างกันสองชิ้น
สองรหัสข้างต้นระบุว่าตราบใดที่วัตถุถูกสร้างขึ้นด้วยใหม่ () มันจะถูกสร้างขึ้นในกองและสตริงของมันจะถูกเก็บไว้แยกกัน แม้ว่าพวกเขาจะเหมือนกันกับข้อมูลในสแต็กพวกเขาจะไม่ถูกแชร์กับข้อมูลในสแต็ก
สรุป:
(1) เมื่อเรากำหนดคลาสโดยใช้รูปแบบเช่น string str = "abc";, เรามักจะรับมันเพื่อให้เราสร้างวัตถุ STR ของคลาสสตริง กังวลเกี่ยวกับกับดัก! วัตถุอาจไม่ได้ถูกสร้างขึ้น! สิ่งเดียวที่แน่นอนคือการอ้างอิงถึงคลาสสตริงถูกสร้างขึ้น สำหรับการอ้างอิงนี้ชี้ไปที่วัตถุใหม่จะต้องได้รับการพิจารณาตามบริบทเว้นแต่คุณจะใช้วิธีใหม่ () เพื่อสร้างวัตถุใหม่อย่างชัดเจน ดังนั้นเพื่อให้แม่นยำยิ่งขึ้นเราจึงสร้างตัวแปรอ้างอิง STR ไปยังวัตถุของคลาสสตริงซึ่งชี้ไปที่คลาสสตริงที่มีค่าของ "ABC" การตระหนักถึงสิ่งนี้มีประโยชน์ในการแก้ไขปัญหาข้อบกพร่องที่ยากในโปรแกรม
(2) การใช้ String str = "ABC"; สามารถปรับปรุงความเร็วในการทำงานของโปรแกรมในระดับหนึ่งเนื่องจาก JVM จะตัดสินใจโดยอัตโนมัติว่าจำเป็นต้องสร้างวัตถุใหม่ตามสถานการณ์จริงของข้อมูลในสแต็กหรือไม่ สำหรับรหัสของสตริง str = สตริงใหม่ ("ABC");, วัตถุใหม่ถูกสร้างขึ้นในฮีปโดยไม่คำนึงว่าค่าสตริงของพวกเขาเท่ากันหรือไม่ไม่ว่าจะเป็นสิ่งจำเป็นในการสร้างวัตถุใหม่ซึ่งจะเป็นการเพิ่มภาระในโปรแกรม
(3) เนื่องจากลักษณะที่ไม่เปลี่ยนรูปของคลาสสตริง (เนื่องจากค่าของคลาส wrapper ไม่สามารถแก้ไขได้) เมื่อจำเป็นต้องเปลี่ยนตัวแปรสตริงบ่อยครั้งคลาสสตริงบัฟเฟอร์ควรได้รับการพิจารณาเพื่อปรับปรุงประสิทธิภาพของโปรแกรม