Pourquoi sun.misc.Unsafe existe-t-il et comment peut-il être utilisé dans le monde réel? [fermé]

267

Je suis tombé sur le paquet sun.misc.Unsafe l'autre jour et j'ai été étonné de ce qu'il pouvait faire.

Bien sûr, la classe n'est pas documentée, mais je me demandais s'il y avait jamais une bonne raison de l'utiliser. Quels scénarios pourraient survenir où vous auriez besoin de l'utiliser? Comment pourrait-il être utilisé dans un scénario réel?

De plus, si vous le faites besoin, ce que cela n'indique que quelque chose est probablement mal avec votre conception?

Pourquoi Java inclut-il même cette classe?

pdeva
la source
7
Les développeurs JDK examinent actuellement cette API pour une éventuelle transformation en API publique en Java 9. Si vous l'utilisez, cela vaut la peine de prendre 5 minutes pour remplir le sondage: surveymonkey.com/s/sun-misc-Unsafe .
Andy Lynch
2
Ce message est en cours de discussion sur meta: meta.stackoverflow.com/questions/299139/…
Jon Clements

Réponses:

159

exemples

  1. «Intrinsification» des VM. c'est-à-dire CAS (Compare-And-Swap) utilisé dans les tables de hachage sans verrouillage, par exemple: sun.misc.Unsafe.compareAndSwapInt, il peut effectuer de vrais appels JNI en code natif qui contient des instructions spéciales pour CAS

    en savoir plus sur CAS ici http://en.wikipedia.org/wiki/Compare-and-swap

  2. La fonctionnalité sun.misc.Unsafe de la machine virtuelle hôte peut être utilisée pour allouer des objets non initialisés, puis interpréter l'appel du constructeur comme tout autre appel de méthode.

  3. On peut suivre les données de l'adresse native.Il est possible de récupérer l'adresse mémoire d'un objet en utilisant la classe java.lang.Unsafe, et d'opérer sur ses champs directement via des méthodes get / put dangereuses!

  4. Compilation des optimisations de temps pour JVM. VM haute performance utilisant la «magie», nécessitant des opérations de bas niveau. par exemple: http://en.wikipedia.org/wiki/Jikes_RVM

  5. Allocation de mémoire, sun.misc.Unsafe.allocateMemory, par exemple: - Le constructeur DirectByteBuffer l'appelle en interne lorsque ByteBuffer.allocateDirect est appelé

  6. Traçage de la pile d'appels et relecture avec des valeurs instanciées par sun.misc.Unsafe, utiles pour l'instrumentation

  7. sun.misc.Unsafe.arrayBaseOffset et arrayIndexScale peuvent être utilisés pour développer des arraylets, une technique pour décomposer efficacement les grands tableaux en objets plus petits afin de limiter le coût en temps réel des opérations de scan, de mise à jour ou de déplacement sur les gros objets

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

plus sur les références ici - http://bytescrolls.blogspot.com/2011/04/interesting-uses-of-sunmiscunsafe.html

zudokod
la source
1
si vous obtenez l'adresse d'un champ en utilisant Unsafe, elle peut toujours être modifiée par le GC, donc cette opération n'est-elle pas assez inutile?
pdeva
obtenir l'adresse de ceux que vous avez attribués
zudokod
que voulez-vous dire exactement par celui que j'ai attribué. cela semble être utilisé dans des endroits où des objets ont été créés à l'aide de l'opérateur «nouveau», d'où ma question.
pdeva
1
unsafe.allocateMemory et mettez la valeur
zudokod
1
Concernant le point 2, j'aimerais savoir comment invoquer le constructeur comme tout autre appel de méthode? Parce que je n'ai trouvé aucun moyen de le faire, sauf dans les bytecodes.
Miguel Gamboa
31

Juste en exécutant une recherche dans un moteur de recherche de code, j'obtiens les exemples suivants:

  • Java Object Notation - utilisez-le pour un traitement plus efficace des tableaux, en citant le javadoc

Classe simple pour obtenir l'accès à l'objet {@link Unsafe}. {@link Unsafe} * est requis pour permettre des opérations CAS efficaces sur les tableaux. Notez que les versions de {@link java.util.concurrent.atomic}, telles que {@link java.util.concurrent.atomic.AtomicLongArray}, nécessitent des garanties de commande de mémoire supplémentaires qui ne sont généralement pas nécessaires dans ces algorithmes et sont également coûteuses. sur la plupart des processeurs.

/ ** Classe de base pour les FieldAccessors basés sur sun.misc.Unsafe pour les champs statiques. L'observation est qu'il n'y a que neuf types de champs du point de vue du code de réflexion: les huit types primitifs et Object. L'utilisation de la classe Unsafe au lieu des bytecodes générés économise de la mémoire et du temps de chargement pour les FieldAccessors générés dynamiquement. * /

  • SpikeSource

/ * FinalFields qui sont envoyés à travers le fil .. comment dé-marshaller et recréer l'objet côté réception? Nous ne voulons pas invoquer le constructeur car il établirait des valeurs pour les champs finaux. Nous devons recréer le champ final exactement comme il était du côté expéditeur. Le sun.misc.Unsafe le fait pour nous. * /

Il existe de nombreux autres exemples, suivez simplement le lien ci-dessus ...

Asaf
la source
25

Intéressant, je n'avais même jamais entendu parler de ce cours (ce qui est probablement une bonne chose, vraiment).

Une chose qui me vient à l'esprit est d'utiliser Unsafe # setMemory pour mettre à zéro les tampons qui contenaient des informations sensibles à un moment donné (mots de passe, clés, ...). Vous pouvez même le faire pour des champs d'objets "immuables" (là encore, je suppose que la simple réflexion ancienne pourrait faire l'affaire ici aussi). Je ne suis pas un expert en sécurité, alors prenez ceci avec un grain de sel.

Mike Daniels
la source
4
I'd never even heard of this class... Je vous en ai parlé tant de fois! soupir + :(
Tim Bender
7
Cela ne servirait à rien, car Java utilise un ramasse-miettes générationnel de copie et vos informations sensibles seront probablement déjà situées ailleurs dans la mémoire «libre» en attente d'être écrasées.
Daniel Cassidy
39
Je n'en ai jamais entendu parler non plus, mais j'adore leur park()documentation: "Bloquer le thread actuel, qui revient lorsqu'un déséquilibrage se produit, ou qu'un déséquilibrage a déjà eu lieu, ou le thread est interrompu, ou, s'il n'est pas absolu et que l'heure n'est pas nulle, le étant donné que les nanosecondes se sont écoulées, ou si elles sont absolues, le délai donné en millisecondes depuis que Epoch s'est écoulé, ou de façon fallacieuse (c'est-à-dire qu'il revient sans aucune "raison") ". Presque aussi bon que "la mémoire est libérée à la fin du programme ou, à des intervalles aléatoires, selon la première éventualité".
2011
1
@Daniel, intéressant, je n'y avais pas pensé. Vous pouvez maintenant voir pourquoi je ne suis pas un expert en sécurité. :)
Mike Daniels
22

Sur la base d'une analyse très brève de la bibliothèque Java 1.6.12 utilisant eclipse pour le traçage de référence, il semble que chaque fonctionnalité utile de Unsafesoit exposée de manière utile.

Les opérations CAS sont exposées via les classes Atomic *. Les fonctions de manipulation de la mémoire sont exposées via les instructions DirectByteBuffer Sync (parquer, retirer) sont exposées par le biais de AbstractQueuedSynchronizer qui à son tour est utilisé par les implémentations de verrouillage.

Tim Bender
la source
Les AtomicXXXUpdaters sont trop lents et quand vous en avez vraiment besoin: CAS - vous ne pouvez pas vous permettre de les utiliser réellement. Si vous allez faire le métal, vous n'utiliserez pas les niveaux d'abstraction et de nombreux contrôles. L'échec du CAS est un mauvais dans une boucle esp. lorsque le matériel décide de mal interpréter la branche (en raison de conflits élevés), mais avoir quelques autres comparaisons / branches fait mal. Park / Unpark sont exposés à travers LockSupportnon AQS (ce dernier est plus une implication de verrouillage que park /
unpark
21

Unsafe.throwException - permet de lever l' exception vérifiée sans les déclarer.

Ceci est utile dans certains cas où vous traitez avec la réflexion ou l'AOP.

Supposons que vous créez un proxy générique pour une interface définie par l'utilisateur. Et l'utilisateur peut spécifier quelle exception est levée par l'implémentation dans un cas spécial simplement en déclarant l'exception dans l'interface. Ensuite, c'est le seul moyen que je connaisse, pour lever une exception vérifiée dans la mise en œuvre dynamique de l'interface.

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
la source
14
vous pouvez faire la même chose Thread.stop(Throwable)sans besoin d'insécurité, dans le même thread, vous pouvez lancer n'importe quoi de toute façon (il n'y a pas de vérification de compilation)
bestsss
Vous pouvez le faire uniquement via le bytecode (ou utilisez Lomboc pour le faire pour vous)
Antimony
1
@bestsss Cette méthode a été tronquée et jette un UnsupportedOperationExceptiondans le thread actuel à partir de Java 8. Cependant, la version sans argument qui lève ThreadDeathfonctionne toujours.
gparyani
@damryfbfnetsi, je n'ai pas suivi les discussions jdk de base depuis un certain temps et je n'ai pas l'intention de passer à java 8. Pourtant, c'est une idée assez déroutante car il est trivial d'être implémenté par la génération de bytecode de toute façon, sauf si maintenant le vérificateur vérifie réellement s'il déclare les objets à lancer ... mais cela pourrait être incompatible en amont car les métadonnées sur l'exception levée pouvaient être supprimées.
bestsss
10

Classe dangereuse

Collection de méthodes pour effectuer des opérations de bas niveau et non sécurisées. Bien que la classe et toutes les méthodes soient publiques, l'utilisation de cette classe est limitée car seul le code approuvé peut en obtenir des instances.

Une utilisation de celui-ci est dans les java.util.concurrent.atomicclasses:

Margus
la source
6

Pour une copie mémoire efficace (plus rapide à copier que System.arraycopy () pour les blocs courts au moins); tel qu'utilisé par les codecs Java LZF et Snappy . Ils utilisent «getLong» et «putLong», qui sont plus rapides que de faire des copies octet par octet; particulièrement efficace lors de la copie d'éléments tels que des blocs 16/32/64 octets.

StaxMan
la source
1
Doh, arraycopy utilise des boucles SSE sur x86-64 qui sont meilleures que getLong/putLong(et vous devez également calculer l'adresse)
bestsss
L'avez-vous réellement mesuré? Pour les blocs plus courts, je vois des performances toujours meilleures sur x86-64 lorsque j'utilise la combinaison de getLong/ putLong: idéalement, je préférerais la System.arraycopy()simplicité et tout; mais les tests réels ont montré le contraire pour les cas que j'ai testés.
StaxMan
oui en utilisant dangereux je ne pouvais aucune performance significative hors de dégonfler impl. Pour plusieurs octets, des copies longues sur de grands tableaux get / putLong peuvent fonctionner en effet lorsque le compilateur doit vérifier les longueurs. Certains impl. ajouter une clôture de mémoire après System.arrayCopy (peut être désactivé / activé cependant), de sorte que cela pourrait être le vrai coupable.
bestsss
D'accord. Il est possible que les nouveaux JDK aient changé cela; à l'origine, lorsque j'ai observé un fonctionnement plus rapide (avec JDK 1.6), j'ai également été surpris. Ou peut-être que j'oublie une différence d'utilisation spécifique. Ce sont des optimisations délicates (et éventuellement instables), même lorsqu'elles fonctionnent, et il est essentiel de mesurer les effets.
StaxMan
5

Je travaillais récemment sur la réimplémentation de la JVM et j'ai constaté qu'un nombre surprenant de classes sont implémentées en termes de Unsafe. La classe est principalement conçue pour les implémenteurs de bibliothèques Java et contient des fonctionnalités qui sont fondamentalement dangereuses mais nécessaires à la construction de primitives rapides. Par exemple, il existe des méthodes pour obtenir et écrire des décalages de champs bruts, en utilisant la synchronisation au niveau matériel, allouer et libérer de la mémoire, etc. Elle n'est pas destinée à être utilisée par les programmeurs Java normaux; il est non documenté, spécifique à l'implémentation et intrinsèquement dangereux (d'où le nom!). De plus, je pense que la SecurityManagervolonté d'interdire l'accès à celui-ci dans presque tous les cas.

En bref, il existe principalement pour permettre aux implémenteurs de bibliothèque d'accéder à la machine sous-jacente sans avoir à déclarer chaque méthode dans certaines classes comme AtomicIntegernative. Vous ne devriez pas avoir besoin de l'utiliser ou de vous en soucier dans la programmation Java de routine, car le but est de rendre le reste des bibliothèques assez rapide pour que vous n'ayez pas besoin de ce type d'accès.

templatetypedef
la source
en fait, le SecurityManager ne permet d'y accéder que si la réflexion est désactivée
amara
@ sparkleshy- Pouvez-vous nous en dire plus?
templatetypedef
tout en obtenant une instance de getUnsafe a des exigences plutôt strictes, Unsafe.class.getDeclaredField("theUnsafe")avec .setAccessible(true)et .get(null)l'obtiendra aussi
amara
@ sparkleshy- Je suis surpris que cela fonctionne - le responsable de la sécurité devrait signaler cela.
templatetypedef
5

Utilisez-le pour accéder et allouer efficacement de grandes quantités de mémoire, comme dans votre propre moteur voxel! (c'est-à-dire un jeu de style Minecraft.)

D'après mon expérience, la JVM est souvent incapable d'éliminer la vérification des limites là où vous en avez vraiment besoin. Par exemple, si vous parcourez un grand tableau, mais que l'accès à la mémoire réel est caché sous un appel de méthode non virtuel * dans la boucle, la JVM peut toujours effectuer une vérification des limites avec chaque accès au tableau, plutôt qu'une seule fois juste avant la boucle. Ainsi, pour des gains de performances potentiellement importants, vous pouvez éliminer la vérification des limites JVM à l'intérieur de la boucle via une méthode qui utilise sun.misc.Unsafe pour accéder directement à la mémoire, en vous assurant d'effectuer vous-même les limites aux bons endroits. (Vous êtes Gonna bornes vérifier à un certain niveau, non?)
* par non virtuel, je veux dire que la JVM ne devrait pas avoir à résoudre dynamiquement quelle que soit votre méthode particulière, car vous avez correctement garanti que la classe / méthode / instance est une combinaison de statique / final / ce que vous avez.

Pour mon moteur de voxel maison, cela a entraîné un gain de performances spectaculaire lors de la génération et de la sérialisation des blocs (aux endroits où je lisais / écrivais sur l'ensemble de la baie à la fois). Les résultats peuvent varier, mais si le manque d'élimination des limites est votre problème, cela le corrigera.

Il y a des problèmes potentiellement majeurs avec cela: en particulier, lorsque vous fournissez la possibilité d'accéder à la mémoire sans vérification des limites aux clients de votre interface, ils en abuseront probablement. (N'oubliez pas que les pirates peuvent également être des clients de votre interface ... en particulier dans le cas d'un moteur voxel écrit en Java.) Ainsi, vous devez soit concevoir votre interface de manière à ce que l'accès à la mémoire ne puisse pas être abusé, ou vous devez être extrêmement prudent pour valider les données utilisateur avant qu'elles ne puissent jamais, jamais se mélanger avec votre interface dangereuse. Compte tenu des choses catastrophiques qu'un pirate peut faire avec un accès mémoire non contrôlé, il est probablement préférable d'adopter les deux approches.

Philip Guin
la source
4

Les collections hors segment peuvent être utiles pour allouer d'énormes quantités de mémoire et la désallouer immédiatement après utilisation sans interférence GC. J'ai écrit une bibliothèque pour travailler avec des tableaux / listes hors tas basés sur sun.misc.Unsafe.

alexkasko
la source
4

Nous avons implémenté d'énormes collections comme des tableaux, des HashMaps, des TreeMaps en utilisant Unsafe.
Et pour éviter / minimiser la fragmentation, nous avons implémenté l'allocateur de mémoire en utilisant les concepts de dlmalloc sur dangereux.
Cela nous a aidés à gagner en performance en simultanéité.

pradipmw
la source
3

Unsafe.park()et Unsafe.unpark()pour la construction de structures de contrôle de simultanéité personnalisées et de mécanismes de planification coopératifs.

andersoj
la source
24
disponibles publiquementjava.util.concurrent.locks.LockSupport
bestsss
1

Je ne l'ai pas utilisé moi-même, mais je suppose que si vous avez une variable qui n'est lue qu'occasionnellement par plusieurs threads (donc vous ne voulez pas vraiment la rendre volatile), vous pouvez utiliser le putObjectVolatilelorsque vous l'écrivez dans le thread principal et readObjectVolatilelors de la lecture des rares autres threads.

Matt Crinklaw-Vogt
la source
1
mais selon la discussion sur le fil ci-dessous, les volatils non contenus sont presque aussi rapides que les non-volatiles de toute façon stackoverflow.com/questions/5573782/…
pdeva
vous ne pouvez pas remplacer la sémantique volatile par des écritures simples et des lectures volatiles ... c'est une recette pour un désastre car cela peut fonctionner dans un cadre mais pas dans un autre. Si vous cherchez à avoir une sémantique volatile avec un seul thread d'écriture, vous pouvez utiliser AtomicReference.lazySet sur le thread d'écriture et obtenir () sur les lecteurs (voir cet article pour une discussion sur le sujet). Les lectures volatiles sont relativement bon marché, mais pas gratuites, voir ici .
Nitsan Wakart
"... vous pouvez utiliser putObjectVolatile lors de l'écriture ..." Je ne proposais pas d'écritures simples.
Matt Crinklaw-Vogt
1

Vous en avez besoin si vous devez remplacer les fonctionnalités fournies par l'une des classes qui l'utilisent actuellement.

Cela peut être une sérialisation / désérialisation personnalisée / plus rapide / plus compacte, une version plus rapide / plus grande du tampon / redimensionnable de ByteBuffer, ou l'ajout d'une variable atomique, par exemple une variable non prise en charge actuellement.

Je l'ai utilisé pour tout cela à un moment donné.

Peter Lawrey
la source
0

L'objet semble être la disponibilité pour fonctionner à un niveau inférieur à ce que le code Java permet généralement. Si vous codez une application de haut niveau, la JVM résume la gestion de la mémoire et d'autres opérations à l'écart du niveau de code, ce qui facilite la programmation. En utilisant la bibliothèque Unsafe, vous effectuez efficacement des opérations de bas niveau qui seraient généralement effectuées pour vous.

Comme l'a dit woliveirajr, "random ()" utilise Unsafe pour amorcer tout comme de nombreuses autres opérations utiliseront la fonction allocateMemory () incluse dans Unsafe.

En tant que programmeur, vous pourriez probablement vous en tirer sans avoir besoin de cette bibliothèque, mais avoir un contrôle strict sur les éléments de bas niveau est très pratique (c'est pourquoi il y a toujours de l'assembly et (dans une moindre mesure) du code C qui dérive dans les principaux produits)

Grambot
la source