De una maravillosa entrevista sobre Habré: "Simon Ritter es una persona que trabajó en Java desde el principio y continúa haciendo esto como subdirector técnico de Azul, una empresa que trabaja en la máquina virtual Zing JVM y uno de los mejores recolectores de basura, C4 (Compactación continua simultánea) Coleccionista) »
A continuación se muestra una traducción de su artículo sobre las nuevas características de JDK 12 y algunas dificultades que puede encontrar al migrar a una nueva compilación.
Escribí varias publicaciones de blog que enumeran todos los cambios para cada una de las últimas versiones de Java ( JDK 10 , JDK 11 ). Ahora exploraré el lado oscuro de JDK 12, centrándome en algunas de las trampas que pueden causar problemas si quieres portar la aplicación a esta versión.

JDK 12 tiene la menor cantidad de nuevas características de todas las versiones de Java hasta la fecha (conté 109 en JDK 10 y 90 en JDK 11). Esto no está mal: debido a los ciclos de lanzamiento, algunas versiones contendrán más cambios y otras menos.
Dividiré nuevas funciones en áreas lógicas obvias: Java, bibliotecas, JVM y otras funciones JDK.
Cambios de idioma
La función que yo (y supongo que muchas otras personas) consideraré más visible en JDK 12 es la nueva declaración de cambio ( JEP 325 ). Este es también el primer cambio de idioma que se utilizará como una función para "previsualizar". La idea de "vista previa" se introdujo a principios de 2018 como parte de JEP 12 . Esta es esencialmente una forma de habilitar versiones beta de nuevas características usando las opciones de línea de comandos. Con la vista previa, aún es posible realizar cambios basados en los comentarios de los usuarios y, en el peor de los casos, eliminar por completo una función si no se recibió correctamente. La clave para previsualizar las funciones es que no están incluidas en la especificación Java SE. Sobre el nuevo interruptor hay una muy buena traducción en Habré.
En JDK 12, un interruptor se ha convertido en una expresión que evalúa su "contenido" para producir un resultado. Explicaré de inmediato que esto no afecta la compatibilidad con versiones anteriores, por lo que no necesita cambiar ningún código que use switch como operador.
Usaré el ejemplo de JEP, ya que es simple y claro:
Interruptor viejoint numLetters; switch (day) { case MONDAY: case FRIDAY: case SUNDAY: numLetters = 6; break; case TUESDAY: numLetters = 7; break; case THURSDAY: case SATURDAY: numLetters = 8; break; case WEDNESDAY: numLetters = 9; break; default: throw new IllegalStateException("Huh? " + day); }
Como puede ver, asignamos el día de la semana al nombre del day
variable, luego asignamos el valor numLetters
. Ahora que ese interruptor es un operador, podemos hacer la asignación una vez (reduciendo significativamente la probabilidad de un código erróneo) usando el resultado de la declaración del interruptor:
int numLetters = switch (day) { case MONDAY, FRIDAY, SUNDAY -> 6; case TUESDAY -> 7; case THURSDAY, SATURDAY -> 8; case WEDNESDAY -> 9; default -> throw new IllegalStateException("Huh? " + day); };
Notará rápidamente dos cambios de sintaxis. Los desarrolladores de OpenJDK se toparon con una función de sintaxis poco conocida llamada lista separada por comas. Además, el operador de expresión lambda ->
facilita la devolución del valor. Todavía puedes usar break
con un valor si realmente quieres esto. Hay varios otros detalles sobre esta característica, pero probablemente sea más fácil leer JEP.
Bibliotecas
Hay un cambio que me parece muy útil. También hay una serie de secundarios.
colector de tee
La API de Streams, como de costumbre, tiene un nuevo recopilador, proporcionado por la clase de utilidad de recopiladores. Se puede obtener un nuevo colector utilizando el método teeing()
. El colector de salida toma tres argumentos: dos colectores y una bifunción. Para comprender el trabajo de este coleccionista, recomiendo este artículo sobre Habré .
Para entender cómo hace esto, dibujé un diagrama:

Todos los valores de la secuencia de entrada se pasan a cada recopilador. El resultado de cada recopilador se pasa como argumentos a BiFunction para generar el resultado final.
Un ejemplo simple es calcular el valor promedio (sí, sé que ya hay recolectores para esto, como averagingInt()
, pero este es un ejemplo simple para ayudar a comprender el concepto).
double average = Stream.of(1, 4, 2, 7, 4, 6, 5) .collect(teeing( summingDouble(i -> i), counting(), (sum, n) -> sum / n) );
El primer recopilador calcula la suma de la secuencia de entrada y el segundo, el número de elementos. BiFunction divide la suma por el número de elementos para obtener el valor promedio.
java.io
InputStream skipNBytes(long n)
: omite y descarta exactamente n bytes del flujo de entrada InputStream. Si n es cero o menos, los bytes no se omiten.
java.lang
Ha aparecido un nuevo paquete, java.lang.constant, que forma parte de la API JVM constante, JEP 334 .
Cada archivo de clase Java tiene un grupo persistente que almacena operandos para instrucciones de código de bytes en la clase. Es difícil para los desarrolladores manipular archivos de clase debido a problemas al cargar clases. La API JVM constante proporciona tipos de referencia simbólicos para describir cada forma de una constante (clase, constante cargable, MethodHandle
, MethodHandle
constante, MethodType
constante).
También influyó en varias otras clases. Todas las siguientes clases ahora tienen un método describeConstable()
:
- Clase
- Doble
- Enum
- Flotador
- Entero
- Largo
- Cadena
- Metodología
- MethodType
- Varhandle
Como británico, me parece bastante divertido. El término Constable, describeConstable
usado desde el siglo XI, y así es como a menudo nos referimos a los oficiales de policía. También es el nombre del famoso artista del siglo XVIII, John Constable. Esto me hace preguntarme si el método describeTurner()
estará en una versión futura. Obviamente, en este caso es una abreviatura de la Constant Table
, no relacionada con un oficial de leyes o un paisajista.
Las siguientes clases ahora incluyen el método resolveConstantDesc()
:
- Doble
- Enum.EnumDesc
- Flotador
- Entero
- Largo
- Cadena
java.lang.Character
Las clases internas se han actualizado para incluir nuevos bloques Unicode. Siempre me gusta ver lo que la gente ha encontrado para agregar a Unicode, aquí hay algunos ejemplos:
- Símbolos de ajedrez
- Numeros mayas
- El sogdiano es un idioma iraní oriental que ya no se usaba en el siglo XI.
- Old Sogdian es una versión anterior (y, sospecho, aún más limitada) de Sogdian
java.lang.Class
arrayType()
devuelve Class
para el tipo de matriz cuyo tipo de componente se describe en esta Class
. Esto se puede verificar usando jshell
:
jshell> (new String[2]).getClass().getName() $11 ==> "[Ljava.lang.String;" jshell> (new String[2]).getClass().arrayType() $12 ==> class [[Ljava.lang.String; jshell> "foo".getClass().arrayType() $15 ==> class [Ljava.lang.String;
No estoy muy seguro de cuál es el significado de este método, ya que todo lo que hace es agregar una Class
al tipo que representa esta clase.
componentType()
, igual que getComponentType()
. La pregunta comienza: ¿por qué agregar un método redundante?
descriptorString()
: nuevamente, devuelve el mismo resultado que getName()
. Sin embargo, es necesario porque Class
ahora implementa la interfaz TypeDescriptor
asociada con la nueva API JVM constante.
lava.lang.String
indent()
: agrega una serie de espacios iniciales a una cadena. Si el parámetro es negativo, este número de espacios iniciales se eliminará (si es posible).
transform()
: aplica la función proporcionada a una cadena. El resultado puede no ser una cadena.
java.lang.invoke
VarHandle
ahora tiene toString()
para devolver una descripción compacta.
java.net.SecureCacheResponse
y java.net.ssl.HttpsConnection
tienen un nuevo método, getSSLSession()
que devuelve Optional
contiene la SSLSession
utilizada en la conexión.
java.nio.files
La clase Files
tiene un nuevo método, mismatch()
, que encuentra y devuelve la posición del primer byte de discordancia en el contenido de dos archivos, o -1L si no hay discordancia.
java.text
Hay una nueva clase CompactNumberFormat
. Esta es una subclase de NumberFormat
que formatea un número decimal en forma compacta. Un ejemplo de una forma compacta, 1M
lugar de 1000000
, por lo tanto, requiere dos en lugar de nueve caracteres. NumberFormat
y java.text.spi.NumberFormatProvider
se han ampliado para incluir el nuevo método getCompactNumberInstance()
. También hay una nueva enumeración, NumberFormatStyle
que tiene dos significados: LARGO y CORTO.
java.util.concurrent
CompletionStage ahora incluye varios formularios sobrecargados con tres métodos:
- excepcionalmente
- excepcionalmente
- excepcionalmenteComposeAsync
Estos métodos amplían las posibilidades de crear un nuevo CompletionStage
partir de uno existente, CompletionStage
si el actual finaliza con una excepción. Consulte la documentación de la API para más detalles.
javax.crypto
La clase Cipher
tiene un nuevo método toString()
que devuelve una cadena que contiene la transformación, el modo y el proveedor de Cipher
.
javax.naming.ldap.spi
Este es un nuevo paquete en JDK 12 y contiene dos clases: LdapDnsProvider
, que es la clase de proveedor para las búsquedas de DNS durante las operaciones LDAP, y LdapDnsProviderResults
que encapsula el resultado de la búsqueda de DNS para la URL de LDAP.
Columpio
Swing todavía se está actualizando! Sí, filechooser.FileSystemView
ahora tiene un nuevo método getChooserShortcutPanelFiles()
. Devuelve una matriz de archivos que representan los valores para mostrar de forma predeterminada en la barra de acceso directo de selección de archivos.
JVM cambios
JEP 189: Shenandoah : recolector de basura de tiempo de pausa bajo
Shenandoah es un proyecto de investigación anunciado por Red Hat en 2014 que se centra en los requisitos de aplicaciones de baja latencia para la administración de memoria en la JVM. Sus objetivos son un tiempo de pausa máximo de 1..10 ms para un montón de más de 20 GB ( por lo tanto no está destinado a aplicaciones pequeñas - como respondió uno de los desarrolladores de Shenandoah , esto no es así y hace un excelente trabajo con pequeñas aplicaciones). Este recolector está diseñado para funcionar en paralelo con subprocesos de aplicaciones, por lo tanto, evite los problemas que vemos en la mayoría de los recolectores de basura.
Este cambio está destinado a mejorar el comportamiento del recopilador G1 cuando alcanza el objetivo de retraso establecido. G1 divide el espacio de almacenamiento dinámico (antiguo y antiguo) en regiones. La idea es que en la generación anterior no es necesario recolectar basura en una sola operación. Cuando G1 necesita recolectar basura, selecciona las regiones que define. Esto se llama un kit de recolección. Antes de JDK 12, cuando comenzó el trabajo en el set, todo el trabajo tenía que completarse, en esencia, como una operación atómica. El problema era que, a veces, debido a cambios en el uso del espacio de almacenamiento dinámico de la aplicación, el conjunto de recopilación resultó ser demasiado grande y tardó demasiado tiempo en recopilarse, lo que llevó al hecho de que no se alcanzó el tiempo de pausa.
En JDK 12, si G1 identifica esta situación, interrumpirá la recopilación de datos hasta la mitad si esto no afecta la capacidad de la aplicación para continuar asignando espacio para nuevos objetos. El efecto neto de G1 será mejor cuando se alcanza un breve tiempo de pausa.
Esta es otra mejora de rendimiento para G1, pero otra está relacionada con la forma en que la JVM interactúa con el resto del sistema. Obviamente, se necesita memoria para el montón JVM, y al inicio, solicita memoria del asignador de memoria virtual del sistema operativo. Cuando se inicia la aplicación, puede haber ocasiones en que la cantidad de memoria requerida para el almacenamiento dinámico caiga, y parte de la memoria asignada se puede devolver al sistema operativo para que otras aplicaciones la utilicen.
G1 ya hace esto, pero solo puede hacerlo en uno de dos lugares. Primero, durante una colección completa, y segundo, durante uno de los ciclos paralelos. G1 intenta no realizar una recopilación completa y, con un uso bajo de memoria, puede haber períodos significativos entre los ciclos de recopilación. Esto lleva al hecho de que G1 puede mantener una memoria fija durante mucho tiempo.
En JDK 12, G1 intentará periódicamente continuar o ejecutar un ciclo paralelo mientras la aplicación está inactiva para determinar el uso general del montón de Java. La memoria no utilizada se puede devolver al sistema operativo de manera más oportuna y predecible.
El nuevo indicador de línea de comando -XX:G1PeriodicGCInterval
se puede usar para establecer el número de milisegundos entre las comprobaciones.
Esta característica conducirá a un uso más conservador de la memoria JVM para aplicaciones que han estado inactivas por largos períodos de tiempo.
Otras nuevas características de JDK
El arnés de microbenchmarking de Java (JMH) fue desarrollado por Alexey Shipilev cuando trabajaba en Oracle y proporciona una amplia plataforma para desarrollar pruebas de rendimiento para aplicaciones Java. Alexey hizo un trabajo excepcional ayudando a las personas a evitar muchos errores simples que cometen al tratar de analizar el rendimiento de la aplicación: calentamiento, evitar excepciones, etc.
Ahora JMH se puede incluir en OpenJDK. Cualquiera que esté interesado en trabajar en el JDK y cambiar el código puede usar esto para comparar el rendimiento antes y después de sus cambios, así como para comparar el rendimiento en diferentes versiones. Se incluyen varias pruebas para permitir la prueba; El diseño de JMH es tal que es fácil agregar nuevas pruebas donde se necesita.
OpenJDK tiene dos puertos para la arquitectura Arm64, uno proporcionado por Oracle y el otro por Red Hat. Como esto no era necesario, y Oracle dejó de admitir Arm para sus archivos binarios JDK, se decidió usar solo el puerto Red Hat, que todavía es compatible y desarrollado.
La clase Data Sharing (CDS) solía ser una característica comercial en Oracle JDK. Con una transición reciente realizada en JDK 11 para eliminar todas las diferencias funcionales entre Oracle JDK y OpenJDK, se incluyó en OpenJDK.
Para usar CDS, necesita un archivo creado para las clases que se cargan cuando se inicia la aplicación. JDK 12 para plataformas de 64 bits ahora tiene el archivo classes.jsa
en el directorio lib/server
. Este es el archivo CDS para "clases predeterminadas". Supongo que significa todas las clases públicas en módulos JDK; No pude encontrar una manera de desempacarlo para verificarlo. Como CDS está habilitado de forma predeterminada, lo que equivale a la opción -Xshare:auto
en la línea de comandos, los usuarios se beneficiarán de los tiempos de inicio de aplicaciones mejorados.
Conclusiones
JDK 12 proporciona una pequeña cantidad de nuevas funciones y API, siendo la switch
la más interesante para los desarrolladores. Los usuarios de G1 seguramente apreciarán las mejoras de rendimiento.
Con la nueva versión del lanzamiento, recomendaría a todos los usuarios que prueben sus aplicaciones en este lanzamiento. Hacer un seguimiento de los cambios incrementales lo ayudará a evitar sorpresas si decide pasar a la próxima versión de soporte a largo plazo.
Tenemos compilaciones gratuitas de JDK 12 para Zulu Community Edition para ayudarte con tus pruebas. Asegúrate de probarlos.