La clase sun.misc.Unsafe de Java se utiliza desde 2002. Proporciona métodos esenciales de bajo nivel que los desarrolladores de marcos utilizan para ofrecer funciones y rendimiento que de otro modo no se podrían obtener. Desafortunadamente, Unsafe también tiene problemas de larga data relacionados con la mantenibilidad de JVM. Y, como su nombre lo indica, su uso no es exactamente seguro. Un JEP más nuevo propone eliminar los métodos de acceso a la memoria de sun.misc.Unsafe en una futura versión de Java. Pero, ¿qué los reemplazará? Este artículo analiza la clase Unsafe, por qué algunos de sus métodos están programados para ser eliminados y qué pueden hacer los desarrolladores para prepararse para este cambio. ¿Por qué los métodos Unsafe de Java están en desuso? La clase sun.misc.Unsafe de Java hace algunos cosas especiales que de otro modo no estarían permitidas. Sus poderes se dividen en dos categorías generales: Acceso y asignación de memoria de bajo nivel, similar a un puntero. Construcción de clases fuera de los medios normales (incluso más allá de la reflexión). Al ejercer estos poderes, algunos métodos de la clase Inseguro rompen las protecciones estándar y la gestión de memoria integradas en el sistema. JVM. Debido a esto, las implementaciones de JVM varían en cómo se adaptan a Unsafe. Como resultado, el código es más frágil y es posible que no sea portátil entre versiones de JVM o sistemas operativos. Tenerlo en uso también dificulta la capacidad de los desarrolladores para desarrollar los componentes internos de JVM. Por qué los desarrolladores usan UnsafeUnsafe no es del todo malo, razón por la cual se ha mantenido durante tanto tiempo. La Guía de Baeldung para sun.misc.Unsafe incluye una descripción general de lo que los desarrolladores pueden hacer con la clase Unsafe: Creación de instancias de clase sin constructores Manipulación directa de campos de clase Lanzamiento de excepciones marcadas cuando no son manejadas por el alcance Acceso directo a operaciones de memoria dinámica Acceso a Operaciones de comparación e intercambio (CAS) La operación de comparación e intercambio de Java es un buen ejemplo de por qué tenemos una clase Insegura. CAS es una instrucción a nivel de hardware que permite el acceso a la memoria atómica. La atomicidad brinda importantes beneficios de rendimiento a los subprocesos concurrentes porque nos permite modificar la memoria sin bloquear. Tradicionalmente, las API de Java estándar no podían aprovechar esta característica porque es específica del sistema operativo. Aquí hay un fragmento de una operación de comparación e intercambio usando Unsafe, de la introducción de Oracle a sun.misc.Unsafe: public final class AtomicCounter implements Counter { private static final Unsafe unsafe; valor largo final estático privadoOffset; valor int volátil privado = 0; estático { intentar { Campo f = Unsafe.class.getDeclaredField(«theUnsafe»); // (1) f.setAccessible(verdadero); // (2) inseguro = (Inseguro) f.get(null); // (3) valueOffset = unsafe.objectFieldOffset (AtomicCounter.class.getDeclaredField(«valor»)); // (4) } catch (Excepción ex) { throw new Error(ex); } } @Override public int increment() { return unsafe.getAndAddInt(this, valueOffset, 1) + 1; // (5) } @Override public int get() { valor de retorno; } } Notarás que incluso el acceso a Inseguro es extraño. Es un miembro de clase estático al que primero se debe acceder con la reflexión (1), luego configurarlo manualmente como accesible (2) y luego hacer referencia, nuevamente con la reflexión (3). Con la clase Unsafe en mano, obtenemos el desplazamiento del campo de valor en la clase AtomicCounter actual (4). El desplazamiento nos dice en qué parte de la memoria reside el campo en relación con la asignación de memoria de la clase. Tenga en cuenta que aquí estamos tratando con punteros, que pueden resultar desconocidos para los desarrolladores de Java, aunque son estándar en lenguajes de administración directa de memoria como C/C++. Los punteros nos dan acceso directo a la memoria del montón al hacer que la responsabilidad del acceso a la memoria recaiga en el desarrollador, algo que el mecanismo de recolección de basura de Java fue diseñado explícitamente para evitar. Para ejecutar la instrucción CAS, se llama a la función getAndAddInt() (5). Esto le indica al sistema operativo que ejecute la instrucción atómica, usándola como marco de referencia, en la ubicación valueOffset y con el «delta» de 1, lo que significa que el sistema operativo debe incrementar en 1.Refactorizando sun.misc.UnsafeAhora He probado el uso de Unsafe. El ejemplo también destaca algunos de sus inconvenientes. Realizar operaciones de memoria incorrectas puede provocar que la JVM se «falle» sin generar una excepción de plataforma. Una mala gestión de la memoria también puede provocar pérdidas de memoria, que son notoriamente fáciles de crear y difíciles de diagnosticar y corregir en algunos idiomas. El acceso directo a la memoria también puede abrir agujeros de seguridad como desbordamientos del búfer. (Consulte Stack Overflow para obtener una revisión más detallada de los inconvenientes del uso de sun.misc.Unsafe). Pero los errores creados por los programadores son sólo una parte del problema. El uso de Unsafe también genera código específico de la implementación. Eso significa que los programas que los utilizan pueden no ser tan portátiles. También hace que sea más difícil para una JVM cambiar sus implementaciones en esas áreas. Por todas estas razones, los desarrolladores de Java están avanzando para introducir una forma ordenada por plataforma para realizar las acciones actualmente asociadas con la clase Unsafe. El problema es que el código que usa sun.misc.Unsafe está por todas partes. Golpea varios marcos en puntos críticos. Y debido a que Unsafe se ocupa de operaciones delicadas de bajo nivel, el código es especialmente complicado de refactorizar. Alternativas a sun.misc.Unsafe Los desarrolladores de Java están actualmente en el proceso de reemplazar las características de Unsafe con versiones estándar y menos problemáticas. Veamos las alternativas, algunas de las cuales se introdujeron en Java 9. VarHandlesVariable Handles se describe en JEP 193. Esta característica cubre uno de los usos más importantes de Unsafe, que es acceder y manipular directamente campos en el montón. Vale la pena leer los objetivos de JEP para comprender qué hace esta característica, pero aquí hay un resumen: No debe ser posible colocar la JVM en un estado de memoria corrupta. El acceso a un campo de un objeto sigue las mismas reglas de acceso que con los códigos de bytes getfield y putfield, además de la restricción de que un campo final de un objeto no se puede actualizar. Sus características de rendimiento deben ser iguales o similares a las operaciones equivalentes de sun.misc.Unsafe. La API debe ser mejor que la API sun.misc.Unsafe. En general, VarHandles nos brinda una versión mejor y más segura de las funciones de Unsafe y no compromete el rendimiento. Puede ver la variedad de tipos de operaciones que admite esta característica consultando el Javadoc para AccessModes. Notará que se admite la comparación e intercambio, junto con muchos otros tipos de operaciones primitivas. El uso de instancias VarHandle es una forma limpia y segura de realizar muchas operaciones de bajo nivel directamente en los campos, sin correr los riesgos que corremos con Unsafe.MethodHandlesLa clase MethodHandles (JEP 274) contiene una clase de búsqueda que reemplaza el uso de la reflexión para obtener y alterar permisos en campos (la técnica que vio anteriormente en el ejemplo de CAS). Con esta clase, los permisos se otorgan de acuerdo con el acceso del código que contiene en lugar de configurarse directamente con la reflexión. La API Stack-Walking El Unsafe.getCallerClass() original se reemplaza parcialmente con la API Stack-Walking entregada en JEP 259. Proporciona una forma más segura, aunque más indirecta, de acceder a la clase de persona que llama. Aún se necesitan soluciones. Algunas características pendientes de Inseguridad aún deben ser reemplazadas. Un ejemplo importante es la capacidad de crear proxies y simulacros. (Ben Evans ofrece una buena descripción general del problema en su artículo de la revista Java, Inseguro a cualquier velocidad). También es interesante observar cómo los proyectos del mundo real están luchando con este problema. Por ejemplo, considere cómo el proyecto Objenesis Git resolvió la eliminación de Unsafe.defineClass() de la API. Según este ejemplo, podemos ver que todavía queda trabajo por hacer para reemplazar completamente las capacidades de Unsafe en la creación de clases sin constructores. Conclusión El anillo de bronce para el viaje inseguro de Java es reemplazar completamente todas las características de acceso a la memoria de Unsafe con versiones autorizadas. Como puede ver, este trabajo no se realiza todo en un solo lugar. Se agregarán fragmentos a través de API más nuevas, aunque VarHandle aborda una gran parte del trabajo. Tener nuevas versiones de la funcionalidad es solo la mitad de la batalla. Los diversos frameworks y proyectos que actualmente dependen de métodos inseguros deben migrar a las nuevas opciones, lo cual no es nada sencillo. En algunos casos, requerirá una refactorización seria. Gradualmente, la JVM probablemente continuará desaprobando y eliminando las características no seguras a medida que se solidifiquen las alternativas válidas. Parece que los administradores de Java están comprometidos a parchar de manera responsable esta área de larga data de la plataforma y al mismo tiempo garantizar la estabilidad del ecosistema que depende de ella. Copyright © 2024 IDG Communications, Inc.