Prefacio
La clase insegura se usa en múltiples clases del código fuente JDK. Esta clase proporciona algunas funciones subyacentes para evitar el JVM, y su implementación puede mejorar la eficiencia. Sin embargo, es una espada de doble filo: como su nombre presagió, no es seguro y la memoria que asigna debe ser libre manualmente (no reciclado por GC). La clase insegura, proporciona una alternativa simple a ciertas características de JNI: garantizar la eficiencia al tiempo que facilita las cosas.
Esta clase pertenece a la clase en el Sol.* API, y no es una parte real de J2SE, por lo que es posible que no encuentre ninguna documentación oficial, y lamentablemente tampoco tiene una mejor documentación de código.
Este artículo trata principalmente de la compilación y la traducción de los siguientes artículos.
http://mishadoff.com/blog/java-magic-part-4-sun-dot-miscdot-unsafe/
1. La mayoría de los métodos de la API insegura son implementaciones nativas, que consisten en 105 métodos, principalmente incluyendo las siguientes categorías:
(1) Información relacionada. Devuelve principalmente cierta información de memoria de bajo nivel: directionSize (), pageSize ()
(2) Objetos relacionados. Proporcione principalmente objetos y sus métodos de manipulación de dominio: asignateInstance (), ObjectFieldOffset ()
(3) Relacionado con la clase. Proporcione principalmente la clase y sus métodos de manipulación de dominio estático: staticFieldOffset (), definitivamente (), DefeSeAnyMaSclass (), EnsureClassinitialized ()
(4) matrices relacionadas. Método de manipulación de la matriz: ArrayBaseffSet (), ArrayIndexscale ()
(5) Relacionado con la sincronización. Proporcione principalmente primitivas de sincronización de bajo nivel (como primitivas CAS basadas en CPU (comparar y swap)): monitoreenter (), trymonitorenter (), monitorexit (), compareanddswapint (), putOderedInt ()
(6) Relacionado con la memoria. Método de acceso de memoria directa (omitir el montón JVM y manipular la memoria local directamente): AllocateMemory (), copyMemory (), freememory (), getAddress (), getInt (), putInt ()
2. Obtener una instancia de clase insegura
El diseño de clase inseguro solo se proporciona al cargador de clase de inicio JVM Trusted, y es una clase típica de patrón singleton. Su método de adquisición de instancias es el siguiente:
public static inseguro getunSafe () {class cc = sun.reflect.reflection.getCallerClass (2); if (cc.getClassLoader ()! = NULL) Lanzar una nueva SecurityException ("insegura"); devolver TheUnsafe;}Un cargador de clase sin inicio llamará directamente al método inseguer.getUnsafe () y lanzará una Ejecución Security (la razón específica implica el mecanismo de carga principal de la clase JVM).
Hay dos soluciones. Una es especificar la clase que se utilizará como la clase de inicio a través del parámetro JVM - XBOOTCLASSPATH. El otro método es la reflexión de Java.
Campo f = unsafe.class.getDeClaredfield ("TheUnSafe"); F.SetAccessible (verdadero); Unsafe Unsafe = (Unsafe) F.Get (NULL);Estableciendo brutalmente accesible a True para la instancia privada de Singleton, y luego obtenga directamente un objeto emitido a inseguro a través del método GET de Field. En el IDE, estos métodos se marcarán como error y pueden resolverse mediante la siguiente configuración:
Preferencias -> Java -> Compilador -> Errores/Advertencias -> API desactivada y restringida -> Referencia prohibida -> Advertencia
3. Escenarios de aplicación "interesantes" de clase insegura
(1) omitir el método de inicialización de clase. El método asignAnstance () se vuelve muy útil cuando desea pasar por alto a los constructores de objetos, verificadores de seguridad o constructores sin público.
Clase A {Private Long A; // no inicializado valor público a () {this.a = 1; // inicialización} public a () {return this.a; }}La siguiente es una comparación del método de construcción, el método de reflexión y el asignación de información ()
A o1 = nuevo a (); // constructoro1.a (); // imprime 1 a o2 = a.class.newinstance (); // refleSo2.a (); // imprime 1 a o3 = (a) inseguro. AllocateInstance (a.class); // unsafeo3.a (); // Impresiones 0
AllocateInstance () no ingresa en absoluto al método del constructor, y en modo singleton parecemos ver una crisis.
(2) Modificación de memoria
La modificación de la memoria es relativamente común en el lenguaje C. En Java, se puede usar para evitar el verificador de seguridad.
Considere las siguientes reglas de verificación de acceso simple:
Guardia de clases {private int access_allowed = 1; public boolean giveAccess () {return 42 == access_allowed; }}En circunstancias normales, GiveAccess siempre devuelve falso, pero no siempre sucede
Guard Guard = new Guard (); Guard.GiveAccess (); // falso, sin acceso // bypassunSafe unsafe = getunSafe (); campo f = Guard.getClass (). getDeclaredfield ("access_allowed"); unsafe.putint (Guard, Unsafe.ObjectFieldOffset (f), 42); // Memory Corruption Guard.GiveAccess (); // Verdadero, Acceso otorgadoAl calcular el desplazamiento de memoria y utilizando el método PutInt (), se modifica el access_lowed de la clase. Cuando se conoce una estructura de clase, el desplazamiento de datos siempre se puede calcular (consistente con el cálculo de compensación de datos en la clase en C ++).
(3) Implementar la función sizeof () similar al lenguaje C
Implemente una función de sizeof () tipo C combinando la función de reflexión Java y ObjectFieldOffset ().
public static sizeof (objeto o) {inseguro u = getunsafe (); Hashset campos = new Hashset (); Clase C = O.GetClass (); while (c! = object.class) {for (campo f: c.getDeclaredfields ()) {if ((f.getModifiers () & Modifier.static) == 0) {Fields.add (f); }} c = c.getSuperClass (); } // Get Offset Long maxSize = 0; para (campo f: campos) {largo offset = U.ObjectFieldOffset (f); if (offset> maxSize) {maxSize = offset; }} return ((maxSize/8) + 1) * 8; // relleno}La idea del algoritmo es muy clara: comience desde la subclase subyacente, elimine los dominios no estáticos de sí mismo y de todas sus superclase a su vez, colóquelos en un hashset (los cálculos repetidos son solo una vez, Java es una sola herencia) y luego use ObjectFieldOffset () para obtener un máximo de compensación, y finalmente considerar alineación.
En un JVM de 32 bits, el tamaño se puede obtener leyendo un largo con una compensación de archivo de clase de 12.
public static sizeof (objeto objeto) {return getunSafe (). getAddress (normalize (getunSafe (). getInt (objeto, 4l)) + 12l);}La función normalize () es un método que convierte el int en INT a no firmar a largo plazo
PRIVADO ESTÁTICO LARGO NOSLORIZADO (INT VALOR) {if (valor> = 0) Valor de retorno; return (0l >>> 32) y valor;}El tamaño de los dos sizeOf () calculado es el mismo. La implementación de sizeof () más estándar es usar java.lang.instrument, sin embargo, requiere especificar el parámetro de línea de comando -javaagent.
(4) Implementar la replicación de Java Shallow
El esquema de replicación superficial estándar es implementar la interfaz clonable o las funciones de replicación implementadas por sí misma, y no son funciones multipropósito. Al combinar el método sizeof (), se puede lograr una copia superficial.
objeto estático shooWcopy (obj obj) {long size = sizeOf (obj); comienzo largo = toaddress (obj); dirección larga = getunSafe (). AllocateMemory (tamaño); getunSafe (). CopyMemory (inicio, dirección, tamaño); regresar deaddress (dirección);}El siguiente toaddress () y fromAddress () convierte el objeto en su dirección y la operación inversa respectivamente.
static long toaddress (objeto obj) {objeto [] array = nuevo objeto [] {obj}; Long BaseOffSet = getunSafe (). ArrayBaseffset (objeto []. class); return normalize (getunSafe (). getInt (array, baseffset));} objeto estático fromAddress (dirección larga) {objeto [] array = nuevo objeto [] {null}; Long BaseOffSet = getunSafe (). ArrayBaseffset (objeto []. class); getunsafe (). putlong (matriz, baseffset, dirección); Array de retorno [0];}La función de copia superficial anterior se puede aplicar a cualquier objeto Java, y su tamaño se calcula dinámicamente.
(5) Elimine las contraseñas en la memoria
Los campos de contraseña se almacenan en String, sin embargo, el reciclaje de cadenas es administrado por JVM. La forma más segura es sobrescribir el campo de contraseña después de que se use.
Campo stringValue = string.class.getDeClaredField ("valor"); stringValue.setAccessible (true); char [] mem = (char []) stringValue.get (contraseña); para (int i = 0; i <mem.length; i ++) {mem [i] = '?';}(6) Carga dinámica de clases
El método estándar de carga dinámicamente clases es class.forname () (al escribir programas JDBC, lo recuerdo profundamente). Unsafe también puede cargar dinámicamente archivos de clase Java.
byte [] classContents = getClassContent (); Clase C = GetunSafe (). Defineclass (null, classContents, 0, classContents.length); c.getMethod ("A"). Invoke (C.NewinStance (), NULL); // El método 1getClassContent () lee un archivo de clase a una matriz de bytes. byte static privado [] getClassContent () lanza la excepción {archivo f = nuevo archivo ("/home/mishadoff/tmp/a.class"); FileInputStream Input = new FileInputStream (f); byte [] content = new byte [(int) f.length ()]; input.read (contenido); input.close (); return content;}Se puede aplicar en carga dinámica, proxy, corte y otras funciones.
(7) La excepción de detección de paquetes es una excepción de tiempo de ejecución.
getunSafe (). ThrowException (nuevo IOException ());
Esto se puede hacer cuando no desea ver la excepción verificada (no se recomienda).
(8) Serialización rápida
El Java Serializable estándar es muy lento, y también limita que la clase debe tener un constructor público sin parámetros. Externalizable es mejor, necesita especificar un esquema para que la clase se sea serializada. Las bibliotecas de serialización eficientes populares, como Kryo, que dependen de las bibliotecas de terceros, aumentarán el consumo de memoria. Puede obtener el valor real del dominio en la clase a través de getInt (), getLong (), getObject () y otros métodos, y persistir información como el nombre de la clase en el archivo juntos. Kryo ha intentado usar inseguros, pero no hay datos específicos de mejora del rendimiento. (http://code.google.com/p/kryo/issues/detail?id=75)
(9) Asignar memoria en un montón no java
El nuevo uso de Java asignará la memoria para los objetos en el montón, y el ciclo de vida del objeto será administrado por JVM GC.
Clase SuperArray {Private final static int byte = 1; Privado de largo tamaño; dirección larga larga; Public SuperArray (tamaño largo) {this.size = size; dirección = getunSafe (). AllocateMemory (tamaño * byte); } set public void (long i, valor de byte) {getunSafe (). PutByte (dirección + i * byte, valor); } public int get (long idx) {return getunSafe (). getByte (dirección + idx * byte); } public size largo () {Tamaño de retorno; }}La memoria asignada por Inse SAFE no está limitada por Integer.max_value y se asigna en la memoria sin alojamiento. Al usarlo, debe ser muy cauteloso: si olvida reciclarlo manualmente, se producirán fugas de memoria; Si usted tiene acceso ilegal de dirección, hará que el JVM se bloquee. Se puede usar cuando necesita asignar grandes áreas continuas, programación en tiempo real (no tolerar la latencia JVM). Java.nio usa esta tecnología.
(10) Aplicaciones en concurrencia de Java
Mediante el uso de unsafe.compareandswap (), se puede utilizar para implementar estructuras de datos eficientes sin bloqueo.
Class cascounter implementa contador {contador largo volátil privado = 0; privado inseguro inseguro; desplazamiento largo privado; public cascounter () lanza la excepción {unsafe = getunSafe (); offset = unsafe.objectFieldOffset (cascounter.class.getDeclaredField ("contador")); } @Override public void increment () {Long antes = contador; while (! unsafe.compareandswaplong (esto, compensación, antes, antes + 1)) {antes = contador; }} @Override public Long getCounter () {return contador; }}A través de las pruebas, la estructura de datos anterior es básicamente la misma que la eficiencia de las variables atómicas de Java. Las variables atómicas de Java también utilizan el método de comparación de UNSAFE, y este método eventualmente corresponderá a las primitivas correspondientes de la CPU, por lo que es muy eficiente. Aquí hay una solución para implementar hashmap sin bloqueo (http://www.azulsystems.com/about_us/presentations/lock-free-hash. La idea de esta solución es: analizar cada estado, crear copias, modificar copias, usar primitivas de CAS, bloqueos de spins). En Ordinary Server Machines (Core <32), utilizando concurrenthashmap (antes de JDK8, se implementó el bloqueo predeterminado de separación de 16 canales y se ha implementado concurrenthashmap usando bloqueo sin bloqueo) es obviamente suficiente.
Resumir
Lo anterior es todo el contenido de este artículo. Espero que el contenido de este artículo tenga cierto valor de referencia para el estudio o el trabajo de todos. Si tiene alguna pregunta, puede dejar un mensaje para comunicarse. Gracias por su apoyo a Wulin.com.