Java IOException "Te veel geopende bestanden"

1. Inleiding

Een veel voorkomende valkuil bij het werken met bestanden in Java is de mogelijkheid dat de beschikbare bestandsdescriptors opraken.

In deze zelfstudie bekijken we deze situatie en bieden we twee manieren om dit probleem te voorkomen.

2. Hoe de JVM omgaat met bestanden

Hoewel de JVM uitstekend werk verricht door ons te isoleren van het besturingssysteem, delegeert het bewerkingen op laag niveau, zoals bestandsbeheer, aan het besturingssysteem.

Dit betekent dat voor elk bestand dat we openen in een Java-applicatie, het besturingssysteem een ​​bestandsdescriptor zal toewijzen om het bestand te relateren aan ons Java-proces. Zodra de JVM klaar is met het bestand, geeft het de descriptor vrij.

Laten we nu eens kijken hoe we de uitzondering kunnen activeren.

3. Lekkende bestandsdescriptors

Bedenk dat we voor elke bestandsverwijzing in onze Java-applicatie een bijbehorende bestandsdescriptor in het besturingssysteem hebben. Deze descriptor wordt alleen gesloten als de bestandsreferentie-instantie wordt verwijderd. Dit gebeurt tijdens de Garbage Collection-fase.

Als de referentie echter actief blijft en er steeds meer bestanden worden geopend, heeft het besturingssysteem uiteindelijk geen bestandsdescriptors meer om toe te wijzen. Op dat moment zal het deze situatie doorgeven aan de JVM, wat zal resulteren in een IOException wordt gegooid.

We kunnen deze situatie reproduceren met een korte unit-test:

@Test openbare leegte whenNotClosingResoures_thenIOExceptionShouldBeThrown () {probeer {voor (int x = 0; x <1000000; x ++) {FileInputStream leakyHandle = nieuwe FileInputStream (tempFile); } fail ("Methode had gefaald moeten zijn"); } catch (IOException e) {assertTrue (e.getMessage (). containsIgnoreCase ("te veel geopende bestanden")); } catch (uitzondering e) {fail ("onverwachte uitzondering"); }} 

Op de meeste besturingssystemen heeft het JVM-proces geen bestandsdescriptors meer voordat de lus is voltooid, waardoor de IOException.

Laten we eens kijken hoe we deze toestand kunnen vermijden met de juiste omgang met bronnen.

4. Omgaan met bronnen

Zoals we al eerder zeiden, worden bestandsdescriptors vrijgegeven door het JVM-proces tijdens Garbage Collection.

Maar als we onze bestandsreferentie niet correct hebben gesloten, kan de verzamelaar ervoor kiezen om de referentie op dat moment niet te vernietigen, de descriptor open te laten en het aantal bestanden dat we konden openen te beperken.

We kunnen dit probleem echter gemakkelijk verhelpen door ervoor te zorgen dat als we een bestand openen, we ervoor zorgen dat we het sluiten wanneer we het niet langer nodig hebben.

4.1. Handmatig verwijzingen vrijgeven

Het handmatig vrijgeven van referenties was een veelgebruikte manier om een ​​goed resourcebeheer te garanderen vóór JDK 8.

Niet alleen we moeten elk bestand dat we openen expliciet sluiten, maar zorg er ook voor dat we het doen, zelfs als onze code faalt en uitzonderingen genereert. Dit betekent dat u de Tenslotte trefwoord:

@ Test openbare leegte whenClosingResoures_thenIOExceptionShouldNotBeThrown () {probeer {voor (int x = 0; x <1000000; x ++) {FileInputStream nonLeakyHandle = null; probeer {nonLeakyHandle = nieuwe FileInputStream (tempFile); } eindelijk {if (nonLeakyHandle! = null) {nonLeakyHandle.close (); }}}} catch (IOException e) {assertFalse (e.getMessage (). toLowerCase (). bevat ("te veel geopende bestanden")); fail ("Methode had niet mogen mislukken"); } catch (uitzondering e) {fail ("onverwachte uitzondering"); }} 

Zoals de Tenslotte block wordt altijd uitgevoerd, het geeft ons de kans om onze referentie correct te sluiten, waardoor het aantal open descriptors wordt beperkt.

4.2. Gebruik makend van probeer-met-middelen

JDK 7 biedt ons een schonere manier om hulpbronnen te verwijderen. Het is algemeen bekend als probeer-met-middelen en stelt ons in staat om de verwijdering van middelen te delegeren door de bron op te nemen in de proberen definitie:

@Test public void whenUsingTryWithResoures_thenIOExceptionShouldNotBeThrown () {try {for (int x = 0; x <1000000; x ++) {try (FileInputStream nonLeakyHandle = nieuwe FileInputStream (tempFile)) {// doe iets met het bestand}}} e vangst ) {assertFalse (e.getMessage (). toLowerCase (). bevat ("te veel geopende bestanden")); fail ("Methode had niet mogen mislukken"); } catch (uitzondering e) {fail ("onverwachte uitzondering"); }}

Hier hebben we verklaard nonLeakyHandle binnen in de proberen uitspraak. Daarom zal Java de bron voor ons sluiten in plaats van dat we deze hoeven te gebruiken Tenslotte.

5. Conclusie

Zoals we kunnen zien, kan het niet goed sluiten van geopende bestanden ons leiden tot een complexe uitzondering met vertakkingen in ons hele programma. Met de juiste omgang met middelen kunnen we ervoor zorgen dat dit probleem zich nooit zal voordoen.

De volledige broncode voor het artikel is beschikbaar op GitHub.