Red de conocimiento informático - Aprendizaje de programación - Cómo optimizar las herramientas de desarrollo de Java

Cómo optimizar las herramientas de desarrollo de Java

Los recursos disponibles para el programa (memoria, tiempo de CPU, ancho de banda de la red, etc.) son limitados. El propósito de la optimización es permitir que el programa complete las tareas programadas con la menor cantidad de recursos posible. La optimización generalmente incluye dos aspectos: reducir el tamaño del código y mejorar la eficiencia de ejecución del código. Este artículo analiza principalmente cómo mejorar la eficiencia del código.

En los programas Java, la mayoría de las causas de los problemas de rendimiento no son el lenguaje Java, sino el programa en sí. Es muy importante desarrollar buenos hábitos de codificación. Por ejemplo, el uso correcto y hábil de java.lang.String y java.util.Vector puede mejorar significativamente el rendimiento del programa. Analicemos este tema en detalle.

1. Intente especificar que una clase con el modificador final no se derive. En la API principal de Java, hay muchos ejemplos de aplicación de final. Por ejemplo, java.lang.String Especificar final para la clase String puede evitar que las personas anulen el método length(). Además, si una clase se designa como final, todos los métodos de esa clase son finales. El compilador de Java buscará oportunidades para alinear todos los métodos finales (esto depende de la implementación específica del compilador). Esto puede mejorar el rendimiento en un promedio del 50%.

.

2. Reutilizar objetos tanto como sea posible. Especialmente cuando se utilizan objetos String, se debe utilizar StringBuffer en su lugar cuando se produce la concatenación de cadenas. Debido a que el sistema no solo necesita tiempo para generar objetos, también puede necesitar tiempo para recopilarlos y procesarlos en el futuro. Por lo tanto, generar demasiados objetos puede tener un gran impacto en el rendimiento de su programa.

3. Intente utilizar variables locales. Los parámetros pasados ​​al llamar al método y las variables temporales creadas durante la llamada se guardan en la pila, lo cual es más rápido. Otras variables, como las variables estáticas y las variables de instancia, se crean en el montón, lo que es más lento. Además, las variables locales se pueden optimizar aún más según el compilador/JVM específico. Consulte Usar variables de pila cuando sea posible.

4. No inicialice las variables repetidamente. De forma predeterminada, cuando se llama al constructor de una clase, Java inicializa las variables con ciertos valores: todos los objetos se establecen en nulo, las variables enteras (byte, short, int, long) se establecen en 0, las variables flotantes y dobles se establecen en 0,0. , los valores lógicos se establecen en falso. Esto es especialmente importante cuando una clase se deriva de otra clase, porque cuando se crea un objeto con la nueva palabra clave, todos los constructores de la cadena de constructores se llaman automáticamente.

5. En el desarrollo del sistema de aplicación JAVA ORACLE, las declaraciones SQL integradas en JAVA deben capitalizarse tanto como sea posible para reducir la carga de análisis del analizador ORACLE.

6. En el proceso de programación Java, tenga cuidado con las conexiones de bases de datos y las operaciones de flujo de E/S. Incluso ciérrelo después de su uso para liberar recursos. Debido a que operar estos objetos grandes causará una gran sobrecarga del sistema, un poco de descuido puede tener consecuencias graves.

7. Dado que la JVM tiene su propio mecanismo de GC, los desarrolladores de programas no necesitan pensar demasiado, lo que reduce la carga de los desarrolladores hasta cierto punto, pero también evita peligros ocultos. La creación de demasiados objetos consumirá mucha memoria del sistema y, en casos graves, puede provocar pérdidas de memoria. Por lo tanto, es de gran importancia garantizar el reciclaje oportuno de los artículos caducados. La condición para que JVM recicle basura es que ya no se haga referencia al objeto; pero el GC de JVM no es muy inteligente. Incluso si el objeto cumple con las condiciones para la recolección de basura, es posible que no se recicle de inmediato. Por lo tanto, se recomienda que configuremos manualmente el objeto como nulo después de usarlo.

8. Cuando utilice el mecanismo de sincronización, intente utilizar la sincronización de métodos en lugar de la sincronización de bloques de código.

9. Minimizar el cálculo repetido de variables.

Por ejemplo: for(int I = 0; iltlist.sizei) {

}

debe reemplazarse con:

for(int i = 0, int len ​​​​= lista . size(); i ltleni ) {

}

10, intente adoptar una estrategia de carga diferida, es decir, comience a crear cuando sea necesario.

Por ejemplo: String str = " aaa

if(i == 1) {

lista . add(str);

}

Debe reemplazarse por:

if(i == 1) {

String str = " aaa

list . add( str);

}

11. Utilice las excepciones con cuidado.

Las excepciones son malas para el rendimiento. Para generar una excepción, primero se debe crear un nuevo objeto. El constructor de la interfaz Throwable llama a un método nativo llamado fillInStackTrace(). El método fillInStackTrace() examina la pila y recopila información de seguimiento de llamadas. Cada vez que se produce una excepción, la VM tiene que ajustar la pila de llamadas porque se crea un nuevo objeto durante el procesamiento. Las excepciones solo deben usarse para el manejo de errores y no deben usarse para controlar el flujo del programa.

12, no lo use en un bucle:

Pruebe {

} catch() {

}

Debe colocarse en la capa más externa.

13 y el uso de StringBuffer:

StringBuffer representa una cadena variable que se puede escribir.

Hay tres métodos de construcción:

string buffer(); //La asignación predeterminada es de 16 caracteres.

String buffer(int size); //Asignar espacio para caracteres de tamaño

StringBuffer(string str); //Asignar 16 caracteres str.length() espacio de caracteres.

Puedes configurar la capacidad inicial de StringBuffer a través de su constructor, lo que puede mejorar significativamente el rendimiento. El constructor mencionado aquí es StringBuffer (int

Longitud), y el parámetro de longitud indica la cantidad de caracteres que puede contener el StringBuffer actual. También puede utilizar el método sureCapacity(int

minimumcapacity) para establecer la capacidad del objeto StringBuffer después de su creación. Primero, veamos el comportamiento predeterminado de StringBuffer y luego busquemos una mejor manera de mejorar el rendimiento.

StringBuffer mantiene una matriz de caracteres internamente. Cuando se crea un objeto StringBuffer utilizando el constructor predeterminado, la capacidad de StringBuffer se inicializa a 16 caracteres porque la longitud de caracteres inicial no está establecida, lo que significa que la capacidad predeterminada es 16 caracteres. Cuando StringBuffer alcanza su capacidad máxima, aumenta su capacidad al doble de su capacidad actual más 2, que es (2*valor anterior 2). Si utiliza el valor predeterminado, agréguele caracteres después de la inicialización. Cuando sumas 16 caracteres aumenta la capacidad a 34 (2*16 2) y cuando sumas 34 caracteres aumenta la capacidad a 70 (2*34 2). Pase lo que pase, cada vez que StringBuffer alcanza su capacidad máxima, tiene que crear una nueva matriz de caracteres y copiar los caracteres nuevos y antiguos nuevamente; esto es demasiado costoso.

Por lo tanto, no hay nada de malo en establecer siempre un valor de capacidad inicial razonable para StringBuffer, lo que traerá ganancias de rendimiento inmediatas.

El papel del ajuste en el proceso de inicialización de StringBuffer se puede ver a partir de esto. Por lo tanto, siempre es el mejor consejo inicializar un StringBuffer con un valor de capacidad apropiado.

14. Uso razonable de la clase Java java.util.Vector

En pocas palabras, Vector es una matriz de instancias de java.lang.Object. Un vector es similar a una matriz en el sentido de que se puede acceder a sus elementos a través de índices enteros. Sin embargo, después de crear un objeto de tipo Vector, el tamaño del objeto puede expandirse o contraerse según la adición o eliminación de elementos. Considere el siguiente ejemplo de cómo agregar un elemento a un vector:

Objeto obj = nuevo Objeto();

Vector v = nuevo vector(100000

); for(int I = 0;

I lt100000; I ) { v.add(0, obj);}

A menos que haya una razón absolutamente buena para insertar un nuevo elemento en delante del Vector cada vez; de lo contrario, el código anterior no beneficiará el rendimiento. En el constructor predeterminado, la capacidad de almacenamiento inicial de Vector es de 10 elementos. Si la capacidad de almacenamiento es insuficiente cuando se agrega un nuevo elemento, la capacidad de almacenamiento se duplicará cada vez a partir de entonces. La clase Vector es como la clase StringBuffer. Cada vez que se amplía la capacidad de almacenamiento, todos los elementos existentes se copian en el nuevo espacio de almacenamiento. El siguiente fragmento de código es mucho más rápido que el ejemplo anterior:

Object obj = new Object()

Vector v = new vector(100000

<); p> for(int I = 0;ilt100000;i){ v. add(obj);}

La misma regla también se aplica al método remove() de la clase Vector. Debido a que no puede haber "espacios" entre los elementos en un Vector, eliminar cualquier otro elemento que no sea el último hará que los elementos posteriores al elemento eliminado avancen. En otras palabras, el "costo" de eliminar el último elemento del vector es varias veces menor que el "costo" de eliminar el primer elemento.

Supongamos que queremos eliminar todos los elementos del vector anterior, podemos usar el siguiente código:

for(int I = 0;ilt100000;I)

{

v . remove(0);

}

Sin embargo, el código anterior es varios órdenes de magnitud más lento que el código siguiente:

p>

for(int I = 0; i lt100000; I )

{

v . /p>

}

La mejor manera de eliminar todos los elementos de un objeto V de tipo Vector es:

v remove allements();

Asumiendo tipo Vector El objeto V contiene la cadena "Hola". Considere el siguiente código, que elimina la cadena "Hola" de un vector:

String s = " Hello

int I = v . index of(s);

If (i!=-1)v.remove;

Este código parece estar bien, pero también es malo para el rendimiento. En este código, el método indexOf() está en orden. la cadena "Hola" en V, y el método remove(s) también busca en el mismo orden.

La versión mejorada es:

String s = "Hello

int I = v . index of(s);

If (I!=-1 ) v . remove(I);

En esta versión, damos directamente la posición de índice exacta del elemento a eliminar en el método remove(), evitando así una segunda búsqueda. Mejor.

String s = "Hola cinco. Quitar;

Finalmente, veamos un fragmento de código sobre la clase Vector:

for( int I = 0 ;i;ilt5.length)

Si v contiene 100.000 elementos, este fragmento de código llamará al método v.size() 100.000 veces. Aunque el método de tamaño es un método simple, aún requiere la sobrecarga de la llamada al método, al menos la JVM necesita configurar y borrar el entorno de pila para ello. Aquí, el código dentro del bucle for no modificará el tamaño del objeto de tipo Vector V de ninguna manera, por lo que es mejor reescribir el código anterior en la siguiente forma:

int size = v. size (); for (int i = 0;i;ilt size)

Aunque este es un cambio simple, aún gana en rendimiento. Después de todo, cada ciclo de CPU es valioso.

15. Al copiar grandes cantidades de datos, utilice el comando System.arraycopy().

16. Refactorización de código: Mejora la legibilidad del código.

Por ejemplo:

Carrito de compras público {

Carrito de compras de lista privada

Vacío público; agregar (elemento de objeto) {

if(carts == null) {

carts = new ArrayList();

}

crts.add(item);

}

eliminación pública nula (elemento objeto){

if(cart.contains(item)){

p>

carts.remove(item);

}

}

Lista pública getCarts() {

/ / Devuelve una lista de solo lectura

return collections . unmodifieblelist(carrito de compras);

}

//No se recomienda este método.

//this.getCarts(). Add(project);

}

17. Cree una instancia de la clase sin la nueva palabra clave.

Cuando se utiliza la palabra clave new para crear una instancia de una clase, se llamarán automáticamente todos los constructores de la cadena de constructores. Pero si un objeto implementa la interfaz Cloneable, podemos llamar a su método clone(). El método clone() no llama a ningún constructor de clase.

Cuando se utilizan patrones de diseño, si se utiliza el patrón de fábrica para crear objetos, es muy sencillo crear nuevas instancias de objetos utilizando el método clone(). Por ejemplo, la siguiente es una implementación típica del patrón de fábrica:

Crédito estático público getNewCredit() {

Devuelve newCredit();

}

El código mejorado utiliza el método clone() de la siguiente manera:

Crédito base de crédito estático privado = crédito nuevo();

Crédito estático público getNewCredit() {

p>

return(Credit)base Credit . clone();

}

Las ideas anteriores también son útiles para el procesamiento de matrices.

18, multiplicación y división

Considera el siguiente código:

for(val = 0; val lt100000; val =5) {

alterX = val * 8; my result = val * 2;

}

Reemplazar las operaciones de multiplicación con operaciones de desplazamiento puede mejorar enormemente el rendimiento. El siguiente es el código modificado:

for(val = 0; val lt100000; val = 5) {

alterX = val lt lt3; myResult = val lt lt1;

}

El código modificado ya no realiza la operación de multiplicar por 8, sino que utiliza la operación equivalente de desplazar 3 bits hacia la izquierda. Cada desplazamiento hacia la izquierda de 1 bit equivale a multiplicar por 2. . Por lo tanto, desplazar 1 bit hacia la derecha equivale a dividir por 2. Vale la pena mencionar que aunque la operación de cambio es rápida, puede hacer que el código sea más difícil de entender, por lo que es mejor agregar algunos comentarios.

19. Cerrar sesiones inútiles en páginas JSP.

Un malentendido común es que la sesión se crea cuando hay un acceso de cliente, pero el hecho es que la sesión no se crea hasta que el programa del lado del servidor llama a una declaración como httpsservletrequest. obtiene sesión (verdadero). Tenga en cuenta que si no se muestra JSP

session = solicitud de servlet http getession(true); este también es el origen del objeto de sesión implícito en JSP. Debido a que la sesión consume recursos de memoria, debe cerrarla en todos los JSP si no planea utilizarla.

Para las páginas que no necesitan realizar un seguimiento del estado de la sesión, desactivar las sesiones creadas automáticamente puede ahorrar algunos recursos. Utilice las siguientes instrucciones de la página:

20.JDBC y entrada y salida

Si una aplicación necesita acceder a grandes conjuntos de datos, debería considerar el uso de recuperaciones de bloques. De forma predeterminada, JDBC recupera 32 filas de datos a la vez. Por ejemplo, suponiendo que queremos iterar a través de un conjunto de registros de 5000 filas, JDBC debe llamar a la base de datos 157 veces para extraer todos los datos. Si el tamaño del bloque se cambia a 512, el número de llamadas a la base de datos se reducirá a 10.

[p][/p]21, Servlets y uso de memoria

Muchos desarrolladores guardan arbitrariamente grandes cantidades de información en las sesiones de los usuarios. A veces, el mecanismo de recolección de basura no recupera a tiempo los objetos guardados en la sesión. Desde una perspectiva de rendimiento, un síntoma típico es que el usuario siente que el sistema se ralentiza periódicamente, pero no puede atribuir la causa a ningún componente específico. Si monitorea el espacio de almacenamiento dinámico de la JVM, su rendimiento es un aumento y una disminución anormales en el uso de la memoria.

Existen dos formas principales de solucionar este problema de memoria. El primer enfoque consiste en implementar la interfaz HttpSessionBindingListener en todos los beans con alcance de sesión. De esta manera, siempre que se implemente el método valueUnbound (), los recursos utilizados por el Bean se pueden liberar explícitamente. Otro enfoque es cancelar la conversación lo antes posible. La mayoría de los servidores de aplicaciones tienen la opción de establecer el intervalo de caducidad de la sesión. Además, también puede llamar mediante programación al método setMaxInactiveInterval() de la sesión, que se utiliza para establecer el intervalo máximo (en segundos) permitido por el contenedor de servlet antes de que expire la sesión.

22. Utilice etiquetas de búfer

Algunos servidores de aplicaciones han agregado funciones de etiquetas de búfer para JSP. Por ejemplo, WebLogic Server de BEA admite esta función desde la versión 6.0, Open.

Symphony Project también admite esta función. Las etiquetas de almacenamiento en búfer JSP pueden almacenar en búfer fragmentos de páginas y páginas enteras. Cuando se ejecuta la página JSP, si el fragmento de destino ya está en el búfer, no es necesario ejecutar el código que generó el fragmento.

El almacenamiento en búfer a nivel de página captura las solicitudes de una URL específica y almacena en búfer toda la página resultante. Esta función es extremadamente útil para carritos de compras, catálogos y páginas de inicio de portales. Para tales aplicaciones, los buffers a nivel de página pueden guardar los resultados de la ejecución de la página para solicitudes posteriores.

23. Elegir un mecanismo de referencia adecuado.

En un sistema de aplicación JSP típico, los encabezados y pies de página generalmente se extraen y luego se introducen según sea necesario. Actualmente, existen dos formas principales de introducir recursos externos en páginas JSP: incluir instrucciones e incluir acciones.

Incluye directivas: por ejemplo

gt. Esta directiva importa el recurso especificado en el momento de la compilación. Las páginas con directivas de inclusión y recursos específicos se combinan en un solo archivo antes de la compilación. Los recursos externos a los que se hace referencia se determinan en tiempo de compilación, lo que es más eficiente que determinar el recurso en tiempo de ejecución.

Incluye acciones: por ejemplo

/ gt;. Esta acción traerá los resultados generados después de ejecutar la página especificada. Debido a que se completa en tiempo de ejecución, el control de los resultados de salida es más flexible. Sin embargo, utilizar la operación de inclusión solo es rentable cuando el contenido al que se hace referencia cambia con frecuencia o la página a la que se hace referencia no se puede determinar hasta que se produce una solicitud de la página de inicio.

24. Borrar las sesiones que ya no sean necesarias con prontitud.

Para borrar las sesiones inactivas, muchos servidores de aplicaciones tienen un tiempo de espera de sesión predeterminado de 30 minutos. Cuando el servidor de aplicaciones necesita guardar más sesiones, si la capacidad de la memoria es insuficiente, el sistema operativo transferirá algunos datos de la memoria al disco y el servidor de aplicaciones también puede usarlos según el "usado recientemente"

El algoritmo reciente

Usado) descarga algunas sesiones inactivas en el disco e incluso puede causar una excepción de "memoria insuficiente". En sistemas de gran escala, serializar sesiones es muy caro. Se debe llamar a httpsession cuando la sesión ya no sea necesaria. El método () utilizado para borrar la sesión rápidamente no es válido. El método httpsession.invalid() normalmente se puede llamar en la página de salida de la aplicación.

25. No declarar el array como: public static final.

26. Discusión sobre la eficiencia transversal de HashMap

Hay dos métodos para atravesar pares de clave y valor en HashMap: Map

..... ... ......//Primer ciclo

Establecer lt string gtappFieldDefIds = paramap . keyset();

for(String appFieldDefId: appFieldDefIds){

cadena[]valores = paramap . get(appFieldDefId);

......

}

//Segundo período

for(Entry lt; String, String[] gt; entrada: paraMap.entrySet()){

string appFieldDefId = entrada getkey();

string[] valores = entrada . getvalue();

.......

}

La primera implementación no es tan buena como la segunda. de eficiente.

El análisis es el siguiente: Configuración

El código es el siguiente:

Configuración pública ltK gt key set (){

Establecer ltK gtks = contraseña Conjunto de claves;

Return (ks! = null? ks: (conjunto de claves = nuevo conjunto de claves());

}

Extensión de conjunto de claves de clase privada AbstractSet ltK gt{

Iterador público ltK gt iterator(){

Devolver nuevo iterador de clave();

}

public int size() {

Tamaño de retorno;

}

El booleano público contiene (objeto o) {

retorno contiene clave (o);

}

eliminación booleana pública (objeto o) {

Devuelve hashmap. removeentryforkey(o)

. p>

}

Public void clear() {

hashmap clear();

De hecho, devuelve una clase privada KeySet, que hereda de AbstractSet e implementa la interfaz Set

Veamos la sintaxis del bucle for/in

for(statement: expresión_r)

Declaración

En la fase de implementación, se traduce en las siguientes categorías

for(iterator ltE gt# i = (expresión_r). iteración. dispositivo();# I . hash next();){

declaración = # I . next();

Declaración

}

Entonces, en la primera declaración de for(string appfielddefid:appfielddefids), HashMap.keySet() llama a iterator(), que llama a newKeyIterator().

El iterador ltK gtnewKeyIterator(). ) {

Devuelve nueva clave iterator();

}

La clase privada KeyIterator extiende HashIterator ltK gt{

public K next() {

Devuelve nextEntry(). getKey();

}

}

Así que todavía se llama.

En el segundo bucle (Entry

clase privada EntryIterator extiende el mapa HashIterator lt. Entry ltk, V gt gt{

Mapa público. Entry ltk, V gtnext (){

Devuelve la siguiente entrada();

}

}

En este momento, el primer bucle obtiene la clave. y el segundo bucle obtiene la entrada de HashMap.

La eficiencia se refleja en el segundo bucle, que puede obtener directamente los valores clave y de valor.

El primer bucle aún debe usarse. get(Object key) de HashMap para obtener el valor

Ahora mire el método get(Object key) de HashMap.

público V get(clave de objeto){

objeto k = máscara nulo(clave);

int hash = hash(k);

int i = indexFor(hash, table.length); //Entrada[] tabla

Entrada ltk, V gte = tabla;

mientras (verdadero) { p>

if (e == null)

Devuelve nulo

if (e . hash == hash amp; ampeq(k, e.key))

Devuelve e.value

e = e next;

}

}

De hecho, es usar el hash nuevamente El valor toma la entrada correspondiente y la compara para obtener el resultado, por lo que usar el primer bucle intermedio equivale a ingresar la entrada HashMap dos veces.

El segundo ciclo obtiene directamente la clave y el valor después de obtener el valor de Entrada, que es más eficiente que el primer ciclo. De hecho, según el concepto de Mapa, debería ser mejor utilizar el segundo bucle, que es el par de valores de clave y valor. No es una buena opción operar la clave y el valor por separado aquí.