Java IOException "יותר מדי קבצים פתוחים"

1. הקדמה

מלכודת נפוצה בעבודה עם קבצים ב- Java היא האפשרות להיגמר מתארי הקבצים הזמינים.

במדריך זה נבחן מצב זה ונציע שתי דרכים להימנע מבעיה זו.

2. כיצד JVM מטפל בקבצים

למרות ש- JVM עושה עבודה מצוינת מבודדת אותנו ממערכת ההפעלה, היא מאצילה פעולות ברמה נמוכה כמו ניהול קבצים למערכת ההפעלה.

המשמעות היא שלכל קובץ שאנו פותחים ביישום Java, מערכת ההפעלה תקצה מתאר קבצים שיחבר את הקובץ לתהליך ה- Java שלנו. לאחר סיום ה- JVM עם הקובץ, הוא משחרר את המתאר.

עכשיו בואו נצלול כיצד נוכל להפעיל את החריג.

3. מתארים קבצים דולפים

נזכיר שלכל הפניה לקובץ ביישום Java שלנו, יש לנו מתאר קבצים תואם במערכת ההפעלה. מתאר זה ייסגר רק כאשר מופנה התייחסות לקובץ. זה יקרה בשלב איסוף האשפה.

עם זאת, אם ההפניה נשארת פעילה ויותר ויותר קבצים נפתחים, בסופו של דבר למערכת ההפעלה ייגמרו מתארי הקבצים להקצאה. בשלב זה, היא תעביר את המצב הזה ל- JVM, מה שיוביל למצב של IOException נזרק.

אנו יכולים לשחזר מצב זה באמצעות מבחן יחידה קצר:

@ מבחן ציבורי בטל כאשר NotClosingResoures_thenIOExceptionShouldBeThrown () {נסה {עבור (int x = 0; x <1000000; x ++) {FileInputStream leakyHandle = חדש FileInputStream (tempFile); } נכשל ("השיטה הייתה צריכה להיכשל"); } לתפוס (IOException e) {assertTrue (e.getMessage (). containIgnoreCase ("יותר מדי קבצים פתוחים")); } לתפוס (חריג e) {fail ("חריג לא צפוי"); }} 

ברוב מערכות ההפעלה, תהליך ה- JVM ייגמר מתארי הקבצים לפני השלמת הלולאה, ובכך יפעיל את ה- IOException.

בואו נראה איך נוכל להימנע ממצב זה עם טיפול נכון במשאבים.

4. טיפול במשאבים

כפי שאמרנו קודם, מתארים קבצים משוחררים על ידי תהליך JVM במהלך איסוף האשפה.

אך אם לא סגרנו כראוי את התייחסות הקובץ שלנו, ייתכן שהאספן יבחר שלא להשמיד את ההפניה באותו זמן, ולהשאיר את המתאר פתוח ולהגביל את מספר הקבצים שנוכל לפתוח.

עם זאת, אנו יכולים להסיר בעיה זו בקלות על ידי וידוא שאם נפתח קובץ, אנו מבטיחים שנסגור אותו כאשר איננו זקוקים לו יותר.

4.1. שחרור ידני של הפניות

פרסום ידני של הפניות היה דרך נפוצה להבטיח ניהול נכון של משאבים לפני JDK 8.

לא רק לעשות עלינו לסגור במפורש את כל הקובץ שנפתח, אך גם ודא כי אנו עושים זאת גם אם הקוד שלנו נכשל וגורם חריגים. משמעות הדבר היא שימוש ב- סוף כל סוף מילת מפתח:

@Test הציבור בטל כאשרClosingResoures_thenIOExceptionShouldNotBeThrown () {נסה {עבור (int x = 0; x <1000000; x ++) {FileInputStream nonLeakyHandle = null; נסה את {nonLeakyHandle = FileInputStream חדש (tempFile); } סוף סוף {if (nonLeakyHandle! = null) {nonLeakyHandle.close (); }}}} לתפוס (IOException e) {assertFalse (e.getMessage (). toLowerCase (). מכיל ("יותר מדי קבצים פתוחים")); fail ("השיטה לא הייתה צריכה להיכשל"); } לתפוס (חריג e) {fail ("חריג לא צפוי"); }} 

כמו ה סוף כל סוף בלוק מתבצע תמיד, זה נותן לנו את ההזדמנות לסגור כראוי את ההתייחסות שלנו, ובכך להגביל את מספר המתארים הפתוחים.

4.2. באמצעות נסה עם משאבים

JDK 7 מביא לנו דרך נקייה יותר לבצע סילוק משאבים. זה מכונה בדרך כלל נסה עם משאבים ומאפשר לנו להאציל את סילוק המשאבים על ידי הכללת המשאב ב- לְנַסוֹת הַגדָרָה:

@Test ציבורי בטל כאשרUsingTryWithResoures_thenIOExceptionShouldNotBeThrown () {נסה {עבור (int x = 0; x <1000000; x ++) {נסה (FileInputStream nonLeakyHandle = FileInputStream חדש (tempFile)) {// עשה משהו עם הקובץ}}} לתפוס eOc ) {assertFalse (e.getMessage (). toLowerCase (). מכיל ("יותר מדי קבצים פתוחים")); להיכשל ("השיטה לא הייתה צריכה להיכשל"); } לתפוס (חריג e) {fail ("חריג לא צפוי"); }}

הנה, הכרזנו nonLeakyHandle בתוך ה לְנַסוֹת הַצהָרָה. מסיבה זו, ג'אווה תסגור את המשאב עבורנו במקום שנצטרך להשתמש בו סוף כל סוף.

5. מסקנה

כפי שאנו רואים, אי סגירה נכונה של קבצים פתוחים יכולה להוביל אותנו לחריג מורכב עם השלכות בכל התוכניות שלנו. בעזרת טיפול נכון במשאבים, אנו יכולים להבטיח שבעיה זו לעולם לא תופיע בפני עצמה.

קוד המקור המלא של המאמר זמין באתר GitHub.


$config[zx-auto] not found$config[zx-overlay] not found