Dlaczego istnieje sun.misc.Unsafe i jak można go używać w prawdziwym świecie? [Zamknięte]

267

Pewnego dnia natknąłem się na pakiet sun.misc.Unsafe i byłem zaskoczony tym, co może zrobić.

Oczywiście klasa nie jest udokumentowana, ale zastanawiałem się, czy kiedykolwiek był dobry powód, aby z niej korzystać. Jakie scenariusze mogą się pojawić, gdzie będziesz musiał z niego skorzystać? Jak można go wykorzystać w scenariuszu w świecie rzeczywistym?

Co więcej, jeśli go potrzebujesz, czy nie oznacza to, że coś jest prawdopodobnie nie tak z twoim projektem?

Dlaczego Java w ogóle obejmuje tę klasę?

pdeva
źródło
7
Deweloperzy JDK sprawdzają obecnie ten interfejs API pod kątem możliwej transformacji na publiczny interfejs API w Javie 9. Jeśli go użyjesz, wypełnienie ankiety zajmuje 5 minut: surveyymonkey.com/s/sun-misc-Unsafe .
Andy Lynch
2
Ten post jest omawiany na stronie meta: meta.stackoverflow.com/questions/299139/...
Jon Clements

Odpowiedzi:

159

przykłady

  1. „Intrinsification” maszyny wirtualnej. tj. CAS (porównaj i zamień) używany w tablicach skrótu bez blokady, np .: sun.misc.Unsafe.compareAndSwapInt może tworzyć prawdziwe wywołania JNI w natywnym kodzie zawierającym specjalne instrukcje dla CAS

    czytaj więcej o CAS tutaj http://en.wikipedia.org/wiki/Compare-and-swap

  2. Funkcji sun.misc.Unsafe maszyny wirtualnej hosta można użyć do przydzielenia niezainicjowanych obiektów, a następnie zinterpretować wywołanie konstruktora jako każde inne wywołanie metody.

  3. Można śledzić dane z adresu natywnego. Możliwe jest odzyskanie adresu pamięci obiektu za pomocą klasy java.lang.Unsafe i operowanie na jego polach bezpośrednio za pomocą niebezpiecznych metod get / put!

  4. Kompiluj optymalizacje czasu dla JVM. Wysoka wydajność maszyny wirtualnej wykorzystującej „magię”, wymagającą operacji na niskim poziomie. np .: http://en.wikipedia.org/wiki/Jikes_RVM

  5. Przydzielanie pamięci, sun.misc.Unsafe.allocateMemory np .: - Konstruktor DirectByteBuffer wywołuje ją wewnętrznie po wywołaniu ByteBuffer.allocateDirect

  6. Śledzenie stosu wywołań i odtwarzanie z wartościami utworzonymi przez sun.misc.Unsafe, przydatne do instrumentacji

  7. sun.misc.Unsafe.arrayBaseOffset i arrayIndexScale można wykorzystać do opracowania tablic, techniki skutecznego dzielenia dużych tablic na mniejsze obiekty w celu ograniczenia kosztów skanowania, aktualizacji lub przenoszenia dużych obiektów w czasie rzeczywistym

  8. http://robaustin.wikidot.com/how-to-write-to-direct-memory-locations-in-java

więcej informacji na temat referencji tutaj - http://bytescrolls.blogspot.com/2011/04/interesting-uses-of-sunmiscunsafe.html

zudokod
źródło
1
jeśli otrzymasz adres pola za pomocą Niebezpiecznego, GC zawsze może go zmienić, więc czy ta operacja nie jest całkiem bezużyteczna?
pdeva
uzyskaj adres dla tych, które przydzieliłeś
zudokod
co dokładnie masz na myśli przez ten , który przeznaczyłem. wydaje się, że jest to stosowane w miejscach, w których obiekty zostały utworzone za pomocą operatora „new”, więc moje pytanie.
pdeva
1
unsafe.allocateMemory i podaj wartość
zudokod
1
Jeśli chodzi o punkt 2, chciałbym wiedzieć, jak można wywołać konstruktor, jak każde inne wywołanie metody? Ponieważ nie znalazłem żadnego sposobu, aby to zrobić, chyba że w bajtecodes.
Miguel Gamboa
31

Po uruchomieniu wyszukiwania w jakiejś wyszukiwarce kodu otrzymuję następujące przykłady:

Prosta klasa pozwalająca uzyskać dostęp do obiektu {@link Unsafe}. {@link Unsafe} * jest wymagany, aby umożliwić wydajne operacje CAS na tablicach. Pamiętaj, że wersje w {@link java.util.concurrent.atomic}, takie jak {@link java.util.concurrent.atomic.AtomicLongArray}, wymagają dodatkowych gwarancji porządkowania pamięci, które na ogół nie są potrzebne w tych algorytmach i są również drogie na większości procesorów.

  • SoyLatte - java 6 dla fragmentu osx javadoc

/ ** Klasa bazowa dla modułów FieldAccessors opartych na sun.misc.Unsafe dla pól statycznych. Obserwacja jest taka, że ​​istnieje tylko dziewięć rodzajów pól z punktu widzenia kodu odbicia: osiem typów pierwotnych i Obiekt. Korzystanie z klasy Niebezpieczne zamiast generowanych kodów bajtowych oszczędza pamięć i czas ładowania dynamicznie generowanych FieldAccessors. * /

  • SpikeSource

/ * FinalFields, które są przesyłane przez drut .. jak odznaczyć i odtworzyć obiekt po stronie odbierającej? Nie chcemy wywoływać konstruktora, ponieważ ustalałby wartości dla pól końcowych. Musimy odtworzyć ostatnie pole dokładnie tak, jak było po stronie nadawcy. Sun.misc.Unsafe robi to za nas. * /

Istnieje wiele innych przykładów, wystarczy kliknąć powyższy link ...

Asaf
źródło
25

Co ciekawe, nigdy nawet nie słyszałem o tej klasie (co jest prawdopodobnie dobrą rzeczą, naprawdę).

Jedną z rzeczy, które przychodzą mi na myśl, jest użycie Niebezpiecznego # setMemory do zerowania buforów zawierających poufne informacje w jednym punkcie (hasła, klucze, ...). Mógłbyś nawet zrobić to z polami „niezmiennych” obiektów (i znów przypuszczam, że zwykłe stare odbicie również może załatwić sprawę). Nie jestem ekspertem od bezpieczeństwa, więc weź to z odrobiną soli.

Mike Daniels
źródło
4
I'd never even heard of this class... Mówiłem ci o tym wiele razy! westchnienie + :(
Tim Bender
7
Nie byłoby sensu, ponieważ Java używa generatora śmieciowego pokolenia, a Twoje poufne informacje prawdopodobnie już znajdują się gdzieś indziej w „wolnej” pamięci, czekając na zastąpienie.
Daniel Cassidy,
39
Nigdy o tym nie słyszałem, ale uwielbiam ich park()dokumentację: „Zablokuj bieżący wątek, powracając, gdy nastąpi równoważenie unparkowania równoważącego lub już wystąpił równoważenie unparkingu, lub wątek jest przerywany lub, jeśli nie jest absolutny, a czas nie jest równy zero, w określonym czasie minęły nanosekundy, a jeśli są bezwzględne, podany termin w milisekundach od epoki minął lub nieoczekiwanie (tzn. wraca bez żadnego „powodu”) ”. Prawie tak dobre, jak „pamięć jest zwalniana po wyjściu z programu lub, w losowych odstępach, w zależności od tego, co nastąpi wcześniej”.
aroth
1
@Daniel, ciekawe, nie zastanawiałem się nad tym. Teraz możesz zobaczyć, dlaczego nie jestem ekspertem od bezpieczeństwa. :)
Mike Daniels
22

Opierając się na bardzo krótkiej analizie biblioteki Java 1.6.12 wykorzystującej eclipse do śledzenia referencji, wydaje się, że każda przydatna funkcjonalność Unsafejest ujawniona w użyteczny sposób.

Operacje CAS są udostępniane za pośrednictwem klas Atomic *. Funkcje manipulowania pamięcią są ujawniane przez instrukcje DirectByteBuffer Sync (parkowanie, unparkowanie) są ujawniane przez AbstractQueuedSynchronizer, który z kolei jest wykorzystywany przez implementacje Lock.

Tim Bender
źródło
AtomicXXXUpdaters są zbyt wolne i kiedy naprawdę ich potrzebujesz: CAS - nie możesz sobie pozwolić na ich użycie. Jeśli zamierzasz wykonać metal, nie będziesz używać poziomów abstrakcji i licznych testów. Niepowodzenie CAS jest złe w pętli esp. kiedy sprzęt decyduje się na nieprzewidywalne rozgałęzienie (z powodu dużej rywalizacji), ale niewiele innych porównań / gałęzi tylko boli. Park / Unpark są narażone przez LockSupportAQS (ten ostatni jest bardziej domniemany niż parkowanie / unpark)
bestsss
21

Unsafe.throwException - pozwala na rzucenie sprawdzonego wyjątku bez deklarowania go.

Jest to przydatne w niektórych przypadkach, gdy masz do czynienia z refleksją lub AOP.

Załóżmy, że zbudujesz ogólny serwer proxy dla interfejsu zdefiniowanego przez użytkownika. A użytkownik może określić, który wyjątek jest zgłaszany przez zastosowanie w specjalnym przypadku, po prostu deklarując wyjątek w interfejsie. To jest jedyny sposób, w jaki wiem, aby zgłosić sprawdzony wyjątek w dynamicznej implementacji interfejsu.

import org.junit.Test;
/** need to allow forbidden references! */ import sun.misc.Unsafe;

/**
 * Demonstrate how to throw an undeclared checked exception.
 * This is a hack, because it uses the forbidden Class {@link sun.misc.Unsafe}.
 */
public class ExceptionTest {

    /**
     * A checked exception.
     */
    public static class MyException extends Exception {
        private static final long serialVersionUID = 5960664994726581924L;
    }

    /**
     * Throw the Exception.
     */
    @SuppressWarnings("restriction")
    public static void throwUndeclared() {
        getUnsafe().throwException(new MyException());
    }

    /**
     * Return an instance of {@link sun.misc.Unsafe}.
     * @return THE instance
     */
    @SuppressWarnings("restriction")
    private static Unsafe getUnsafe() {
        try {

            Field singleoneInstanceField = Unsafe.class.getDeclaredField("theUnsafe");
            singleoneInstanceField.setAccessible(true);
            return (Unsafe) singleoneInstanceField.get(null);

        } catch (IllegalArgumentException e) {
            throw createExceptionForObtainingUnsafe(e);
        } catch (SecurityException e) {
            throw createExceptionForObtainingUnsafe(e);
        } catch (NoSuchFieldException e) {
            throw createExceptionForObtainingUnsafe(e);
        } catch (IllegalAccessException e) {
            throw createExceptionForObtainingUnsafe(e);
        }
    }

    private static RuntimeException createExceptionForObtainingUnsafe(final Throwable cause) {
        return new RuntimeException("error while obtaining sun.misc.Unsafe", cause);
    }


    /**
     * scenario: test that an CheckedException {@link MyException} can be thrown
     * from an method that not declare it.
     */
    @Test(expected = MyException.class)
    public void testUnsingUnsaveToThrowCheckedException() {
        throwUndeclared();
    }
}
Ralph
źródło
14
możesz zrobić to samo Thread.stop(Throwable)bez niebezpieczeństwa, w tym samym wątku i tak możesz cokolwiek rzucić (nie ma testu kompilacji)
bestsss
Możesz to zrobić wyłącznie za pomocą kodu bajtowego (lub użyj Lomboc, aby zrobić to za Ciebie)
Antimony
1
@bestsss Ta metoda została usunięta i wrzuca UnsupportedOperationExceptionw bieżący wątek od Java 8. Jednak wersja, która rzuca bez argumentów, ThreadDeathnadal działa.
gparyani
@damryfbfnetsi, od dłuższego czasu nie śledzę podstawowych dyskusji na jdk i nie planuję przejścia na java 8. Jest to jednak dość zagadkowy pomysł, ponieważ i tak generowanie kodu bajtowego jest banalne, chyba że teraz weryfikator faktycznie sprawdza, czy metoda deklaruje rzucalne elementy ... ale może to być niezgodne wstecz, ponieważ metadane dotyczące zgłoszonego wyjątku mogą zostać odrzucone.
bestsss
10

Klasa niebezpieczna

Zbiór metod wykonywania niebezpiecznych operacji na niskim poziomie. Chociaż klasa i wszystkie metody są publiczne, użycie tej klasy jest ograniczone, ponieważ tylko zaufany kod może uzyskać jej instancje.

Jednym z nich jest wykorzystanie w java.util.concurrent.atomicklasach:

Margus
źródło
6

Dla wydajnego kopiowania pamięci (szybsze kopiowanie niż System.arraycopy () przynajmniej dla krótkich bloków); używane przez kodeki Java LZF i Snappy . Używają „getLong” i „putLong”, które są szybsze niż robienie kopii bajt po bajcie; szczególnie wydajny podczas kopiowania takich bloków jak 16/32/64 bajty.

StaxMan
źródło
1
Doh, arraycopy używa pętli SSE na x86-64, które są lepsze niż getLong/putLong(i trzeba też obliczyć adres)
bestsss
Czy rzeczywiście to zmierzyłeś? W przypadku krótszych bloków widzę konsekwentnie lepszą wydajność na x86-64 przy użyciu kombinacji getLong/ putLong: najlepiej wolałbym ze względu System.arraycopy()na prostotę i wszystko; ale rzeczywiste testy wykazały inaczej w przypadku testowanych przeze mnie przypadków.
StaxMan
tak przy użyciu niebezpiecznych nie mogłem żadnej znaczącej wydajności z deflate impl. W przypadku kilku bajtów długie kopie na dużych tablicach get / putLong może rzeczywiście działać, gdy kompilator musi sprawdzać długości. Some impl. dodaj płot pamięciowy obok System.arrayCopy (można go jednak włączyć / wyłączyć), aby mógł być prawdziwym winowajcą.
bestsss
Dobrze. Możliwe, że nowsze JDK to zmieniły; pierwotnie, kiedy obserwowałem szybszą pracę (z JDK 1.6), również byłem zaskoczony. A może zapominam o konkretnej różnicy w użytkowaniu. Są to trudne (i prawdopodobnie niestabilne) optymalizacje, nawet jeśli działają, i konieczny jest pomiar efektów.
StaxMan,
5

Niedawno pracowałem nad ponownym wdrożeniem JVM i odkryłem, że pod względem liczby implementacji zaskakuje liczba klas Unsafe. Klasa jest głównie zaprojektowana dla implementatorów bibliotek Java i zawiera funkcje, które są zasadniczo niebezpieczne, ale niezbędne do budowania szybkich operacji podstawowych. Na przykład istnieją metody pobierania i zapisywania surowych przesunięć pola, korzystania z synchronizacji na poziomie sprzętowym, przydzielania i zwalniania pamięci itp. Nie jest przeznaczony do użytku przez zwykłych programistów Java; jest nieudokumentowane, specyficzne dla implementacji i z natury niebezpieczne (stąd nazwa!). Co więcej, myślę, że SecurityManageruniemożliwi to dostęp do niego w prawie wszystkich przypadkach.

Krótko mówiąc, istnieje głównie po to, aby umożliwić implementatorom bibliotek dostęp do komputera bazowego bez konieczności deklarowania każdej metody w niektórych klasach, takich jak AtomicIntegernatywna. Nie powinieneś używać go ani martwić się o to w rutynowym programowaniu w Javie, ponieważ chodzi o to, aby reszta bibliotek była wystarczająco szybka, abyś nie potrzebował takiego dostępu.

templatetypedef
źródło
w rzeczywistości SecurityManager nie zezwala na dostęp do niego tylko wtedy, gdy odbicie jest wyłączone
amara,
@ sparkleshy- Czy możesz to rozwinąć?
templatetypedef
podczas gdy uzyskanie instancji z getUnsafe ma raczej surowe wymagania, Unsafe.class.getDeclaredField("theUnsafe")z, .setAccessible(true)a potem .get(null)też ją otrzymam
amara
@ sparkleshy- Jestem zaskoczony, że to działa - menedżer bezpieczeństwa powinien to oznaczyć.
templatetypedef
5

Użyj go, aby uzyskać dostęp i wydajnie alokować duże ilości pamięci, na przykład we własnym silniku wokseli! (tj. gra w stylu Minecraft).

Z mojego doświadczenia wynika, że ​​JVM często nie jest w stanie wyeliminować sprawdzania granic w miejscu, którego naprawdę potrzebujesz. Na przykład, jeśli iterujesz po dużej tablicy, ale rzeczywisty dostęp do pamięci jest schowany pod nie-wirtualnym * wywołaniem metody w pętli, JVM może nadal sprawdzać granice przy każdym dostępie do tablicy, a nie tylko raz przed pętla. Tak więc, w celu potencjalnie dużego wzrostu wydajności, możesz wyeliminować sprawdzanie granic JVM w pętli za pomocą metody, która wykorzystuje sun.misc.Unsafe do bezpośredniego dostępu do pamięci, upewniając się, że wykonujesz sprawdzanie granic we właściwych miejscach. (Ty jesteś gonna granice sprawdzić na jakimś poziomie, prawda?)
* przez nie-wirtualny, mam na myśli, że JVM nie powinien dynamicznie rozwiązywać jakiejkolwiek konkretnej metody, ponieważ poprawnie zagwarantowałeś, że klasa / metoda / instancja są kombinacją statycznego / końcowego / what-have-you.

W przypadku mojego domowego silnika wokselowego spowodowało to dramatyczny wzrost wydajności podczas generowania porcji i serializacji (w miejscach, w których odczytywałem / zapisywałem jednocześnie całą tablicę). Wyniki mogą się różnić, ale jeśli Twoim problemem jest brak eliminacji granic, to to rozwiąże.

Występują z tym potencjalnie poważne problemy: w szczególności, gdy zapewnisz dostęp do pamięci bez sprawdzania granic klientom interfejsu, prawdopodobnie wykorzystają to. (Nie zapominaj, że hakerzy mogą być również klientami twojego interfejsu ... szczególnie w przypadku silnika wokseli napisanego w Javie.) Dlatego powinieneś albo zaprojektować swój interfejs, aby nie można było nadużywać dostępu do pamięci, lub należy być bardzo ostrożnym, aby sprawdzić poprawność danych użytkowików zanim można kiedykolwiek, kiedykolwiek mieszają się ze swoim niebezpiecznym interfejsu. Biorąc pod uwagę katastrofalne rzeczy, które haker może zrobić z niekontrolowanym dostępem do pamięci, prawdopodobnie najlepiej jest zastosować oba podejścia.

Philip Guin
źródło
4

Kolekcje zwałowe mogą być przydatne do alokacji ogromnej ilości pamięci i zwolnienia jej natychmiast po użyciu bez zakłóceń GC. Napisałem bibliotekę do pracy z tablicami / listami off-heap opartymi na sun.misc.Unsafe.

alexkasko
źródło
4

Wdrożyliśmy ogromne kolekcje, takie jak Arrays, HashMaps, TreeMaps, używając Unsafe.
Aby uniknąć / zminimalizować fragmentację, zaimplementowaliśmy alokator pamięci, używając koncepcji dlmalloc w stosunku do niebezpiecznych.
Pomogło nam to uzyskać wydajność w zakresie współbieżności.

pradipmw
źródło
3

Unsafe.park()oraz Unsafe.unpark()do budowy niestandardowych struktur kontroli współbieżności i mechanizmów planowania współpracy.

andersoj
źródło
24
publicznie dostępny jakojava.util.concurrent.locks.LockSupport
bestsss,
1

Nie korzystałem z niej osobiście, ale przypuszczam, że jeśli masz zmienną, która tylko czasami jest odczytywana przez więcej niż jeden wątek (więc tak naprawdę nie chcesz, aby była niestabilna), możesz użyć tego putObjectVolatile, pisząc ją w głównym wątku i readObjectVolatilepodczas wykonywania rzadkich odczytów z innych wątków.

Matt Crinklaw-Vogt
źródło
1
ale zgodnie z dyskusją w poniższym wątku, nieokreślone substancje lotne są prawie tak szybkie jak substancje nielotne stackoverflow.com/questions/5573782/…
pdeva
nie można zastąpić niestabilnej semantyki zwykłymi zapisami i zmiennymi odczytami ... to jest przepis na katastrofę, ponieważ może działać w jednym ustawieniu, ale nie w innym. Jeśli chcesz mieć zmienną semantykę z jednym wątkiem piszącym, możesz użyć AtomicReference.lazySet w wątku pisania i get () w czytnikach (zobacz ten post, aby omówić ten temat). Zmienne odczyty są stosunkowo tanie, ale nie za darmo, patrz tutaj .
Nitsan Wakart
„... możesz użyć putObjectVolatile podczas pisania ...” Nie sugerowałem zwykłego pisania.
Matt Crinklaw-Vogt,
1

Potrzebujesz go, jeśli chcesz zastąpić funkcjonalność zapewnianą przez jedną z klas, która go obecnie używa.

Może to być niestandardowa / szybsza / bardziej kompaktowa serializacja / deserializacja, szybszy / większy bufor / wersja ByteBuffer o zmiennym rozmiarze lub dodanie zmiennej atomowej, np. Takiej, która nie jest obecnie obsługiwana.

Kiedyś korzystałem z tego do wszystkich.

Peter Lawrey
źródło
0

Obiekt wydaje się być dostępny do pracy na niższym poziomie niż zwykle pozwala na to kod Java. Jeśli kodujesz aplikację wysokiego poziomu, JVM wyodrębnia obsługę pamięci i inne operacje z dala od poziomu kodu, aby ułatwić programowanie. Korzystając z niebezpiecznej biblioteki, skutecznie wykonujesz operacje niskiego poziomu, które zwykle byłyby dla Ciebie wykonane.

Jak stwierdził woliveirajr, „random ()” używa Unsafe do inicjowania, podobnie jak wiele innych operacji będzie używać funkcji replaceateMemory () zawartej w Unsafe.

Jako programista prawdopodobnie mógłbyś uniknąć tej biblioteki, ale ścisła kontrola nad elementami niskiego poziomu przydaje się (dlatego wciąż istnieje kod asemblera i (w mniejszym stopniu) kod C przemieszczający się w głównych produktach)

Grambot
źródło