Los métodos en línea aumenta el rendimiento reduciendo el número de llamadas a métodos que hace nuestro programa. La JVM alínea métodos que devuelven constantes o sólo acceden a campos internos. Para tomar ventaja de los métodos en línea podemos hacer una de estas dos cosas. Podemos hacer que un método aparezca atractivo para que la JVM lo ponga en línea o ponerlo manualmente en línea si no rompe nuestro modelo de objetos. La alineación manual en este contexto sólo significa poner el código de un método dentro del método que lo ha llamado.
El alineamiento automático de la JVM se ilustra con este pequeño ejemplo:
public class InlineMe {
int counter=0;
public void method1() {
for(int i=0;i<1000;i++)
addCount();
System.out.println("counter="+counter);
}
public int addCount() {
counter=counter+1;
return counter;
}
public static void main(String args[]) {
InlineMe im=new InlineMe();
im.method1();
}
}
En el estado actual, el método addCount no parece muy atractivo para el detector en línea de la JVM porque el método addCount devuelve un valor. Para ver si éste método está en línea compilamos el ejemplo con este perfil activado:
Esto genera un fichero de salida java.hprof.txt. Los 10 primeros métodos se parecerán a esto:java -Xrunhprof:cpu=times InlineMe
CPU TIME (ms) BEGIN (total = 510)
Thu Jan 28 16:56:15 1999
rank self accum count trace method
1 5.88% 5.88% 1 25 java/lang/Character.
<clinit>
2 3.92% 9.80% 5808 13 java/lang/String.charAt
3 3.92% 13.73% 1 33 sun/misc/
Launcher$AppClassLoader.
getPermissions
4 3.92% 17.65% 3 31 sun/misc/
URLClassPath.getLoader
5 1.96% 19.61% 1 39 java/net/
URLClassLoader.access$1
6 1.96% 21.57% 1000 46 InlineMe.addCount
7 1.96% 23.53% 1 21 sun/io/
Converters.newConverter
8 1.96% 25.49% 1 17 sun/misc/
Launcher$ExtClassLoader.
getExtDirs
9 1.96% 27.45% 1 49 java/util/Stack.peek
10 1.96% 29.41% 1 24 sun/misc/Launcher.<init>
Si cambiamos el método addCount para que no devuelva ningún valor, la JVM lo pondrá en línea durante la ejecución. Para amigable el código en línea reemplazamos el método addCount con esto:
public void addCount() {
counter=counter+1;
}
Y ejecutamos el perfil de nuevo:
Esta vez el fichero de salida java.hprof.txt debería parecer diferente. El método addCount se ha ido. Ha sido puesto en línea!java -Xrunhprof:cpu=times InlineMe
CPU TIME (ms) BEGIN (total = 560)
Thu Jan 28 16:57:02 1999
rank self accum count trace method
1 5.36% 5.36% 1 27 java/lang/
Character.<clinit>
2 3.57% 8.93% 1 23 java/lang/
System.initializeSystemClass
3 3.57% 12.50% 2 47 java/io/PrintStream.<init>
4 3.57% 16.07% 5808 15 java/lang/String.charAt
5 3.57% 19.64% 1 42 sun/net/www/protocol/file/
Handler.openConnection
6 1.79% 21.43% 2 21 java/io/InputStreamReader.fill
7 1.79% 23.21% 1 54 java/lang/Thread.<init>
8 1.79% 25.00% 1 39 java/io/PrintStream.write
9 1.79% 26.79% 1 40 java/util/jar/
JarFile.getJarEntry
10 1.79% 28.57% 1 38 java/lang/Class.forName0
Si hemos usado préviamente otros mecanimos de bloqueos porque el punto de rendimiento con los métodos sincronizados merece la pena re-visitar ese código y incorporarle los bloqueos en línea de Java 2.
En el siguiente ejemplo que está creando monitores para el bloque sincronizado podemos alcanzar un 40% de aumento de velocidad. El tiempo empleado fue 14ms usando JDK 1.1.7 y sólo 10ms con Java 2 en una máquina Sun Ultra 1.
class MyLock {
static Integer count=new Integer(5);
int test=0;
public void letslock() {
synchronized(count) {
test++;
}
}
}
public class LockTest {
public static void main(String args[]) {
MyLock ml=new MyLock();
long time = System.currentTimeMillis();
for(int i=0;i<5000;i++ ) {
ml.letslock();
}
System.out.println("Time taken="+
(System.currentTimeMillis()-time));
}
}
Optimización Adaptativa
El Java Hotspot no incluye un compilador interno JIT pero en su lugar compila y pone métodos en línea que parecen ser los más utilizados en la aplicación. Esto significa que en el primer paso por los bytescodes Java son interpretados como si ni tubieramos un compilador JIT. Si el código aparece como un punto caliente de nuestra aplicación el compilador Hotspot compilará los bytecodes a código nativo que es almacenado en un caché y los métodos en línea al mismo tiempo.
Una ventaja de la compilazión selectiva sobre un compilador JIT es que el compilador de bytes puede gastar más tiempo en generar alta optimización para áreas que podrían provocar la mayor optimización. el compilador también puede compiladr código que podría ejecutarse mejor en modo intérprete.
En el versiones anteriores de la Java HotSpot VM donde no era posible optimizar código que no estába actualmente en uso. El lado negativo de esto es que la aplicación estaba en una enorme bucle y el optimizador no podía compilar el código del área hasta que el bucle finalizara. Posteriores versiones de la Java Hotspot VM, usa un reemplazamiento en la pila, significando que el código puede ser compilado en código nativo incluso si está siendo utilizado por el intérprete.
Recolección de Basura Mejorada
El recolector de basura usado en el la Java HotSpot VM presenta varias mejoras sobre los recolectores de basura existentes. El primero es que el recolector se ha convertido en un recolector de basura totalmente seguro. Lo que esto significa es que el recoelcto sabe exactamente qué es una referencia y qué son sólo datos. El uso de referencias directas a objetos en el heap en una Java HotSpot VM en lugar de usar manejadores de objetos. Este incremento del conocimiento significa que la fragmentación de memoria puede reducirse con un resultado de una huella de memoria más compacta.
La segunda mejora es el uso de cópiado generacional. Java crea un gran número de objetos en la pila y frecuentemente estos objetos tenían una vida muy corta. Reemplazado los objetos creados recientemente por un cubo de memoria, esperando a que el cubo se lene y luego sólo copiando los objetos vivos restantes a una nuevo área del bloque de memoria que el cubo puede liberar en un bloque. Esto significa que la JVM no tiene que buscar un hueco para colocar cada nuevo objeto en la pila y significa que se necesita manejar secciones de memoria más pequeñas de una vez.
Para objetos viejos el recolector de basura hace un barrido a través del hepa y compacta los huecos de los objetos muertos directamente, eliminando la necesidad de una lista libre usada en algoritmos de recolección de basura anteriores.
El tercer área de mejora es eliminar la percepción de pausar en la recolección de basura escalonando la compactaciónde grandes objetos liberados en pequeños grupos y compactándolos de forma incremental.
Sincronización Rápida de Threads
La Java HotSpot VM also mejora el código de sincronización existente. Los bloques y métodos sincronizados siempren representan una sobrecarga cuando se ejecutan en una JVM. El Java HotSpot implementa los propios puntos de entrada y salida del monitor de sincroniación y no dependen del Sistema Operativo local para proporcionar esta sincronización. Este resultado es un gran aumento de la velocidad especialmente en las frecuentes aplicaciones GUI sincronizadas.
El compilador JIT se puso disponible como una actualización de rendimiento en la versión Java Development Kit (JDKTM) 1.1.6 y ahora es una herramienta estándard invocada siempre qu eusamos el intérprete java en la versión de la plataforma Java 2. Podemos desactivar el uso del compilador JIT usando la opción -Djava.compiler=NONE en la JVM.
Se usa la clase java.lang.Compiler para cargar la librería nativa y empezar la inicialización dentro del compilador JIT.
Cuando la JVM llama a un método Java, usa un método llamante como especificado en el bloque método del objeto class cargado. La JVM tiene varios métodos llamantes, por ejemplo, se utiliza un llamante diferente si el método es sincronizado o si es un método nativo.
El compilador JIT usa su propio llamante. Las versión de Sun chequean el bit de aceso al método por un valor ACC_MACHINE_COMPILED para notificarle al intérprete que el código de esté método ya está compilado y almacenado en las clases cargadas.
Una vez que el código ha sido compilado se activa el bit ACC_MACHINE_COMPILED, que es usado en la plataforma Sun.
Unix:
export JIT_ARGS="trace exclude(InlineMe.addCount
InlineMe.method1)"
$ java InlineMe
Initializing the JIT library ...
DYNAMICALLY COMPILING java/lang/System.getProperty
mb=0x63e74
DYNAMICALLY COMPILING java/util/Properties.getProperty
mb=0x6de74
DYNAMICALLY COMPILING java/util/Hashtable.get
mb=0x714ec
DYNAMICALLY COMPILING java/lang/String.hashCode
mb=0x44aec
DYNAMICALLY COMPILING java/lang/String.equals
mb=0x447f8
DYNAMICALLY COMPILING java/lang/String.valueOf
mb=0x454c4
DYNAMICALLY COMPILING java/lang/String.toString
mb=0x451d0
DYNAMICALLY COMPILING java/lang/StringBuffer.<init>
mb=0x7d690
<<<< Inlined java/lang/String.length (4)
Observa que los métodos en línea como String.length está exentos. El metodo String.length también es un método especial y es normalmente compilado en un atajo de bytecodes interno para el intérprete java. Cuando usamos el compilador JIT estás optimizaciones proporcionadas por el intérprete Java son desactivadas para activar el compilador JIT para entender qué método está siendo llamado.
El compilador JIT también consigue una ganancias menores de rendimiento al no prechequear ciertas condiciones Java como punteros Null o excepciones de array fuera de límites. La única forma de que el compilador JIT conozca una excepción de puntero null es mediante una señal lanzada por el sistema operativo. Como la señal viene del sistema operativo y no de la JVM, nuestro programa mejora su rendimiento. Para asegurarnos el mejor rendimiento cuando se ejecuta una aplicación con el JIT, debemos asegurarnos de que nuestro código está muy limpio y sin errores como excepciones de punteros null o arrays fuera de límites.
Podríamos querer desactivar el compilador JIT su estámos ejecutando la JVM en modo de depuración remoto, o si queremos ver los números de líneas en vez de la etiqueta (Compiled Code) en nuestos seguimientos de pila. Para desactivar el compilador JIT, suministramos un nombre no válido o un nombre en blanco para el compilador JIT cuando invoquemos al intérprete. Los siguientes ejemplos muestran el comando javac para compilar el código fuente en bytecodes, y dos formas del comando java para invocar al intérprete sin el compilador JIT:
ojavac MyClass.java java -Djava.compiler=NONE MyClass
javac MyClass.java java -Djava.compiler="" MyClass
El fichero class se refiere a un campo de la clase como a una referencia a un entrada en el almacen de constantes. Esto significa que mientras las referencias permanezcan iguales no importa los valores almacenados en el almacen de constantes. Este conocimiento es explotado por varias herramientas que reescriben los nombres de los campos y de los métodos en el almacen de constantes con nombres recortardos. Esta técnica puede reducir el tamaño del fichero class en un porcentaje significante con el beneficio de que un fichero class más pequeño significa un tiempo de descarga menor.