Business
Jobs
  • About Us
  • Solutions
    • Job Postings
      Post your job and receive qualified candidates in 48h.
    • Candidate Assessments
      500+ technical and psychological tests, plus anti-fraud.
    • Headhunting
      Tailor-made executive search from start to finish.
    • Payroll + EOR
      Payroll dispersal and EOR across 15+ LATAM countries.
  • Pricing
  • Jobs

0

159
Views
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 answers
Answer question

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 Report

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 Report
Answer question
Find remote jobs

Discover the new way to find a job!

Top jobs
Top job categories
Business
Post vacancy Pricing Sales
Legal
Terms and conditions Privacy policy
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Show me some job opportunities
There's an error!