overzicht
In deze handleiding laten we de voors en tegens zien van het gebruik van Java primitieve types en hun omwikkelde tegenhangers.
Java type systeem
Java heeft een tweevoudig type systeem dat bestaat uit primitieven zoals int, boolean en referentie types zoals Integer, Boolean. Elk primitief type komt overeen met een referentietype.
elk object bevat een enkele waarde van het corresponderende primitieve type. De wrapper klassen zijn onveranderlijk (zodat hun toestand niet kan veranderen zodra het object is geconstrueerd) en zijn definitief (zodat we niet kunnen erven van hen).
onder de motorkap voert Java een conversie uit tussen de primitieve en referentie typen als een feitelijk type verschilt van het gedeclareerde type:
Integer j = 1; // autoboxingint i = new Integer(1); // unboxing
het proces van het converteren van een primitief type naar een referentie wordt autoboxing genoemd, het tegenovergestelde proces wordt unboxing genoemd.
voors en tegens
de beslissing welk object moet worden gebruikt is gebaseerd op welke prestaties van de toepassing we proberen te bereiken, hoeveel beschikbaar geheugen we hebben, de hoeveelheid beschikbaar geheugen en welke standaardwaarden we moeten hanteren.
als we geen van deze zaken onder ogen zien, kunnen we deze overwegingen negeren, hoewel het de moeite waard is ze te kennen.
3.1. Single Item Memory Footprint
alleen voor de referentie, de primitieve type variabelen hebben de volgende impact op het geheugen:
- boolean – 1 bit
- byte-8 bits
- kort, char-16 bits
- int, float – 32 bits
- lang, dubbel 64 bits
in de praktijk kunnen deze waarden variëren afhankelijk van de implementatie van de virtuele Machine. In Oracle ‘ s VM wordt het Booleaanse type bijvoorbeeld toegewezen aan int-waarden 0 en 1, dus het duurt 32 bits, zoals hier beschreven: primitieve Types en waarden.
variabelen van deze types leven in de stack en worden daarom snel benaderd. Voor de details, raden we onze tutorial over het Java memory model.
De referentietypes zijn objecten, ze leven op de hoop en zijn relatief traag toegankelijk. Ze hebben een zekere overhead met betrekking tot hun primitieve tegenhangers.
De concrete waarden van de overheadkosten zijn in het algemeen JVM-specifiek. Hier presenteren we resultaten voor een 64-bit virtuele machine met de volgende parameters:
java 10.0.1 2018-04-17Java(TM) SE Runtime Environment 18.3 (build 10.0.1+10)Java HotSpot(TM) 64-Bit Server VM 18.3 (build 10.0.1+10, mixed mode)
om de interne structuur van een object te krijgen, kunnen we het Java Object Layout tool gebruiken (zie onze andere tutorial over hoe de grootte van een object te krijgen).
Het blijkt dat een enkele instantie van een referentietype op deze JVM 128 bits bezet behalve voor lange en dubbele die 192 bits bezetten:
- Boolean – 128 bits
- Byte – 128 bits
- short, Character – 128 bits
- Integer, Float – 128 bits
- Long, Double – 192 bits
We kunnen zien dat een enkele variabele van booleaans type neemt net zoveel ruimte in als 128 primitieve, terwijl één integer variabele net zoveel ruimte in beslag neemt als vier int-enen.
3.2. Geheugen voetafdruk voor Arrays
de situatie wordt interessanter als we vergelijken hoeveel geheugen arrays van de onderzochte types bezet.
wanneer we arrays maken met het verschillende aantal elementen voor elk type, verkrijgen we een plot:
die aantoont dat de types zijn gegroepeerd in vier families met betrekking tot hoe het geheugen m(s) afhangt van het aantal elementen s van de array:
- lang, dubbel: m(s) = 128 + 64 s
- kort, char: m(s) = 128 + 64
- Byte, Boolean: m(s) = 128 + 64
- de rest: m ( s) = 128 + 64
waarbij de vierkante haken de standaard plafondfunctie aangeven.
verrassend genoeg verbruiken arrays van de primitieve types long en double meer geheugen dan hun wrapper klassen Long en Double.
We kunnen zien dat arrays met een enkel element van primitieve types bijna altijd duurder zijn (behalve voor lang en dubbel) dan het corresponderende referentietype.
3.3. Performance
De performance van een Java-code is een heel subtiel probleem, het hangt sterk af van de hardware waarop de code draait, van de compiler die bepaalde optimalisaties kan uitvoeren, van de status van de virtuele machine, van de activiteit van andere processen in het besturingssysteem.
zoals we al hebben gezegd, leven de primitieve types in de stack terwijl de referentietypes in de heap leven. Dit is een dominante factor die bepaalt hoe snel de objecten worden benaderd.
om aan te tonen in hoeverre de operaties voor primitieve types sneller zijn dan die voor wrapper klassen, maken we een vijf miljoen element array waarin alle elementen gelijk zijn behalve de laatste; dan voeren we een lookup uit voor dat element:
while (!pivot.equals(elements)) { index++;}
en vergelijken we de prestaties van deze operatie voor het geval wanneer de array variabelen van de primitieve types bevat en voor het geval wanneer het objecten van de referentie types bevat.
We gebruiken de bekende JMH benchmarking tool (zie onze tutorial over hoe het te gebruiken), en de resultaten van de lookup operatie kunnen worden samengevat in deze grafiek:
zelfs voor een dergelijke eenvoudige operatie, kunnen we zien dat er meer tijd nodig is om de operatie uit te voeren voor wrapper klassen.
in het geval van meer gecompliceerde operaties zoals optelling, vermenigvuldiging of deling, kan het verschil in snelheid omhoogschieten.
3.4. Standaardwaarden
standaardwaarden van de primitieve typen zijn 0 (in de corresponderende representatie, d.w.z. 0, 0.0d etc) voor numerieke types, false Voor het Booleaanse type, \u0000 voor het char type. Voor de wrapper klassen is de standaardwaarde null.
het betekent dat de primitieve types alleen waarden kunnen verwerven uit hun domeinen, terwijl de referentietypes een waarde (null) kunnen verwerven die in zekere zin niet tot hun domeinen behoort.
hoewel het niet als een goede gewoonte wordt beschouwd om variabelen niet geïnitialiseerd te laten, kunnen we soms een waarde toekennen na het aanmaken ervan.
In een dergelijke situatie, wanneer een primitieve type variabele een waarde heeft die gelijk is aan zijn standaard type, moeten we uitzoeken of de variabele echt geïnitialiseerd is.
Er is geen dergelijk probleem met een wrapper class variabelen omdat de null waarde een duidelijke indicatie is dat de variabele niet is geïnitialiseerd.
gebruik
zoals we hebben gezien, zijn de primitieve types veel sneller en vereisen ze veel minder geheugen. Daarom willen we ze misschien liever gebruiken.
aan de andere kant staat de huidige Java-taalspecificatie het gebruik van primitieve typen niet toe in de geparametriseerde typen (generics), in de Java-collecties of de reflectie-API.
wanneer onze applicatie Collecties met een groot aantal elementen nodig heeft, moeten we overwegen arrays met een zo zuiniger mogelijk type te gebruiken, zoals het is geïllustreerd op de plot hierboven.
conclusie
in deze tutorial hebben we geïllustreerd dat de objecten in Java trager zijn en een grotere geheugenimpact hebben dan hun primitieve analogen.
zoals altijd kunnen codefragmenten gevonden worden in onze repository op GitHub.
aan de slag met Spring 5 en Spring Boot 2, Via de learn Spring course:
>> bekijk de cursus