Compartir a través de


Transición de Java 8 a Java 11

No hay una solución única para todos para realizar la transición del código de Java 8 a Java 11. Para una aplicación no trivial, pasar de Java 8 a Java 11 puede ser una cantidad significativa de trabajo. Entre los posibles problemas se incluyen la API eliminada, los paquetes en desuso, el uso de la API interna, los cambios en los cargadores de clases y los cambios en la recolección de elementos no utilizados.

En general, los enfoques son intentar ejecutarse en Java 11 sin volver a compilar o compilar primero con JDK 11. Si el objetivo es poner en marcha una aplicación lo más rápido posible, intentar ejecutarse en Java 11 suele ser el mejor enfoque. Para una biblioteca, el objetivo será publicar un artefacto compilado y probado con JDK 11.

La migración a Java 11 merece la pena. Se han agregado nuevas características y se han realizado mejoras desde Java 8. Estas características y mejoras mejoran el inicio, el rendimiento, el uso de memoria y proporcionan una mejor integración con los contenedores. Y hay adiciones y modificaciones a la API que mejoran la productividad del desarrollador.

Este documento toca las herramientas para inspeccionar el código. También trata los problemas que puede surgir y recomendaciones para resolverlos. También debe consultar otras guías, como la Guía de migración de Oracle JDK. Aquí no se explica cómo hacer que el código existente sea modular .

Cuadro de herramientas

Java 11 tiene dos herramientas, jdeprscan y jdeps, que son útiles para detectar posibles problemas. Estas herramientas se pueden ejecutar en archivos jar o de clase existentes. Puede evaluar el esfuerzo de transición sin tener que volver a compilar.

jdeprscan busca el uso de la API en desuso o eliminada. El uso de la API en desuso no es un problema de bloqueo, pero es algo que se debe examinar. ¿Hay un archivo jar actualizado? ¿Necesita registrar un problema para solucionar el uso de la API en desuso? El uso de la API eliminada es un problema de bloqueo que debe solucionarse antes de intentar ejecutarse en Java 11.

jdeps, que es un analizador de dependencias de clases de Java. Cuando se usa con la --jdk-internals opción , jdeps indica qué clase depende de la API interna. Puede seguir usando la API interna en Java 11, pero reemplazar el uso debe ser una prioridad. La página wiki de OpenJDK Java Dependency Analysis Tool ha recomendado reemplazos para algunas API internas de JDK usadas habitualmente.

Hay complementos jdeps y jdeprscan para Gradle y Maven. Se recomienda agregar estas herramientas a los scripts de compilación.

Herramienta Complemento de Gradle Complemento de Maven
jdeps jdeps-gradle-plugin Complemento JDeps de Apache Maven
jdeprscan jdeprscan-gradle-plugin Complemento JDeprScan de Apache Maven

El propio compilador de Java, javac, es otra herramienta del cuadro de herramientas. Las advertencias y errores que obtenga de jdeprscan y jdeps saldrán del compilador. La ventaja de usar jdeprscan y jdeps es que puede ejecutar estas herramientas en archivos jar y de clase existentes, incluidas bibliotecas de terceros.

Lo que jdeprscan y jdeps no pueden hacer es advertir sobre el uso de la reflexión para acceder a la API encapsulada. El acceso reflejo se comprueba en tiempo de ejecución. En última instancia, debe ejecutar el código en Java 11 para saber con certeza.

Uso de jdeprscan

La forma más fácil de usar jdeprscan es darle un archivo jar desde una compilación existente. También puede asignarle un directorio, como el directorio de salida del compilador o un nombre de clase individual. Use la --release 11 opción para obtener la lista más completa de la API en desuso. Si desea establecer la prioridad de las API en desuso que deben ir después, vuelva a marcar la configuración con --release 8. Es probable que la API que está en desuso en Java 8 se quite antes que la API que ha quedado en desuso más recientemente.

jdeprscan --release 11 my-application.jar

La herramienta jdeprscan genera un mensaje de error si tiene problemas para resolver una clase dependiente. Por ejemplo: error: cannot find class org/apache/logging/log4j/Logger. Se recomienda agregar clases dependientes a --class-path o utilizar la ruta de clase de la aplicación, pero la herramienta continuará el examen sin ellos. El argumento es --class-path. No funcionará ninguna otra variación del argumento class-path.

jdeprscan --release 11 --class-path log4j-api-2.13.0.jar my-application.jar
error: cannot find class sun/misc/BASE64Encoder
class com/company/Util uses deprecated method java/lang/Double::<init>(D)V

Esta salida nos indica que la com.company.Util clase llama a un constructor en desuso de la java.lang.Double clase . El javadoc recomendará la API que se usará en lugar de la API en desuso. No se puede hacer nada para resolver el error error: cannot find class sun/misc/BASE64Encoder porque se trata de una API que se ha quitado. Desde Java 8, se debe usar java.util.Base64.

Ejecute jdeprscan --release 11 --list para obtener una idea de qué API ha quedado en desuso desde Java 8. Para obtener una lista de api que se ha quitado, ejecute jdeprscan --release 11 --list --for-removal.

Cómo usar jdeps

Use jdeps con la opción --jdk-internals para buscar dependencias de la API interna de JDK. La opción --multi-release 11 de línea de comandos es necesaria para este ejemplo porque log4j-core-2.13.0.jar es un archivo jar de varias versiones. Sin esta opción, jdeps se quejará si encuentra un archivo jar de varias versiones. La opción especifica qué versión de los archivos de clase se van a inspeccionar.

jdeps --jdk-internals --multi-release 11 --class-path log4j-core-2.13.0.jar my-application.jar
Util.class -> JDK removed internal API
Util.class -> jdk.base
Util.class -> jdk.unsupported
   com.company.Util        -> sun.misc.BASE64Encoder        JDK internal API (JDK removed internal API)
   com.company.Util        -> sun.misc.Unsafe               JDK internal API (jdk.unsupported)
   com.company.Util        -> sun.nio.ch.Util               JDK internal API (java.base)

Warning: JDK internal APIs are unsupported and private to JDK implementation that are
subject to be removed or changed incompatibly and could break your application.
Please modify your code to eliminate dependence on any JDK internal APIs.
For the most recent update on JDK internal API replacements, please check:
https://wiki.openjdk.java.net/display/JDK8/Java+Dependency+Analysis+Tool

JDK Internal API                         Suggested Replacement
----------------                         ---------------------
sun.misc.BASE64Encoder                   Use java.util.Base64 @since 1.8
sun.misc.Unsafe                          See http://openjdk.java.net/jeps/260   

La salida proporciona algunos consejos buenos sobre cómo eliminar el uso de la API interna de JDK. Siempre que sea posible, se sugiere la API de reemplazo. El nombre del módulo donde se encapsula el paquete se da entre paréntesis. El nombre del módulo se puede usar con --add-exports o --add-opens si es necesario interrumpir explícitamente la encapsulación.

El uso de sun.misc.BASE64Encoder o sun.misc.BASE64Decoder dará como resultado java.lang.NoClassDefFoundError en Java 11. El código que usa estas API debe modificarse para usar java.util.Base64.

Intente eliminar el uso de cualquier API procedente del módulo jdk.unsupported. La API de este módulo hará referencia a la propuesta de mejora de JDK (JEP) 260 como reemplazo sugerido. En pocas palabras, JEP 260 dice que el uso de la API interna se admitirá hasta que la API de reemplazo esté disponible. Aunque el código puede usar la API interna de JDK, seguirá ejecutándose durante un tiempo como mínimo. Eche un vistazo a JEP 260, ya que apunta a reemplazos de algunas API internas. Los manejadores de variables se pueden usar en lugar de la API sun.misc.Unsafe, por ejemplo.

jdeps puede hacer algo más que analizar el uso de los componentes internos del JDK. Es una herramienta útil para analizar las dependencias y para generar archivos de información de módulo. Eche un vistazo a la documentación para obtener más información.

Uso de javac

La compilación con JDK 11 requerirá actualizaciones para compilar scripts, herramientas, marcos de pruebas y bibliotecas incluidas. Use la -Xlint:unchecked opción para javac para obtener los detalles sobre el uso de la API interna de JDK y otras advertencias. También puede ser necesario usar --add-opens o --add-reads exponer paquetes encapsulados al compilador (consulte JEP 261).

Las bibliotecas pueden considerar la posibilidad de empaquetar como un archivo jar de varias versiones. Los archivos jar de varias versiones permiten admitir los entornos de ejecución de Java 8 y Java 11 desde el mismo archivo jar. Agregan complejidad a la construcción. Cómo compilar archivos jar de varias versiones está fuera del ámbito de este documento.

Ejecución en Java 11

La mayoría de las aplicaciones deben ejecutarse en Java 11 sin modificaciones. Lo primero que hay que intentar es ejecutarse en Java 11 sin volver a compilar el código. El motivo es ver qué advertencias y errores produce la ejecución. Con este enfoque se obtiene una
Ejecutar la aplicación en Java 11 más rápidamente centrándose en lo mínimo que se debe realizar.

La mayoría de los problemas que puede surgir pueden resolverse sin tener que volver a compilar el código. Si se debe corregir un problema en el código, realice la corrección, pero continúe compilando con JDK 8. Si es posible, trabaje para que la aplicación se ejecute con java la versión 11 antes de compilar con JDK 11.

Comprobar las opciones de la línea de comandos

Antes de ejecutarse en Java 11, realice un examen rápido de las opciones de la línea de comandos. Las opciones que se han quitado harán que se cierre la máquina virtual Java (JVM). Esta comprobación es especialmente importante si usa opciones de registro de GC, ya que han cambiado drásticamente desde Java 8. La herramienta JaCoLine es una buena herramienta para detectar problemas con las opciones de la línea de comandos.

Comprobación de bibliotecas de terceros

Un posible origen de problemas es bibliotecas de terceros que no controla. Puede actualizar de forma proactiva bibliotecas de terceros a versiones más recientes. También puede ver qué queda fuera de la ejecución de la aplicación y actualizar solo las bibliotecas necesarias. El problema con la actualización de todas las bibliotecas a una versión reciente es que resulta más difícil encontrar la causa principal si hay algún error en la aplicación. ¿Se produjo el error debido a alguna biblioteca actualizada? ¿O fue el error causado por algún cambio en el tiempo de ejecución? El problema con actualizar solo lo necesario es que pueda requerir varias iteraciones para resolverse.

La recomendación aquí es realizar los pocos cambios posibles y actualizar las bibliotecas de terceros como un esfuerzo independiente. Si actualiza una biblioteca de terceros, lo más probable es que desee la versión más reciente y mejor compatible con Java 11. Según lo antigua que sea la versión actual, es posible que quiera ser más prudente y actualizar a la primera versión compatible con Java 9+.

Además de examinar las notas de la versión, puede usar jdeps y jdeprscan para evaluar el archivo jar. Además, el OpenJDK Quality Group mantiene una página wiki de Alcance de Calidad que enumera el estado de las pruebas de muchos proyectos de software de código abierto gratuito (FOSS) en versiones de OpenJDK.

Establecimiento explícito de la recolección de elementos no utilizados

El recolector de basura paralelo (Parallel GC) es el recolector de basura predeterminado en Java 8. Si la aplicación usa el valor predeterminado, el GC debe establecerse explícitamente usando la opción de línea de comandos -XX:+UseParallelGC. El valor predeterminado ha cambiado en Java 9 al primer recolector de elementos no utilizados (G1GC). Para realizar una comparación justa de una aplicación que se ejecuta en Java 8 frente a Java 11, la configuración de GC debe ser la misma. La experimentación con la configuración de GC debe aplazarse hasta que la aplicación se haya validado en Java 11.

Establecer explícitamente las opciones predeterminadas

Si se ejecuta en la máquina virtual hotSpot, al establecer la opción -XX:+PrintCommandLineFlags de línea de comandos se volcarán los valores de las opciones establecidas por la máquina virtual, especialmente los valores predeterminados establecidos por el GC. Ejecute con esta marca en Java 8 y use las opciones impresas cuando ejecute en Java 11. En su mayor parte, los valores predeterminados son los mismos en las versiones 8 y 11. Sin embargo, la configuración de la versión 8 garantiza la paridad.

Se recomienda establecer la opción --illegal-access=warn de línea de comandos. En Java 11, el uso de la reflexión para acceder a la API interna de JDK provocará una advertencia de acceso reflectante no válida. De forma predeterminada, la advertencia solo se emite para el primer acceso no válido. La configuración de --illegal-access=warn producirá una advertencia en todos los accesos reflectantes no válidos. Encontrará más casos de acceso ilegal si la opción está configurada en warn. Pero también obtendrá una gran cantidad de advertencias redundantes.
Una vez que la aplicación se ejecuta en Java 11, establezca --illegal-access=deny para imitar el comportamiento futuro del entorno de ejecución de Java. A partir de Java 16, el valor predeterminado será --illegal-access=deny.

Advertencias de ClassLoader

En Java 8, puede convertir el cargador de clases del sistema en URLClassLoader. Normalmente, esto se realiza mediante aplicaciones y bibliotecas que desean insertar clases en la ruta de clase en tiempo de ejecución. La jerarquía del cargador de clases ha cambiado en Java 11. El cargador de clases del sistema (también conocido como cargador de clases de aplicación) ahora es una clase interna. La conversión a URLClassLoader producirá una excepción ClassCastException en tiempo de ejecución. Java 11 no tiene una API para aumentar dinámicamente la ruta de acceso a clases en tiempo de ejecución, pero se puede hacer mediante reflexión, con las advertencias obvias sobre el uso de la API interna.

En Java 11, el cargador de clases de arranque solo carga los módulos principales. Si crea un cargador de clases con un elemento primario nulo, es posible que no encuentre todas las clases de plataforma. En Java 11, debe pasar ClassLoader.getPlatformClassLoader() en lugar de null como el cargador de clases primario en estos casos.

Cambios en los datos regionales

El origen predeterminado para los datos de configuración regional en Java 11 se cambió con JEP 252 al repositorio de datos de configuración regional común del Consorcio Unicode. Esto puede tener un impacto en el formato localizado. Establezca la propiedad java.locale.providers=COMPAT,SPI del sistema para revertir al comportamiento de configuración regional de Java 8, si es necesario.

Posibles problemas

Estos son algunos de los problemas comunes que podría surgir. Siga los vínculos para obtener más detalles sobre estos problemas.

Opciones no reconocidas

Si se ha quitado una opción de línea de comandos, la aplicación imprimirá Unrecognized option: o Unrecognized VM option, seguido del nombre de la opción eliminada. Una opción no reconocida hará que la máquina virtual se cierre. Las opciones que han quedado en desuso, pero que no se han quitado, producirán una advertencia de máquina virtual.

En general, las opciones que se quitaron no tienen reemplazo y el único recurso es quitar la opción de la línea de comandos. La excepción son las opciones del registro de la recolección de elementos no utilizados. El registro de GC se reimplementó en Java 9 para usar el marco de registro unificado de JVM. Consulte la correspondencia entre las marcas de registro de recolección de elementos no utilizados heredadas y la configuración de Xlog en la tabla 2-2 de la sección sobre cómo habilitar el registro con la plataforma de registro unificada de JVM, en la referencia de las herramientas de Java SE 11.

Advertencias de máquina virtual

El uso de opciones en desuso generará una advertencia. Una opción está en desuso cuando se ha reemplazado o ya no es útil. Al igual que con las opciones eliminadas, estas opciones deben quitarse de la línea de comandos. La advertencia VM Warning: Option <option> was deprecated significa que la opción sigue siendo compatible, pero esa compatibilidad se puede quitar en el futuro. Una opción que ya no se admite y generará la advertencia VM Warning: Ignoring option. Las opciones que ya no se admiten no tienen ningún efecto en el tiempo de ejecución.

El Explorador de Opciones de VM de la página web proporciona una lista exhaustiva de opciones que se han añadido o quitado de Java desde JDK 7.

Error: No se pudo crear la máquina virtual Java

Este mensaje de error se imprime cuando la JVM encuentra una opción no reconocida.

ADVERTENCIA: Se ha producido una operación de acceso reflectante no válida

Cuando el código Java utiliza la reflexión para acceder a la API interna de JDK, la ejecución mostrará una advertencia de acceso reflejado no válido.

WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by my.sample.Main (file:/C:/sample/) to method sun.nio.ch.Util.getTemporaryDirectBuffer(int)
WARNING: Please consider reporting this to the maintainers of com.company.Main
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release

Esto significa que un módulo no ha exportado el paquete al que se accede a través de la reflexión. El paquete se encapsula en el módulo y es, básicamente, api interna. La advertencia se puede omitir como primer esfuerzo para ponerse en marcha en Java 11. El tiempo de ejecución de Java 11 permite el acceso reflectante para que el código heredado pueda seguir funcionando.

Para solucionar esta advertencia, busque código actualizado que no use la API interna. Si el problema no se puede resolver con código actualizado, se puede usar la --add-exports opción de línea de comandos o --add-opens para abrir el acceso al paquete. Estas opciones permiten el acceso a tipos no exportados de un módulo desde otro módulo.

La --add-exports opción permite al módulo de destino acceder a los tipos públicos del paquete con nombre del módulo de origen. A veces, el código utilizará setAccessible(true) para acceder a miembros y APIs no públicos. Esto se conoce como reflexión profunda. En este caso, use --add-opens para conceder acceso al código a los miembros no públicos de un paquete. Si no está seguro de si va a usar --add-exports o --add-opens, comience con --add-exports.

Las opciones --add-exports o --add-opens deben considerarse como una solución temporal, no como una solución a largo plazo. El uso de estas opciones interrumpe la encapsulación del sistema de módulos, que está diseñado para impedir que se use la API interna de JDK. Si se quita o cambia la API interna, se producirá un error en la aplicación. Se denegará el acceso reflexivo en Java 16, excepto donde el acceso esté habilitado por opciones de la línea de comandos, como --add-opens. Para imitar el comportamiento futuro, establezca --illegal-access=deny en la línea de comandos.

La advertencia del ejemplo anterior se emite porque el módulo no exporta el sun.nio.chjava.base paquete. En otras palabras, no hay ningún exports sun.nio.ch; en el module-info.java archivo del módulo java.base. Esto se puede resolver con --add-exports=java.base/sun.nio.ch=ALL-UNNAMED. Las clases que no están definidas en un módulo pertenecen implícitamente al módulo sin nombre , denominado ALL-UNNAMEDliteralmente .

java.lang.reflect.InaccessibleObjectException

Esta excepción indica que está intentando llamar setAccessible(true) a en un campo o método de una clase encapsulada. También puede obtener una advertencia de acceso reflectante no válido. Use la --add-opens opción para conceder acceso al código a los miembros no públicos de un paquete. El mensaje de excepción le informará que el módulo "no abre" el paquete al módulo que intenta llamar a setAccessible. Si el módulo es "módulo sin nombre", use UNNAMED-MODULE como módulo de destino en la opción --add-opens .

java.lang.reflect.InaccessibleObjectException: Unable to make field private final java.util.ArrayList jdk.internal.loader.URLClassPath.loaders accessible: 
module java.base does not "opens jdk.internal.loader" to unnamed module @6442b0a6

$ java --add-opens=java.base/jdk.internal.loader=UNNAMED-MODULE example.Main

java.lang.NoClassDefFoundError

NoClassDefFoundError es más probable que se deba a un paquete dividido o haciendo referencia a módulos quitados.

Error NoClassDefFoundError causado por paquetes divididos

Un paquete dividido es cuando se encuentra un paquete en más de una biblioteca. El síntoma de un problema de paquete dividido es que no se encuentra una clase que se sabe que está en la ruta de acceso a clases.

Este problema solo se producirá al usar la ruta de acceso del módulo. El sistema de módulos de Java optimiza la búsqueda de clases mediante la restricción de un paquete a un módulo con nombre . El entorno de ejecución prioriza la ruta de módulo sobre la ruta de clase al buscar una clase. Si un paquete se divide entre un módulo y la ruta de acceso de clase, solo se usa el módulo para realizar la búsqueda de clases. Esto puede dar lugar a errores de NoClassDefFound.

Una manera fácil de comprobar un paquete dividido es conectar la ruta de acceso a módulos y clases en jdeps, y usar la ruta de acceso a los archivos de clase de la aplicación como valor de <path>. Si hay un paquete dividido, jdeps imprimirá una advertencia: Warning: split package: <package-name> <module-path> <split-path>.

Este problema se puede resolver mediante --patch-module <module-name>=<path>[,<path>] para agregar el paquete dividido al módulo con nombre.

NoClassDefFoundError causado por el uso de módulos Java EE o CORBA

Si la aplicación se ejecuta en Java 8 pero lanza un java.lang.NoClassDefFoundError o un java.lang.ClassNotFoundException, es probable que la aplicación esté usando un paquete de los módulos de Java EE o de CORBA. Estos módulos estaban en desuso en Java 9 y se quitaron en Java 11.

Para resolver el problema, agregue una dependencia en tiempo de ejecución al proyecto.

Módulo quitado Paquete afectado Dependencia sugerida
API de Java para servicios web XML (JAX-WS) java.xml.ws JAX WS RI Runtime
Arquitectura de Java para Vinculación XML (JAXB) java.xml.bind Tiempo de ejecución de JAXB
JavaBeans Activation Framework (JAV) java.activation Marco de activación de JavaBeans (TM)
Anotaciones comunes java.xml.ws.annotation API de anotación de Javax
Arquitectura del Agente de solicitud de objetos comunes (CORBA) java.corba GlassFish CORBA ORB
JAVA Transaction API (JTA) java.transaction API de transacción de Java

-Xbootclasspath/p ya no es una opción compatible

Se ha eliminado el soporte para -Xbootclasspath/p. En su lugar, use --patch-module. La opción --patch-module se describe en JEP 261. Busque la sección sobre el contenido del módulo de revisión. --patch-module se puede usar con javac y con java para invalidar o aumentar las clases de un módulo.

Lo que hace --patch-module, en efecto, es insertar el módulo de parche en la búsqueda de clases del sistema de módulos. El sistema de módulos tomará primero la clase del módulo de parche. Este es el mismo efecto que bootclasspath en Java 8.

UnsupportedClassVersionError

Esta excepción significa que está intentando ejecutar código compilado con una versión posterior de Java en una versión anterior de Java. Por ejemplo, estás usando Java 11 junto con un archivo JAR compilado con JDK 13.

Versión de Java Versión del formato de archivo de clase
8 52
9 53
10 54
11 55
12 56
13 57

Pasos siguientes

Una vez que la aplicación se ejecute en Java 11, considere la posibilidad de mover bibliotecas fuera de la ruta de acceso de clase y a la ruta de acceso del módulo. Busque versiones actualizadas de las bibliotecas de las que depende la aplicación. Elija bibliotecas modulares, si está disponible. Use la ruta de acceso del módulo tanto como sea posible, incluso si no planea usar módulos en la aplicación. El uso de module-path tiene un mejor rendimiento para la carga de clases que el class-path.