Empresas
Empleos
  • Sobre nosotros
  • Soluciones
    • Publicación de vacantes
      Publica tu vacante y recibe candidatos calificados en 48h.
    • Evaluación de candidatos
      500+ pruebas técnicas y psicológicas, más anti-fraude.
    • Headhunting
      Búsqueda ejecutiva a la medida de principio a fin.
    • Nómina + EOR
      Dispersión de nómina y EOR en más de 15 países de LATAM.
  • Precios
  • Empleos

0

153
Vistas
newInstance vs new en jdk-9/jdk-8 y jmh

He visto muchos hilos aquí que comparan y tratan de responder cuál es más rápido: newInstance o new operator .

Mirando el código fuente, parecería que newInstance debería ser mucho más lento , quiero decir que hace muchos controles de seguridad y usa la reflexión. Y he decidido medir, primero ejecutando jdk-8. Aquí está el código usando jmh .

 @BenchmarkMode(value = { Mode.AverageTime, Mode.SingleShotTime }) @Warmup(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS) @Measurement(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS) @State(Scope.Benchmark) public class TestNewObject { public static void main(String[] args) throws RunnerException { Options opt = new OptionsBuilder().include(TestNewObject.class.getSimpleName()).build(); new Runner(opt).run(); } @Fork(1) @Benchmark public Something newOperator() { return new Something(); } @SuppressWarnings("deprecation") @Fork(1) @Benchmark public Something newInstance() throws InstantiationException, IllegalAccessException { return Something.class.newInstance(); } static class Something { } }

No creo que haya grandes sorpresas aquí (JIT hace muchas optimizaciones que hacen que esta diferencia no sea tan grande ):

 Benchmark Mode Cnt Score Error Units TestNewObject.newInstance avgt 5 7.762 ± 0.745 ns/op TestNewObject.newOperator avgt 5 4.714 ± 1.480 ns/op TestNewObject.newInstance ss 5 10666.200 ± 4261.855 ns/op TestNewObject.newOperator ss 5 1522.800 ± 2558.524 ns/op

La diferencia para el código caliente sería de alrededor de 2x y mucho peor para el tiempo de disparo único.

Ahora cambio a jdk-9 (compilación 157 en caso de que sea importante) y ejecuto el mismo código. Y los resultados:

 Benchmark Mode Cnt Score Error Units TestNewObject.newInstance avgt 5 314.307 ± 55.054 ns/op TestNewObject.newOperator avgt 5 4.602 ± 1.084 ns/op TestNewObject.newInstance ss 5 10798.400 ± 5090.458 ns/op TestNewObject.newOperator ss 5 3269.800 ± 4545.827 ns/op

Esa es una enorme diferencia de 50x en el código caliente. Estoy usando la última versión de jmh (1.19.SNAPSHOT).

Después de agregar un método más a la prueba:

 @Fork(1) @Benchmark public Something newInstanceJDK9() throws Exception { return Something.class.getDeclaredConstructor().newInstance(); }

Aquí están los resultados generales n jdk-9:

 TestNewObject.newInstance avgt 5 308.342 ± 107.563 ns/op TestNewObject.newInstanceJDK9 avgt 5 50.659 ± 7.964 ns/op TestNewObject.newOperator avgt 5 4.554 ± 0.616 ns/op

¿Alguien puede arrojar algo de luz sobre por qué hay una diferencia tan grande ?

over 4 years ago · Santiago Trujillo
2 Respuestas
Responde la pregunta

0

En primer lugar, el problema no tiene nada que ver con el sistema de módulos (directamente).

Noté que incluso con JDK 9, la primera iteración de calentamiento de newInstance fue tan rápida como con JDK 8.

 # Fork: 1 of 1 # Warmup Iteration 1: 10,578 ns/op <-- Fast! # Warmup Iteration 2: 246,426 ns/op # Warmup Iteration 3: 242,347 ns/op

Esto significa que algo se ha roto en la compilación JIT.
-XX:+PrintCompilation confirmó que el punto de referencia se volvió a compilar después de la primera iteración:

 10,762 ns/op # Warmup Iteration 2: 1541 689 ! 3 java.lang.Class::newInstance (160 bytes) made not entrant 1548 692 % 4 bench.generated.NewInstance_newInstance_jmhTest::newInstance_avgt_jmhStub @ 13 (56 bytes) 1552 693 4 bench.generated.NewInstance_newInstance_jmhTest::newInstance_avgt_jmhStub (56 bytes) 1555 662 3 bench.generated.NewInstance_newInstance_jmhTest::newInstance_avgt_jmhStub (56 bytes) made not entrant 248,023 ns/op

Luego -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining señaló el problema de inserción:

 1577 667 % 4 bench.generated.NewInstance_newInstance_jmhTest::newInstance_avgt_jmhStub @ 13 (56 bytes) @ 17 bench.NewInstance::newInstance (6 bytes) inline (hot) ! @ 2 java.lang.Class::newInstance (160 bytes) already compiled into a big method

El mensaje "ya compilado en un método grande" significa que el compilador no pudo llamar a Class.newInstance en línea porque el tamaño compilado del destinatario es mayor que el valor de InlineSmallCode (que es 2000 de forma predeterminada).

Cuando volví a ejecutar el punto de referencia con -XX:InlineSmallCode=2500 , volvió a ser rápido.

 Benchmark Mode Cnt Score Error Units NewInstance.newInstance avgt 5 8,847 ± 0,080 ns/op NewInstance.operatorNew avgt 5 5,042 ± 0,177 ns/op

Ya sabes, JDK 9 ahora tiene G1 como el GC predeterminado . Si recurro a Parallel GC, el punto de referencia también será rápido incluso con el InlineSmallCode predeterminado.

Vuelva a ejecutar el punto de referencia de JDK 9 con -XX:+UseParallelGC :

 Benchmark Mode Cnt Score Error Units NewInstance.newInstance avgt 5 8,728 ± 0,143 ns/op NewInstance.operatorNew avgt 5 4,822 ± 0,096 ns/op

G1 requiere poner algunas barreras cada vez que ocurre un almacenamiento de objetos, es por eso que el código compilado se vuelve un poco más grande, por lo que Class.newInstance excede el límite predeterminado InlineSmallCode . Otra razón por la que Class.newInstance compilado se ha vuelto más grande es que el código de reflexión se ha reescrito ligeramente en JDK 9.

TL; DR JIT no pudo Class.newInstance porque se superó el límite de InlineSmallCode . La versión compilada de Class.newInstance se ha vuelto más grande debido a los cambios en el código de reflexión en JDK 9 y porque el GC predeterminado se cambió a G1.

over 4 years ago · Santiago Trujillo Denunciar

0

La implementación de Class.newInstance() es casi idéntica, excepto en la siguiente parte:

Java 8:
 Constructor<T> tmpConstructor = cachedConstructor; // Security check (same as in java.lang.reflect.Constructor) int modifiers = tmpConstructor.getModifiers(); if (!Reflection.quickCheckMemberAccess(this, modifiers)) { Class<?> caller = Reflection.getCallerClass(); if (newInstanceCallerCache != caller) { Reflection.ensureMemberAccess(caller, this, null, modifiers); newInstanceCallerCache = caller; } }
Java 9
 Constructor<T> tmpConstructor = cachedConstructor; // Security check (same as in java.lang.reflect.Constructor) Class<?> caller = Reflection.getCallerClass(); if (newInstanceCallerCache != caller) { int modifiers = tmpConstructor.getModifiers(); Reflection.ensureMemberAccess(caller, this, null, modifiers); newInstanceCallerCache = caller; }

Como puede ver, Java 8 tenía un quickCheckMemberAccess que permitía eludir las costosas operaciones, como Reflection.getCallerClass() . Esta verificación rápida se ha eliminado, supongo, porque no era compatible con las nuevas reglas de acceso al módulo.

Pero hay más. La JVM podría optimizar las instancias reflexivas con un tipo predecible y Something.class.newInstance() se refiere a un tipo perfectamente predecible. Esta optimización podría haberse vuelto menos efectiva. Hay varias razones posibles:

  • las nuevas reglas de acceso a los módulos complican el proceso
  • dado que Class.newInstance() ha quedado en desuso, se ha eliminado deliberadamente algo de soporte (me parece poco probable)
  • debido al código de implementación modificado que se muestra arriba, HotSpot no reconoce ciertos patrones de código que activan las optimizaciones
over 4 years ago · Santiago Trujillo Denunciar
Responde la pregunta
Encuentra empleos remotos

¡Descubre la nueva forma de encontrar empleo!

Top de empleos
Top categorías de empleo
Empresas
Publicar vacante Precios Comercial
Legal
Términos y condiciones Política de privacidad
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Recomiéndame algunas ofertas
Necesito ayuda