Ajuste de desempenho Java: 10 técnicas comprovadas para maximizar a velocidade Java

Ajuste del rendimiento de Java: 10 técnicas probadas para maximizar la velocidad de Java

Mejore el rendimiento de sus aplicaciones Java con técnicas de ajuste efectivas. Descubra estrategias para optimizar su código y aumentar la eficiencia general.

Imagem em destaque

Java, uno de los lenguajes de programación más populares con una amplia variedad de usos, tiene muchas cualidades útiles. Sin embargo, a menudo ha sido criticado por su desempeño.

¿Cómo resuelve los problemas de rendimiento de Java en sus servicios de desarrollo de Java? Aquí, exploraremos varias técnicas para optimizar el rendimiento de sus aplicaciones, un proceso conocido como ajuste del rendimiento de Java.

Perfil n.º 1 para degradación del rendimiento

Al optimizar su código, debe intentar encontrar los mayores problemas de rendimiento y solucionarlos primero. Pero a veces no es obvio qué pieza está causando los problemas. En estos casos, es mejor utilizar un perfilador.

Un generador de perfiles es una herramienta que se utiliza para identificar problemas de rendimiento, como cuellos de botella, pérdidas de memoria y otras áreas de ineficiencia en el código.

Los perfiladores de Java funcionan recopilando datos sobre diversos aspectos de una aplicación Java en ejecución, como el tiempo necesario para ejecutar métodos, asignaciones de memoria, comportamiento de subprocesos y uso de CPU. Luego, estos datos se analizan para proporcionar información detallada sobre las características de rendimiento de la aplicación.

Algunos perfiladores de Java comúnmente utilizados son VisualVM , JProfiler y SeuKit . Intellij también proporciona un conjunto de herramientas para la creación de perfiles de aplicaciones .

#2 Pruebas de rendimiento

Las pruebas de rendimiento son el proceso de someter su aplicación a situaciones realistas o extremas y analizar su rendimiento. Algunas opciones populares son Apache JMeter , Gatling y BlazeMeter .

La creación de perfiles y las pruebas de rendimiento son dos cosas diferentes. Crear perfiles es similar a mirar de cerca un coche y examinar sus diferentes partes. Por otro lado, las pruebas de rendimiento son como dar una vuelta con un coche de juguete y ver cómo se comporta en diferentes situaciones. Después de haber realizado pruebas de rendimiento e identificado algunos errores, puede utilizar una herramienta de creación de perfiles para encontrar la causa subyacente de estos problemas.

#3 Prueba de carga

Las pruebas de carga son un tipo de prueba de rendimiento que implica la simulación de cargas realistas en un sistema o aplicación para medir su comportamiento en condiciones de carga normales y máximas.

Imagina que tienes un sitio web y quieres saber cuántas personas pueden usarlo al mismo tiempo sin que se rompa o se ralentice demasiado. Para ello utilizamos herramientas especiales que simulan una gran cantidad de usuarios utilizando el sitio web o la aplicación al mismo tiempo.

Algunas aplicaciones de prueba de carga populares son Apache JMeter, WebLoad LoadUI, LoadRunner NeoLoad, LoadNinja, etc.

#4 Utilice la declaración preparada

En Java, Statement se utiliza para ejecutar consultas SQL. Sin embargo, la clase Statement tiene algunos defectos. Veamos el ejemplo a continuación.

 Conexión db_con = DriverManager.getConnection; 
Declaración st = db_con.createStatement;

 Cadena nombre de usuario = "ENTRADA DE USUARIO";
 Consulta de cadena = "SELECCIONAR usuario DE usuarios DONDE nombre de usuario="" + nombre de usuario + """;
 Resultado del conjunto de resultados = st.executeQuery(consulta);

Si el usuario pasó ' OR 1=1– la consulta resultante sería:

 SELECCIONE usuario DE usuarios DONDE nombre de usuario="" O 1=1 -- '

Esto haría que la consulta devolviera todos los registros de la tabla de usuarios, ya que la condición OR 1=1 siempre es verdadera. El doble guión – al final es un comentario SQL, lo que hace que se ignore el resto de la consulta original. Este tipo de ataque se llama inyección SQL.

Las declaraciones son vulnerables a las inyecciones de SQL y, por lo tanto, debe utilizar PreparedStatement.

Las declaraciones preparadas son declaraciones SQL precompiladas. Al estar precompiladas, tienen un rendimiento mucho mejor que las instrucciones que se compilan para cada nueva consulta. A diferencia de las declaraciones, se pueden reutilizar varias veces con diferentes parámetros. Puedes poner "?" en la consulta que se puede reemplazar con un parámetro deseado.

#5 Optimizar cadenas

Como sabemos, las cadenas en Java son objetos. Sin embargo, es posible que hayas notado que las cadenas se comportan de manera un poco diferente a los objetos normales. Aquí hay un ejemplo para demostrarlo.

Cuando creamos dos objetos, se les asignan diferentes ubicaciones de memoria y una comparación de igualdad devuelve falso.

 Objeto obj1 = nuevo Objeto;
 Objeto obj2 = nuevo Objeto;
 System.out.print(obj1 == obj2); // FALSO

Pero para las cadenas, comparar la igualdad en dos cadenas similares da como resultado la verdad. De hecho, esto sólo ocurre cuando creamos cadenas usando sintaxis literal. Si crea cadenas usando el constructor, la comparación de igualdad producirá falso.

 Cadena str1 = "java";
 Cadena str2 = "java";
 System.out.print(str1 == str2); // verdadero

 Cadena obj1 = nueva cadena ("java");
 Cadena obj2 = nueva cadena ("java");
 System.out.print(str1 == str2); // FALSO

Esto se debe a que cuando las cadenas se crean utilizando sintaxis literal, se almacenan en un grupo de cadenas, que forma parte de la memoria del montón. Cada vez que se crea una nueva cadena, Java comprueba si existen cadenas similares y, de ser así, devuelve la referencia al objeto de cadena original en lugar de crear una nueva.

Otra cosa a tener en cuenta es que las cadenas son inmutables y, por lo tanto, los métodos de cadenas devuelven cadenas nuevas en lugar de modificar las antiguas. Por lo tanto, cada vez que modifica una cadena, está creando nuevas cadenas, cada una de las cuales verifica el grupo de cadenas en busca de duplicados. Esto no está optimizado.

StringBuilder y StringBuffer

Para resolver estos problemas, Java proporciona la clase StringBuilder, que es simplemente una secuencia mutable de caracteres. Utilice StringBuilder cuando modifique cadenas con frecuencia.

StringBuffer funciona de manera muy similar a StringBuilder, excepto que se puede sincronizar entre múltiples subprocesos.

Apache Commons StringUtils

La clase StringUtils proporciona algunos métodos adicionales para trabajar con cadenas. Algunos de ellos funcionan mejor que los métodos de cadena nativos. Por ejemplo, StringUtils.replace es más rápido que String.replace. Además, la mayoría de los métodos son seguros para nulos, lo que significa que no arrojan NullPointerException si la cadena es nula.

Expresiones regulares

Utilice expresiones regulares al hacer coincidir un patrón porque son mucho más eficientes que cualquier técnica de coincidencia de patrones iterativa.

#6: Optimice el proceso de recolección de basura de la máquina virtual Java

La JVM es responsable de gestionar el entorno de ejecución de las aplicaciones Java, incluida la gestión de la memoria, la gestión de subprocesos y el proceso de recolección de basura. La configuración predeterminada de JVM está optimizada para una amplia gama de aplicaciones y configuraciones de hardware, pero puede no ser ideal para aplicaciones y entornos específicos.

Elegir el recolector de basura adecuado

El recopilador predeterminado para JDK 9 y versiones posteriores es el recopilador G1, que está diseñado para escalar según el tamaño de la memoria dinámica. ZGC fue desarrollado para aplicaciones con pilas grandes que requieren un rendimiento bajo.

-XX:+UseG1GC – para usar el recopilador G1

-XX:+UnlockExperimentalVMOptions -XX:+UseZGC – para usar ZGC

Ajustar el tamaño de la memoria del montón

El montón es el área de la memoria donde se asignan los objetos. Al ajustar el tamaño de la memoria del montón, puede optimizar el uso de la memoria y evitar problemas como OutOfMemoryError o una recolección excesiva de basura.

Para establecer el tamaño del montón, puede utilizar los indicadores -Xmx y -Xms.

El indicador -Xmx limita el tamaño máximo del montón que la JVM puede asignar. Por ejemplo, pasar -Xmx2g limitará el tamaño máximo del montón a 2 gigabytes.

El indicador -Xms establece el tamaño inicial del montón. Por ejemplo, si configura -Xms256m, la JVM comenzará con un tamaño de montón de 256 megabytes.

Ajustando algunas opciones del compilador

Hay muchas opciones que se pueden utilizar para personalizar el comportamiento de la máquina virtual Java. Éstos son algunos de los más importantes:

-XX:MaxGCPauseMillis: esta opción especifica el tiempo máximo que el recolector de basura puede pausar la aplicación.

-XX: UseAdaptiveSizePolicy: esta opción le indica a la JVM que ajuste dinámicamente el tamaño de las generaciones jóvenes y actuales en función del uso de memoria de la aplicación.

#7 Usa la recursividad

La recursividad es una excelente manera de resolver problemas complejos donde la solución iterativa puede no ser obvia. Sin embargo, debe utilizar la recursividad con moderación si el uso de la memoria es crítico (por ejemplo, sistemas integrados).

Para entender por qué, veamos cómo se asigna la memoria durante una llamada a un método.

Cuando se llama a una función, la JVM asigna un marco de pila para la función en la pila de llamadas, que contiene las variables locales de la función y los argumentos del método. Si la función llama a otra función, se asigna un nuevo marco de pila a esa función y se agrega a la parte superior de la pila de llamadas.

En el enfoque iterativo, las variables locales se crean una vez. Sin embargo, en el caso del enfoque recursivo, cada marco de pila tiene su propio conjunto de variables locales, que pueden ocupar más espacio del necesario.

Entonces, si está trabajando en un entorno donde la memoria es limitada, es mejor evitar la recursividad o agregar algún tipo de verificación que evite la recursividad después de un cierto límite.

#8 Uso de primitivas y envoltorios

Las primitivas son más eficientes que sus clases contenedoras. Esto es de esperarse porque las primitivas solo ocupan una cantidad fija de espacio, mientras que las clases contenedoras tienen sus propios métodos y variables locales que ocupan algo de espacio adicional.

Por motivos similares, intente evitar las clases BigInteger o BigDecimal si la precisión no es un problema.

Sin embargo, hay ocasiones en las que debes utilizar clases contenedoras. Por ejemplo, cuando se utilizan colecciones como Listas y Mapas, la Máquina Virtual Java convierte las primitivas en sus respectivas clases contenedoras (autoboxing). En estos casos, el uso de primitivas puede provocar problemas de rendimiento.

Al crear instancias de clase contenedora, intente utilizar el método estático valueOf en lugar del constructor, ya que está en desuso desde Java 9.

#9 Utilice la última versión de JDK

A menos que su aplicación dependa de algunas características de JDK más antiguos que tienen compatibilidad limitada con los más nuevos, no hay muchas razones para no utilizar los JDK más nuevos. Cada nueva versión viene con correcciones de errores, mejoras de rendimiento y parches de seguridad. Las versiones más recientes pueden incluir características que mejoren su código de muchas maneras.

#10 Optimización prematura

La optimización prematura se refiere a la práctica de optimizar el código, incluido el uso de marcos Java antes de que sea necesario. No hay ningún beneficio adicional por optimizar su código o seleccionar marcos Java específicos de antemano. Es mejor concentrarse primero en hacer que su código funcione y luego preocuparse por hacerlo más rápido o seleccionar los marcos Java ideales.

Centrarse en la optimización demasiado pronto en el proceso de desarrollo puede desviar tiempo y recursos valiosos de tareas más críticas, como la funcionalidad, la confiabilidad y la mantenibilidad.

En lugar de optimizar cada parte del código, optimice los componentes críticos o los cuellos de botella que tienen el mayor impacto en el rendimiento. Este enfoque le permite obtener mejores resultados rápidamente.

Conclusión

En esta guía de ajuste del rendimiento de Java, exploramos métodos para aumentar la velocidad de sus aplicaciones. Esto es crucial ya sea que trabaje internamente o subcontrate el desarrollo de Java. Si sigue estas mejores prácticas y comprende las capacidades de rendimiento exactas, podrá lanzar productos de mayor calidad independientemente de su enfoque de desarrollo.

Preguntas frecuentes

¿Cómo pueden los desarrolladores de Java gestionar eficazmente el uso de la memoria para minimizar el impacto en el rendimiento de las aplicaciones?

Para administrar la memoria, los desarrolladores de Java deben utilizar el recolector de basura correcto. El recolector de basura G1 es el más versátil. En segundo lugar, debe descartar los objetos no utilizados estableciendo su referencia en nulo.

¿Cuáles son algunas bibliotecas y herramientas populares de optimización del rendimiento de Java para ayudar a los desarrolladores a mejorar el rendimiento de las aplicaciones?

Algunas herramientas populares para monitorear el rendimiento de Java son JMeter, VisualVM, JProfiler y YourKit.

Además, Apache Commons son bibliotecas que proporcionan clases de utilidades de mayor rendimiento para operaciones comunes.

Si te gustó esto, asegúrate de consultar uno de nuestros otros artículos sobre Java:

  • Las 7 mejores bibliotecas de aprendizaje automático de Java
  • C# x Java: principales diferencias explicadas
  • Concurrencia de Java: domine el arte del subproceso múltiple
  • recolección de basura de Java
  • Pruebas de integración de Java explicadas con ejemplos

Fuente: BairesDev

Regresar al blog

Deja un comentario

Ten en cuenta que los comentarios deben aprobarse antes de que se publiquen.