Vejledning til sun.misc. usikker

1. Oversigt

I denne artikel vil vi se på en fascinerende klasse, der leveres af JRE - Usikker fra sun.misc pakke. Denne klasse giver os mekanismer på lavt niveau, der blev designet til kun at blive brugt af det centrale Java-bibliotek og ikke af standardbrugere.

Dette giver os mekanismer på lavt niveau, der primært er designet til intern brug inden for kernebibliotekerne.

2. Opnåelse af en forekomst af Usikker

For det første at kunne bruge Usikker klasse, er vi nødt til at få en forekomst - hvilket ikke er ligetil, da klassen kun var designet til intern brug.

Måden at få forekomsten på er via den statiske metode getUnsafe (). Advarslen er, at som standard - dette vil kaste et Sikkerhedsundtagelse.

Heldigvis, vi kan få forekomsten ved hjælp af refleksion:

Felt f = Unsafe.class.getDeclaredField ("theUnsafe"); f.setAccessible (true); usikker = (usikker) f.get (null);

3. Instantiering af en klasse ved hjælp af Usikker

Lad os sige, at vi har en simpel klasse med en konstruktør, der indstiller en variabelværdi, når objektet oprettes:

klasse InitialiseringBestilling {privat lang a; public InitializationOrdering () {this.a = 1; } offentlig lang getA () {returner dette.a; }}

Når vi initialiserer objektet ved hjælp af konstruktøren, få en() metoden returnerer en værdi på 1:

InitializationOrdering o1 = new InitializationOrdering (); assertEquals (o1.getA (), 1);

Men vi kan bruge allocateInstance () metode ved hjælp af Usikker. Det tildeler kun hukommelsen til vores klasse og påkalder ikke en konstruktør:

InitializationOrdering o3 = (InitializationOrdering) unsafe.allocateInstance (InitializationOrdering.class); assertEquals (o3.getA (), 0);

Bemærk, at konstruktøren ikke blev påberåbt, og på grund af dette faktum, få en() metoden returnerede standardværdien for lang type - som er 0.

4. Ændring af private felter

Lad os sige, at vi har en klasse, der har en hemmelighed privat værdi:

klasse SecretHolder {private int SECRET_VALUE = 0; offentlig boolsk secretIsDisclosed () {return SECRET_VALUE == 1; }}

Bruger putInt () metode fra Usikre, vi kan ændre en værdi af det private SECRET_VALUE felt, ændre / ødelægge tilstanden for den instans:

SecretHolder secretHolder = ny SecretHolder (); Felt f = secretHolder.getClass (). GetDeclaredField ("SECRET_VALUE"); unsafe.putInt (hemmeligHolder, usikker.objectFieldOffset (f), 1); assertTrue (secretHolder.secretIsDisclosed ());

Når vi først får et felt ved refleksionsopkaldet, kan vi ændre dets værdi til ethvert andet int værdi ved hjælp af Usikker.

5. At kaste en undtagelse

Koden, der påberåbes via Usikker undersøges ikke på samme måde af compileren som almindelig Java-kode. Vi kan bruge throwException () metode til at kaste enhver undtagelse uden at begrænse den, der ringer op til at håndtere denne undtagelse, selvom det er en markeret undtagelse:

@Test (forventet = IOException.class) offentlig ugyldighed givenUnsafeThrowException_whenThrowCheckedException_thenNotNeedToCatchIt () {unsafe.throwException (ny IOException ()); }

Efter at have kastet en IO-undtagelse, som er markeret, behøver vi ikke fange det eller specificere det i metodedeklarationen.

6. Hukommelse uden for bunken

Hvis et program løber tør for ledig hukommelse på JVM, kan vi ende med at tvinge GC-processen til at køre for ofte. Ideelt set vil vi have en speciel hukommelsesregion, off-heap og ikke styret af GC-processen.

Det allocateMemory () metode fra Usikker klasse giver os muligheden for at allokere enorme objekter fra bunken, hvilket betyder det denne hukommelse kan ikke ses og tages i betragtning af GC og JVM.

Dette kan være meget nyttigt, men vi skal huske, at denne hukommelse skal styres manuelt og korrekt genvindes med freeMemory () når det ikke længere er nødvendigt.

Lad os sige, at vi ønsker at oprette det store hukommelsesarray af bytes. Vi kan bruge allocateMemory () metode til at opnå det:

klasse OffHeapArray {privat endelig statisk int BYTE = 1; privat lang størrelse; privat lang adresse; offentlig OffHeapArray (lang størrelse) kaster NoSuchFieldException, IllegalAccessException {this.size = størrelse; adresse = getUnsafe (). allocateMemory (størrelse * BYTE); } privat usikker getUnsafe () kaster IllegalAccessException, NoSuchFieldException {Field f = Unsafe.class.getDeclaredField ("theUnsafe"); f.setAccessible (true); return (Usikker) f.get (null); } offentligt ugyldigt sæt (lang i, byteværdi) kaster NoSuchFieldException, IllegalAccessException {getUnsafe (). putByte (adresse + i * BYTE, værdi); } public int get (long idx) kaster NoSuchFieldException, IllegalAccessException {return getUnsafe (). getByte (adresse + idx * BYTE); } offentlig lang størrelse () {returstørrelse; } public void freeMemory () kaster NoSuchFieldException, IllegalAccessException {getUnsafe (). freeMemory (adresse); }
}

I konstruktøren af OffHeapArray, vi initialiserer det array, der er af en given størrelse. Vi gemmer startadressen for arrayet i adresse Mark. Det sæt() metoden er at tage indekset og det givne værdi der gemmes i arrayet. Det få() metoden er at hente byteværdien ved hjælp af dens indeks, der er en forskydning fra matrixens startadresse.

Dernæst kan vi tildele det off-heap-array ved hjælp af dets konstruktør:

lang SUPER_SIZE = (lang) Heltal.MAX_VALUE * 2; OffHeapArray array = ny OffHeapArray (SUPER_SIZE);

Vi kan sætte N antal byteværdier i denne matrix og derefter hente disse værdier og opsummere dem for at teste, om vores adressering fungerer korrekt:

int sum = 0; for (int i = 0; i <100; i ++) {array.set ((long) Integer.MAX_VALUE + i, (byte) 3); sum + = array.get ((long) Integer.MAX_VALUE + i); } assertEquals (array.size (), SUPER_SIZE); assertEquals (sum, 300);

I sidste ende er vi nødt til at frigøre hukommelsen tilbage til operativsystemet ved at ringe freeMemory ().

7. CompareAndSwap Operation

De meget effektive konstruktioner fra java.concurrent pakke, ligesom AtomicInteger, bruger sammenlignAndSwap () metoder ud af Usikker nedenunder for at give den bedst mulige ydeevne. Denne konstruktion bruges i vid udstrækning i de låsefrie algoritmer, der kan udnytte CAS-processorinstruktionen til at give stor hastighed i forhold til den standard pessimistiske synkroniseringsmekanisme i Java.

Vi kan konstruere den CAS-baserede tæller ved hjælp af sammenlignAndSwapLong () metode fra Usikker:

klasse CASCounter {privat usikker usikker; privat flygtig lang tæller = 0; privat lang offset; private Usikre getUnsafe () kaster IllegalAccessException, NoSuchFieldException {Field f = Unsafe.class.getDeclaredField ("theUnsafe"); f.setAccessible (true); return (Usikker) f.get (null); } offentlig CASCounter () kaster undtagelse {usikker = getUnsafe (); offset = unsafe.objectFieldOffset (CASCounter.class.getDeclaredField ("counter")); } offentlig tomrumsforøgelse () {længe før = tæller; while (! unsafe.compareAndSwapLong (dette, offset, før, før + 1)) {before = counter; }} offentlig lang getCounter () {retur tæller; }}

I CASTæller konstruktør vi får adressen på tællerfeltet for at kunne bruge det senere i stigning () metode. Dette felt skal erklæres som det flygtige for at være synligt for alle tråde, der skriver og læser denne værdi. Vi bruger objectFieldOffset () metode til at få hukommelsesadressen til forskudt Mark.

Den vigtigste del af denne klasse er stigning () metode. Vi bruger sammenlignAndSwapLong () i mens loop for at øge den tidligere hentede værdi og kontrollere, om den tidligere værdi blev ændret, siden vi hentede den.

Hvis det gjorde det, prøver vi igen denne operation, indtil vi lykkes. Der er ingen blokering her, hvorfor dette kaldes en låsefri algoritme.

Vi kan teste vores kode ved at øge den delte tæller fra flere tråde:

int NUM_OF_THREADS = 1_000; int NUM_OF_INCREMENTS = 10_000; ExecutorService-tjeneste = Executors.newFixedThreadPool (NUM_OF_THREADS); CASCounter casCounter = ny CASCounter (); IntStream.rangeClosed (0, NUM_OF_THREADS - 1). ForEach (i -> service.submit (() -> IntStream .rangeClosed (0, NUM_OF_INCREMENTS - 1) .forEach (j -> casCounter.increment ())));

For derefter at hævde, at tællerens tilstand er korrekt, kan vi få tællerværdien af ​​den:

assertEquals (NUM_OF_INCREMENTS * NUM_OF_THREADS, casCounter.getCounter ());

8. Parker / afparker

Der er to fascinerende metoder i Usikker API, der bruges af JVM til kontekstskifte tråde. Når tråden venter på en handling, kan JVM gøre denne tråd blokeret ved hjælp af parkere() metode fra Usikker klasse.

Det ligner meget på Object.wait () metode, men det kalder den oprindelige OS-kode og drager dermed fordel af nogle arkitekturspecifikationer for at få den bedste ydeevne.

Når tråden er blokeret og skal gøres kører igen, bruger JVM unpark () metode. Vi ser ofte disse metodeopkald i tråddumps, især i de applikationer, der bruger trådpuljer.

9. Konklusion

I denne artikel så vi på Usikker klasse og dens mest nyttige konstruktioner.

Vi så, hvordan man får adgang til private felter, hvordan man tildeler hukommelse uden for bunken, og hvordan man bruger sammenligning-og-swap-konstruktionen til at implementere låsefrie algoritmer.

Implementeringen af ​​alle disse eksempler og kodestykker kan findes på GitHub - dette er et Maven-projekt, så det skal være let at importere og køre som det er.