Veiledning til sun.misc. Usikker

1. Oversikt

I denne artikkelen ser vi på en fascinerende klasse levert av JRE - Usikre fra sol. diverse pakke. Denne klassen gir oss mekanismer på lavt nivå som ble designet for kun å brukes av Java-biblioteket og ikke av standardbrukere.

Dette gir oss mekanismer på lavt nivå som primært er designet for intern bruk i kjernebibliotekene.

2. Få et tilfelle av Usikre

For det første å kunne bruke Usikre klasse, må vi få en forekomst - som ikke er greit med tanke på at klassen bare var designet for intern bruk.

Måten å oppnå forekomsten på er via den statiske metoden getUnsafe (). Advarselen er at som standard - dette vil kaste a SecurityException.

Heldigvis, vi kan skaffe forekomsten ved hjelp av refleksjon:

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

3. Instansere en klasse ved hjelp av Usikre

La oss si at vi har en enkel klasse med en konstruktør som setter en variabelverdi når objektet blir opprettet:

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

Når vi initialiserer det objektet ved hjelp av konstruktøren, blir få en() metoden vil returnere verdien 1:

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

Men vi kan bruke allocateInstance () metoden bruker Usikre. Det vil bare tildele minnet til klassen vår, og vil ikke påkalle en konstruktør:

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

Legg merke til at konstruktøren ikke ble påkalt, og på grunn av det faktum, få en() metoden returnerte standardverdien for lang type - som er 0.

4. Endring av private felt

La oss si at vi har en klasse som har en hemmelig privat verdi:

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

Bruker putInt () metode fra Usikre, vi kan endre en verdi av det private SECRET_VALUE felt, endre / ødelegge tilstanden til den forekomsten:

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

Når vi får et felt ved refleksjonssamtalen, kan vi endre verdien til andre int verdi ved hjelp av Usikre.

5. Kast et unntak

Koden som påkalles via Usikre blir ikke undersøkt på samme måte av kompilatoren som vanlig Java-kode. Vi kan bruke throwException () metode for å kaste noe unntak uten å begrense innringer til å håndtere dette unntaket, selv om det er et avkrysset unntak:

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

Etter å ha kastet en IO unntak, som er sjekket, trenger vi ikke å fange den eller spesifisere den i metodedeklarasjonen.

6. Off-Heap-minne

Hvis et program går tom for tilgjengelig minne på JVM, kan vi ende opp med å tvinge GC-prosessen til å kjøre for ofte. Ideelt sett ønsker vi en spesiell hukommelsesregion, utenfor heapen og ikke kontrollert av GC-prosessen.

De allocateMemory () metoden fra Usikre klasse gir oss muligheten til å tildele store objekter fra haugen, noe som betyr at dette minnet vil ikke bli sett og tatt i betraktning av GC og JVM.

Dette kan være veldig nyttig, men vi må huske at dette minnet må styres manuelt og skikkelig gjenvinnes med freeMemory () når det ikke lenger er behov for det.

La oss si at vi ønsker å opprette det store minnesystemet utenfor heapen med byte. Vi kan bruke allocateMemory () metode for å oppnå det:

klasse OffHeapArray {private final static int BYTE = 1; privat lang størrelse; privat lang adresse; offentlig OffHeapArray (lang størrelse) kaster NoSuchFieldException, IllegalAccessException {this.size = size; adresse = getUnsafe (). allocateMemory (størrelse * BYTE); } private Usikre getUnsafe () kaster IllegalAccessException, NoSuchFieldException {Field f = Unsafe.class.getDeclaredField ("theUnsafe"); f.setAccessible (true); return (Usikker) f.get (null); } public void set (long i, byte value) kaster NoSuchFieldException, IllegalAccessException {getUnsafe (). putByte (adresse + i * BYTE, verdi); } public int get (long idx) kaster NoSuchFieldException, IllegalAccessException {return getUnsafe (). getByte (adresse + idx * BYTE); } offentlig lang størrelse () {retur størrelse; } public void freeMemory () kaster NoSuchFieldException, IllegalAccessException {getUnsafe (). freeMemory (adresse); }
}

I konstruktøren av OffHeapArray, vi initialiserer matrisen som er gitt størrelse. Vi lagrer startadressen til matrisen i adresse felt. De sett() metoden er å ta indeksen og den gitte verdi som vil bli lagret i matrisen. De få() metoden er å hente byteverdien ved hjelp av indeksen som er en forskyvning fra startadressen til matrisen.

Deretter kan vi tildele det off-heap-arrayet ved hjelp av konstruktøren:

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

Vi kan sette N antall byteverdier i denne matrisen og deretter hente disse verdiene og oppsummere dem for å teste om adresseringen vår fungerer riktig:

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);

Til slutt må vi frigjøre minnet til operativsystemet ved å ringe freeMemory ().

7. CompareAndSwap Operasjon

De veldig effektive konstruksjonene fra java.concurrent pakke, som AtomicInteger, bruker CompareAndSwap () metoder ut av Usikre under for å gi best mulig ytelse. Denne konstruksjonen er mye brukt i de låsfrie algoritmene som kan utnytte CAS-prosessorinstruksjonen for å gi god hastighet sammenlignet med standard pessimistisk synkroniseringsmekanisme i Java.

Vi kan konstruere den CAS-baserte telleren ved hjelp av sammenlignAndSwapLong () metode fra Usikre:

klasse CASCounter {private Usikre usikre; privat flyktig lang teller = 0; privat lang offset; private Unsafe getUnsafe () kaster IllegalAccessException, NoSuchFieldException {Field f = Unsafe.class.getDeclaredField ("theUnsafe"); f.setAccessible (true); return (Usikker) f.get (null); } offentlig CASCounter () kaster unntak {usikker = getUnsafe (); offset = unsafe.objectFieldOffset (CASCounter.class.getDeclaredField ("counter")); } offentlig ugyldig økning () {lenge før = teller; while (! unsafe.compareAndSwapLong (this, offset, before, before + 1)) {before = counter; }} offentlig lang getCounter () {retur teller; }}

I CASCounter konstruktør vi får adressen til tellerfeltet, for å kunne bruke den senere i økning () metode. Det feltet må erklæres som flyktig, for å være synlig for alle tråder som skriver og leser denne verdien. Vi bruker objectFieldOffset () metode for å få minneadressen til forskyvning felt.

Den viktigste delen av denne klassen er økning () metode. Vi bruker sammenlignAndSwapLong () i samtidig som loop for å øke tidligere hentet verdi, og sjekke om den forrige verdien endret seg siden vi hentet den.

Hvis det gjorde det, prøver vi den operasjonen på nytt til vi lykkes. Det er ingen blokkering her, og det er derfor dette kalles en låsfri algoritme.

Vi kan teste koden vår ved å øke den delte telleren fra flere tråder:

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 å hevde at tilstanden til telleren er riktig, kan vi få motverdien fra den:

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

8. Parker / avparker

Det er to fascinerende metoder i Usikre API som brukes av JVM for å kontekstbytte tråder. Når tråden venter på noe, kan JVM gjøre denne tråden blokkert ved å bruke parkere() metoden fra Usikre klasse.

Det ligner veldig på Object.wait () metoden, men den kaller den opprinnelige OS-koden, og utnytter dermed noen arkitekturspesifikasjoner for å få best ytelse.

Når tråden er blokkert og må gjøres kjørbar igjen, bruker JVM avparkere () metode. Vi vil ofte se disse metodeanropene i tråddumper, spesielt i applikasjonene som bruker trådbassenger.

9. Konklusjon

I denne artikkelen så vi på Usikre klasse og dens mest nyttige konstruksjoner.

Vi så hvordan man får tilgang til private felt, hvordan man tildeler hukommelse utenfor heapen, og hvordan man bruker sammenligne-og-bytte-konstruksjonen til å implementere låsfrie algoritmer.

Implementeringen av alle disse eksemplene og kodebitene finner du på GitHub - dette er et Maven-prosjekt, så det skal være enkelt å importere og kjøre som det er.