Uno de los mayores retos al desarrollar grandes aplicaciones Java es hacer que la aplicaci�n alcance sus criterios de rendimiento. Este cap�tulo muestra c�mo aumentar el rendimiento de la aplicaci�n.
�Mejorar el Rendimiento por Dise�o
Las restricciones del ancho de banda en las redes alrededor del mundo hacen de las operaciones basadas en red potenciales cuellos de botella que pueden tener un importante impacto en el rendimiento de las aplicaciones. Muchas aplicaciones de red est�n disa�adas para usar almacenes de conexiones y por ello pueden reutilizar conexiones de red existentes y ahorrar el tiempo y la sobrecarga que conllevan el abrir y cerrar conexiones de red.
Junto con el almacen de conexiones, hay otras caracter�sticas que podemos dise�ar dentro de nuestros programas para mejorar el rendimiento. Este cap�tulo explica c�mo podemos dise�ar un applet para que descargue ficheros y recursos de forma m�s eficiente, o dise�ar un programa basado en threads para usar un almacen de threads para ahorrarnos el costoso proceso de arrancar threads.
�Mejorar la Velocidad de Descarga de un Applet
El rendimiento de descarga de un applet se refiere al tiempo que tarda el navegador en descargar todos los ficheros y recursos que necesita para arrancar el applet. Un factor importante que afecta al rendimiento de la descarga del applet es el n�mero de veces que tiene que solicitar datos al servidor. Podemos reducir el n�mero de peticiones empaquetando las imagenes del applet en un fichero class, o usando un archivo JAR.
�Empaquetar Im�genes en un Clase
Normalmente, si un applet tiene seis im�genes de botones se traducen en seis solicitudes adicionales al servidor para cargar esos ficheros de im�genes. Seis solicitudes adicionales podr�an no parecer demasiadas en una red interna, pero en las conexiones de baja velocidad y eficiencia, esas solicitudes adicionales pueden tener un impacto muy negativo en el rendimiento. Por eso, nuestro �ltimo objetivo ser� cargar el applet tan r�pido como sea posible.
Una forma de almacenar im�genes en un fichero class es usar un esquema de codificaci�n ASCII como X-PixMap (XPM). De esta forma, en vez de mantener la im�genes en ficheros GIF en el servidor, los ficheros son codificados como un Strings y son almacenados en un s�lo fichero class.
Este c�digo de ejemplo usa p�quetes del ganador de la JavaCup del JavaOne 1996, que conten�a las clases XImageSource y XpmParser. Estas clases proporciona todo los necesario para leer un fichero XPM. Podemos ver esto ficheros en SunSite.
Para el proceso inicial de codificaci�n, hay un n�mero de herramientas gr�ficas que podemos usar para crear fichero XPM. En Solaris podemos usar ImageTool o una variedad de otros GNU image packages. Podemos ir a la web site Download.com para obtener software de codificaci�n para las plataformas Windows.
El siguiente c�digo extraido del ejemplo de c�digo MyApplet que carga im�genes. Podemos ver el String codificado en la definici�n XPM de im�genes.
La clase Toolkit crea un objeto Image para cada imagen desde el objeto fuente XPM Image.
Toolkit kit = Toolkit.getDefaultToolkit(); Image image; image = kit.createImage (new XImageSource (_reply)); image = kit.createImage (new XImageSource (_post)); image = kit.createImage (new XImageSource (_reload)); image = kit.createImage (new XImageSource (_catchup)); image = kit.createImage (new XImageSource (_back10)); image = kit.createImage (new XImageSource (_reset)); image = kit.createImage (new XImageSource (_faq));
La alternativa t�cnica de abajo usa ficheros GIF. Requiere una petici�n al servidor para cada imagen cargada.
Image image;
image = getImage ("reply.gif");
image = getImage ("post.gif");
image = getImage ("reload.gif");
image = getImage ("catchup.gif");
image = getImage ("back10.gif");
image = getImage ("reset.gif");
image = getImage ("faq.gif");
Esta t�cnica reduce el trafico de la red porque todas las im�genes est�n disponibles en un s�lo fichero class.
- Usar im�genes XPM codificadas hace m�s grande el fichero de la clase, pero el n�mero de peticiones de red es menor.
- Al hacer que las definiciones de im�genes XPM formen parte del fichero class, hacemos que el proceso de carga de im�genes sea parte de la carga normal del fichero class del applet sin clases extras.
Una vez cargado, podemos usar las im�genes para crear botones u otros componentes del interface de usuario. El siguiente segmento de c�digo muestra c�mo usar la im�genes con la clase javax.swing.JButton.
ImageIcon icon = new ImageIcon (
kit.createImage (
new XImageSource (_reply)));
JButton button = new JButton (icon, "Reply");
�Usar Ficheros JAR
Cuando un applet consta de m�s de un fichero, podemos mejorar el rendimiento de la descarga con ficheros JAR. Un fichero JAR contiene todos los ficheros del applet en un s�lo fichero m�s r�pido de dsacargar. Mucha parte del tiempo ahorrado viene de la reducci�n del n�mero de conexiones HTTP que el navegador tiene que hacer.
El cap�tulo: Desarrollar Nuestra Aplicaci�n tiene informaci�n sobre c�mo crear y firmar ficheros JAR.
El c�digo HTML de abajo usa la etiqueta CODE para especificar el ejecutable del applet MyApplet, y la etiqueta ARCHIVE especifica el fichero JAR que contiene todos los ficheros relacionados con MyApplet. El ejecutable especificado por la etiqueta CODE algunas veces es llamado code base.
Por razones de seguridas los ficheros JAR listados por el par�metro archive deben estar en el mismo directorio o subdirectorio que el codebase del applet. Si no se suministra el par�metro codebase el directorio de donde se carg� el applet se usa como el codebase.
El siguiente ejemplo especifica jarfile como el fichero JAR que contiene todos los ficheros relacionados para el ejecutable MyApplet.class.
<APPLET CODE="MyApplet.class" ARCHIVE="jarfile" WIDTH="100" HEIGHT="200"> </APPLET>
Si la descarga del applet usa m�ltiples ficheros JAR como se muestra en el siguiente segmento HTML, el ClassLoader carga cada fichero JAR cuando el applet arranca. Por eso, si nuestro applet usa algunos ficheros de recursos de forma infrecuente, el fichero JAR que contiene esos ficheros es descargado sin importar si los recursos van a ser usados durante la sesi�n o no.
<APPLET CODE="MyApplet.class" ARCHIVE="jarfile1, jarfile2"
WIDTH="100" HEIGHT="200">
</APPLET>
Para mejorar el rendimiento cuando se descargan fichero no usados de forma frecuente, ponemos los ficheros usados m�s frecuentemente dentro de un fichero JAR y los ficheros menos usados en el directorio de la clase del applet. Los ficheros usados poco frecuentemente son localizados y descargados s�lo cuando el navegador los necesita.
�Almacen de Threads
El servidor de applets Java Developer Connection (JDC) y el Java Web Server hacen un uso extensivo del almacen de threads para mejorar el rendimiento. El almacen de threads es crear un suministro de threads durmientes al principio de la ejecuci�n. Como el proceso de arranque de un thread es muy caro en t�rminos de recursos del sistema, el almacen de threads hace el proceso de arrancada un poco m�s lento, pero aumenta el rendimiento en tiempo de ejecuci�n porque los threads durmientes (o suspendidos) s�lo se despiertan cuando cuando son necesarios para realizar nuevas tareas.
Este c�digo de ejemplo tomado de la clase Pool.java muestra una forma de implementar la fusi�n de threads, En el constructor de la fusi�n (mostrado abajo), se inicializan y arrancan los WorkerThreads. La llamada al m�todo start ejecuta el m�todo run del WorkerThread, y la llamada a wait suspende el Thread mientras el Thread espera a que llegue un trabajo. La �ltima l�nea del constructor empuja el Thread durmiente hacia la pila.
public Pool (int max, Class workerClass)
throws Exception {
_max = max;
_waiting = new Stack();
_workerClass = workerClass;
Worker worker;
WorkerThread w;
for ( int i = 0; i < _max; i++ ) {
worker = (Worker)_workerClass.newInstance();
w = new WorkerThread ("Worker#"+i, worker);
w.start();
_waiting.push (w);
}
}
Junto al m�todo run, la clase WorkerThread tiene un m�todo wake. Cuando viene el trabajo, se llama al m�todo wake, que asigna los datos y notifica al WorkerThread durmiente (el inicializado por el Pool) para recuperar la ejecuci�n. El m�todo wake llama a notify hace que el WorkerThread bloqueado salga del estado de espera, y se ejecuta el m�todo run de la clase HttpServerWorker. Una vez realizado el trabajo, el WorkerThread se pone de nuevo en el Stack (asumiento que el Pool de threads no est� lleno) o termina.
synchronized void wake (Object data) {
_data = data;
notify();
}
synchronized public void run(){
boolean stop = false;
while (!stop){
if ( _data == null ){
try{
wait();
}catch (InterruptedException e){
e.printStackTrace();
continue;
}
}
if ( _data != null ){
_worker.run(_data);
}
_data = null;
stop = !(_push (this));
}
}
En este alto nivel, el trabajo entrante es manejado por el m�todo performWork en la clase Pool. Cuando viene el trabajo, se saca de la pila un WorkerThread existente (o se crea uno nuevo si el Pool est� vac�o). El WorkerThread durmiente es activado mendiate una llamada a su m�todo wake.
public void performWork (Object data)
throws InstantiationException{
WorkerThread w = null;
synchronized (_waiting){
if ( _waiting.empty() ){
try{
w = new WorkerThread ("additional worker",
(Worker)_workerClass.newInstance());
w.start();
}catch (Exception e){
throw new InstantiationException (
"Problem creating
instance of Worker.class: "
+ e.getMessage());
}
}else{
w = (WorkerThread)_waiting.pop();
}
}
w.wake (data);
}
El constructor de la clase HttpServer.java crea un nuevo ejemplar Pool para servir ejemplares de la clase HttpServerWorker. Los ejemplares HttpServerWorker se crean y almacenan como parte de los datos WorkerThread. Cuando se activa un WorkerThread mediante una llamada a su m�todo wake, el ejemplar HttpServerWorker es invocado mediante su m�todo run.
try{
_pool = new Pool (poolSize,
HttpServerWorker.class);
}catch (Exception e){
e.printStackTrace();
throw new InternalError (e.getMessage());
}
Este c�digo est� en el m�todo run de la clase HttpServer.java. Cada vec que viene una petici�n, el dato es inicializado y el Thread empieza el trabajo.
Nota: Si creamos un nuevo Hashtable por cada WorkerThread provocamos demasiada sobrecarga, s�lo modificamos el c�digo para que no use la abstraci�n Worker.
try{
Socket s = _serverSocket.accept();
Hashtable data = new Hashtable();
data.put ("Socket", s);
data.put ("HttpServer", this);
_pool.performWork (data);
}catch (Exception e){
e.printStackTrace();
}
El almacen de threads es una t�cnica efectiva de ajuste de rendimiento que coloca el caro proceso de arranque de threads en la arrancada de la aplicaci�n. De esta forma, el impacto negativo en el rendimiento ocurre s�lo una vez durante el arrancada del programa donde se nota menos.
�Almacen de Conexiones
Si hemos usado SQL u otra herramienta similar para conectarnos con una base de datos y act�ar sobre los datos, probablemente habremos notado que la obteneci�n de la conexi�n y el login es la parte que tarda m�s tiempo. Una aplicaci�n puede f�cilmente tardar varios segundos cada vez que necesita establecer una conexi�n.
El varsiones anteriores a JDBC 2.0 cada sesi�n de base de datos requer�a una nueva conexi�n y un login incluso si la conexi�n anterior usaba la misma tabla y cuenta de usuario. Si est�mos usando versioens anteriores al JDBC 2.0 y queremos mejorar el rendimiento, podemos cachear las conexiones JDBC.
Las conexiones cacheadas se mantienen un objeto pool en tiempo de ejecuci�n y pueden ser utilizadas y reutilizadas cuando las necesite la aplicaci�n. Una forma de implementar un objeto pool es hacer una una simple hashtable de objetos conection. Sin embargo, una forma m�s sencilla de hacerlo es escribir un driver JDBC envuelto que es un intermediario entre la aplicaci�n y la base de datos.
La envoltura trabaja particulamente en los Beans de Enterprise que san persistencia manejada por el Bean por dos razones: 1) S�lo se carga una clase Driver por cada Bean, y 2) los detalles espec�ficos de la conexi�n se manejan fuera del Bea.
Esta secci�n explica c�mo escribir una clase Driver JDBC envuelta.
�Clases Wrapper
El Driver JDBC envuelto creado para estos ejemplos consta de las siguientes clases:
- JDCConnectionDriver
- JDCConnectionPool
- JDCConnection
�Driver de Conexi�n
La clase JDCConnectionDriver.java implementa el interface java.sql.Driver, que proporciona m�todo para cargar drivers y crear nuevas conexiones a bases de datos.
Un objeto JDCConnectionManager es creado por una aplicaci�n que pretende una conexi�n con una base de datos. La aplicaci�n proprociona el ULR para la base de datos, el ID del usuario y la password.
El constructor JDCConnectionManager hace esto.
- Registra el objeto JDCConnectionManager con DriverManager.
- Carga la clase Driver pasada al constructor por el programa llamante.
- Inicializa un objeto JDCConnectionPool para las conexiones con la URL de la base de datos, el ID y el password del usuario pasados al constructor por el programa llamante.
public JDCConnectionDriver(String driver,
String url,
String user,
String password)
throws ClassNotFoundException,
InstantiationException,
IllegalAccessException,
SQLException {
DriverManager.registerDriver(this);
Class.forName(driver).newInstance();
pool = new JDCConnectionPool(url, user, password);
}
Cuando el programa llamante necesita una conexi�n con la base de datos, llama al m�todo JDCConnectionDriver.connect, que a su vez, llama al m�todo JDCConnectionPool.getConnection.
�Almacen de Conexiones
La clase JDCConnectionPool.java tiene conexiones disponibles para el programa llamando en su m�todo getConnection. Este m�todo busca una conexi�n disponible en el almacen de conexiones. Si no hay ninguna disponible, crea una nueva conexi�n. Si hay una conexi�n disponible en el almacen, el m�todo getConnection alquila la conexi�n y la devuelve al programa llamante.
public synchronized Connection getConnection()
throws SQLException {
JDCConnection c;
for(int i = 0; i < connections.size(); i++) {
c = (JDCConnection)connections.elementAt(i);
if (c.lease()) {
return c;
}
}
Connection conn = DriverManager.getConnection(
url, user, password);
c = new JDCConnection(conn, this);
c.lease();
connections.addElement(c);
return c;
}
La clase JDCConnection.java representa una conexi�n JDBC en el almacen de conexiones, y esencialmente es una envoltura alrededor de un conexi�n real JDBC. El objeto JDCConnection mantiene una bandera de estado para indicar si la conexi�n est� en uso y el momento en que la conexi�n se sac� del almacen. Este tiempo es usado por la clase ConnectionReaper.java para identificar las conexiones colgadas.
�Bloqueos y Cuelgues
Mientras que muchos clientes y servidores de bases de datos tiene formas de manejar los bloqueos y los cuelgues y no tenemos que preocuparnos de escribir c�digo para manejar estas situaciones, muchos de los nuevos modelos de base de datos ligeros distribuidos no est�n tan bien equipados. La clase conection pool proporciona una cosechador de conexiones muerta para manejar dichas situacciones.
La clase ConnectionReaper decide que una clase est� muerta cuando se cumplen las siguientes condiciones.
- La conexi�n est� marcada como que est� en uso.
- La conexi�n es m�s vieja que tiempo de timeout preseleccionado.
- La conexi�n falla en un chequeo de validaci�n.
El chequeo de validaci�n ejecuta una simple consulta SQL sobre la conexi�n para ver si lanza una excepci�n. En este ejemplo, el m�todo de validaci�n solicita una descripci�n de alto nivel de las tablas de la base de datos. Si una conexi�n falla el test de validaci�n, se cierra, se inicia una nueva conexi�n con la base de datos y se a�ade al almacen de conexiones.
public boolean validate() {
try {
conn.getMetaData();
}catch (Exception e) {
return false;
}
return true;
}
�Cerrar Conexiones
La conexi�nes devuelta al almacen de conexiones cuando el programa llamante llama al m�todo JDCConnection.close en su cla�sulafinally.
public void close() throws SQLException {
pool.returnConnection(this);
}
�Aplicaci�n de Ejemplo
Usamos un almacen de conexiones en una aplicaci�n de forma similar a como usar�amos cualquiere otro driver JDBC. Aqu� est� el c�digo de un RegistrationBean controlado por el Bean. Este RegistrationBean se ha adaptado desde la casa de subastas de JavaBeans enterprise descrito en los coa�tulo 1 -3.
Cuando se crea el primer objeto RegistrationBean, crea un ejemplar est�tico de la clase JDCConnectionDriver. Este objeto driver est�tico se registra a s� mismo con el DriverManager en el constructor JDCConnectionDriver poniendo disponibles la solicitudes de conexiones para todos los objetos RegistrationBean creados por la aplicaci�n cliente.
Pasar la URL como jdbc:jdc:jdcpool en el m�todo getConnection permite que el DriverManager corresponda la getConnection solicitada al driver registrado. El DriverManager usa un sencillo String para encontrar un driver disponible que pueda manejar URLs en ese formato.
public class RegistrationBean implements EntityBean{
private transient EntityContext ctx;
public String theuser, password;
public String creditcard, emailaddress;
public double balance;
//Static class instantiation
static {
try{
new pool.JDCConnectionDriver(
"COM.cloudscape.core.JDBCDriver",
"jdbc:cloudscape:ejbdemo",
"none", "none");
}catch(Exception e){}
}
public Connection getConnection()
throws SQLException{
return DriverManager.getConnection(
"jdbc:jdc:jdcpool");
}
}
�Caracter�sticas y Herramientas de Rendimiento
La nueva M�quina Virtual Java (JVM) tiene caracter�sticas para mejorar el rendimiento, y podemos usar un n�mero de herramientas para incrementar el rendimiento de la aplicaci�n o reducir el tama�o de los ficheros Class generados. Por eso las caracter�sticas y herramientas mejoran el rendimiento de nuestra aplicaci�n con muy pocos o casi ning�n cambio en en nuestra aplicaci�n.
�Caracter�sitcas de la M�quina Virtual Java (JVM)
La plataforma Java� 2 ha presentamo muchas mejoras de rendimiento sobre versiones anteriores, incluyendo asignaci�n m�s r�pida de memoria, reducci�n del tama�o de las clases, mejorar la recolecci�n de basura, monitores lineales y un JIT interno como est�ndard. Cuando usamo la nueva JVM de Java 2 nada m�s sacarla de la caja veremos una mejora, sin embargo para entendiendo como funciona el aumento de velocidad podemos ajustar nuestra aplicaci�n para exprimir hasta el �ltimo bit de rendimiento.
�M�todos en L�nea
La versi�n Java 2 de la JVM autom�ticamente alinea m�todos sencillo en el momento de la ejecuci�n. En una JVM sin optimizar, cada vez que se llama a un m�todo, se crea un nuevo marco de pila. La creacci�n de un nuevo marco de pila requiere recursos adicionales as� como alg�n re-mapeo de la pila, el resultado final crear nuevos marcos de pila incurre en una peque�a sobrecarga.
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.
java -Xrunhprof:cpu=times InlineMe
Esto genera un fichero de salida java.hprof.txt. Los 10 primeros m�todos se parecer�n a esto.
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.
java -Xrunhprof:cpu=times InlineMe
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!
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
�Sincronizaci�n
Los m�todos y objetos sincronizados en Java han tenido un punto de rendimiento adicional como el mecanismo utilizado para implementar el bloqueo de este c�digo usando un registro de monitor glogal que s�lo fue enhebrado en algunas �reas como la b�squeda de monitores existentes. En la versi�n Java 2, cada thread tiene un registro de monitor y por eso se han eliminado mucho de esos cuellos de botellas.
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));
}
}
�Java Hotspot
La m�quina virtual Java HotSpot es la siguiente generaci�n de implementaciones de la m�quina virtual de Sun Microsystem. La Java HotSpot VM se adhiere a la misma especificaci�n que la JVM de Java 2, y ejecuta los mismos bytecodes, pero ha sido redise�ada para lanzar nuevas tecnolog�as como los modelos de la optimizaci�n adaptativa y de recolecci�n de basura mejorada para mejorar dram�ticamente la velocidad del JVM.
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.
�Compiladores Just-In-Time
La herramienta m�s sencilla para mejorar el rendimiento de nuestra aplicaci�n el compilador Just-In-Time (JIT). Un JIT es un generador de c�digo que convierte los bytecodes Java en c�digo nativo de la m�quina. Los programas Java invocados con un JIT generalmente se ejecutan m�s r�pidos que cuando se ejecutan en bytecodes por el int�rprete. La Java Hotspot VM elimina la necesidad de un compilador JIT en muchos casos, sin embargo podr�an utilizar el compilador JIT en versiones anteriores.
El compilador JIT se puso disponible como una actualizaci�n de rendimiento en la versi�n Java Development Kit (JDK) 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.
��C�mo Funcionan los Compiladores JIT?
Los compiladores JIT se suministran como librer�as nativas dependientes de la plataforma. Si xiste la librer�a del compilador JIT, la JVM inicializa el JNI (Java Native Interface) para llamar a las funciones JIT disponibles en la librer�a en lugar de su funci�n equivalente del int�rprete.
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.
��Cuando es el compilado el c�digo JIT?
Cuando se llama a un m�todo por primera vez el compilador JIT compilad el bloque del m�todo a c�digo nativo y lo almacena en un bloque de c�digo.
Una vez que el c�digo ha sido compilado se activa el bit ACC_MACHINE_COMPILED, que es usado en la plataforma Sun.
��C�mo puedo ver lo que est� haciendo el compilador JIT?
La variable de entorno JIT_ARGS permite un control sencillo sobre el compilador JIT en Sun Solaris. Hay dos valores �tiles trace y exclude(list). Para excluir los m�todos del ejemplo InlineMe un mostrar un seguimiennto seleccionamos JIT_ARGS de esta forma.
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.
��C�mo Aprovechar la Ventaja del Compilador JIT?
Lo primero a recordar es que el compilador JIT consigue la mayor�a del aumento de velocidad la segunda vez que llama a un m�todo. El compilador JIT compila el m�todo completo en lugar de int�rpretarlo l�nea por l�nea que tambi�n puede ser una ganancia de rendimiento cuando se ejecuta una aplicaci�n el JIT activado. Esto significa que si el c�digo s�lo se llama una vez no veremos una ganancia de rendimiento significante. El compilador JIT tambi�n ignora los constructores de las clases por eso si es posible debemos mantener al m�nimo el c�digo en los constructores.
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.
javac MyClass.java java -Djava.compiler=NONE MyClass
o
javac MyClass.java java -Djava.compiler="" MyClass
�Herramientas de Terceras Partes
Hay otras herramientas disponibles incluidas aquellas que reducen el tama�o de los ficheros class generados. El fichero class Java contiene un �rea llamada almacen de constantes. Este almacen de constantes mantiene una lista de strings y otra informaci�n del fichero class para referencias. Unas de las piezas de informaci�n disponibles en el almacen de constantes son los nombres de los m�todos y campos.
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.
�An�lisis de Rendimiento
Otra forma de aumentar el rendimiento es con �nalisis de rendimiento. Los an�lisis de rendimientos buscan las ejecuci�n del programa apuntar donde podr�an estar los cuellos de botella y otros problemas de rendimiento como los picos de memoria. Una vez que sables donde est�n los puntos de problemas potenciales podemos cambiar nuestro c�digo para eliminar o reducir su impacto.
�Perfiles
Las M�quinas Vituales Java (JVMs) han tenido la habilidad de proporcionar sencillos informes de perfiles desde Java Development Kit (JDK) 1.0.2. Sin embargo, la informaci�n que ellos proporcionaban estaban limitadas a una lista de los m�todos que un programa hab�a llamado.
La plataforma Java� 2 proporciona muchas m�s capacidades de perfilado que las anteriormente disponibles y el an�lisis de estos datos generado se ha hecho m�s f�cil por la emergencia de un "Heap Analysis Tool" (HAT). Esta herramienta, como implica su nombre, nos permite analizar los informes de perfiles del heap. El heap es un bloque de memoria que la JVM usa cuando se est� ejecutando. La herramienta de an�lisis de heap nos permite generar informes de objetos que fueron usado al ejecutar nuestra aplicaci�n. No s�lo podemos obtener un listado de los m�todos llamados m�s frecuentemente y la memoria usada en llamar a esos m�todos, pero tambi�n podemos seguir los picos de memeoria. Los picos de memoria pueden tener un significante impacto en el rendimiento.
�Analizar un Programa
Para analizar el programa TableExample3 incluido en el directorio demo/jfc/Table de la plataforma Java 2, necesitamos generar un informe de perfil. El informa m�s sencillo de generar es un perfil de texto. Para generarlo, ejecutamos la aplicaci�n el par�metro -Xhprof. En la versi�n final de la plataforma Java 2, esta opci�n fue renombrada como -Xrunhprof. Para ver una lista de opciones actualmente disponibles ejecutamos el comando.
java -Xrunhprof:help Hprof usage: -Xrunhprof[:help]|[<option>=<value>, ...]
| Nombre de Opci�n y Valor | Descripci�n | Por Defecto |
| -------------------------- | --------------- | --------------- |
| heap=dump|sites|all | heap profiling | all |
| cpu=samples|times|old | CPU usage | off |
| monitor=y|n | monitor contention | n |
| format=a|b | ascii or binary output | a |
| file=<file> | write data to file | java.hprof(.txt for ascii) |
| net=<host>:<port> | send data over a socket | write to file |
| depth=<size> | stack trace depth | 4 |
| cutoff=<value> | output cutoff point | 0.0001 |
| lineno=y|n | line number in traces | y |
| thread=y|n | thread in traces? | n |
| doe=y|n | dump on exit? | y |
Example: java -Xrunhprof:cpu=samples,file=log.txt,
depth=3 FooClass
La siguiente invocaci�n crea una fichero de texto que podemos ver sin la herramienta de an�lisis de heap llamado java.hprof.txt cuando el programa genera un seguimiento de pila o sale. Se utiliza una invocaci�n diferente para crear un fichero binario para usarlo con la herramienta de an�lisis de heap.
java -Xrunhprof TableExample3 d:\jdk12\demo\jfc\Table> java -Xrunhprof TableExample3 Dumping Java heap ... allocation sites ... done.
La opci�n de perfil literalmente hace un diario de cada objeto creado en el heap, por incluso cuando arrancamos y paramos el peque�o progeama TableExample3 resulta un ficheo de informe de cuatro megabytes. Aunque la herramienta de an�lisis de heap usa una versi�n binaria de este fichero y proporciona un sumario, hay algunas cosas r�pidas y f�ciles que podemos aprender desde el fichero de texto sin usar la herramienta de an�lisis de heap.
Nota: Para listar todas las opciones disponibles, usamos
�Ver el Fichero de Texto
Elegimos un fichero que pueda manejar grandes ficheros y vamos hasta el final del fichero. Podr�a haber cientos o miles de l�neas, por eso un atajo es buscar las palabras SITES BEGIN. Ver�amos una lista de l�nea que empezar�an un tango creciente de n�meros seguido por dos n�meros de porcentaje. La primera entrada en la lista ser�a similar a este ejemplo.
SITES BEGIN (ordered by live bytes)
Sun Dec 20 16:33:28 1998
� |
percent | � |
live | � |
alloc'ed | � |
stack | class |
| rank | self | accum | bytes | objs | bytes | objs | trace | name |
| 1 | 55.86% | 55.86% | 826516 | 5 | 826516 | 5 | 3981 | [S |
La notaci�n [S al final de la �ltima l�nea indica que es la primera entrada de un array de short, un tipo primitivo. Esto es lo esperado con aplicaciones (AWT). Los primeros cinco contadores bajo la cabecera objs significa que actualmente hay cinco de esos arrays, y s�lo ha habido cinco durante el tiempo de vida de esta aplicaci�n, y han ocupado 826516 bytes. La referencia clase de este objeto es el valor listado bajp stack trace. Para encontrar donde se creo est� objeto en este ejmplo, buscamos TRACE 3981. Veremos esto.
TRACE 3981.
java/awt/image/DataBufferUShort.<init>(
DataBufferUShort.java:50)
java/awt/image/Raster.createPackedRaster(
Raster.java:400)
java/awt/image/DirectColorModel.
createCompatibleWritableRaster(
DirectColorModel.java:641)
sun/awt/windows/WComponentPeer.createImage(
WComponentPeer.java:186)
El c�digo TableExample3 selecciona un scrollpane de 700 por 300. Cuando miramos el fuente de Raster.java, qu est� en el fichero src.jar, encontraremos estas sentencias en la l�nea 400.
case DataBuffer.TYPE_USHORT.
d = new DataBufferUShort(w*h);
break;
Los valores w y h son la anchura y altura de la llamada a createImage que arranca en TRACE 3981. El constructor DataBufferUShort crea un array de shorts.
data = new short[size];
donde size es w*h. Por eso, en teor�a deber�a hacer una entrada en el array para 210000 elementos. Buscamos una enteada por cada ejemplarizaci�n de esta clase buscando por trace=3981. Una de las cinco entradas se parecer� a esto.
OBJ 5ca1fc0 (sz=28, trace=3979,
class=java/awt/image/DataBufferUShort@9a2570)
data 5ca1670
bankdata 5ca1f90
offsets 5ca1340
ARR 5ca1340 (sz=4, trace=3980, nelems=1,
elem type=int)
ARR 5ca1670 (sz=420004, trace=3981, nelems=210000,
elem type=short)
ARR 5ca1f90 (sz=12, trace=3982, nelems=1,
elem type=[S@9a2d90)
[0] 5ca1670
Podemos ver que los valores de los datos de estas referencias de imagen en un array 5ca1670 que devuelve un alista de 210000 elementos short de tama�o 2. Esto significa qu este array usa 420004 bytes de memoria.
De este dato podemos concluir que el programa TableExample3 usa cerca de 0.5Mb para mapear cada tabal. Si la aplicaci�n de ejemplo se ejecuta en una m�quina con poca memoria, debemos asegurarnos de que no mantenemos referencias a objetos geandes o a im�genes que fueron construidas con el m�todo createImage.
�La Herramienta de An�lisis de Heap
Esta herramienta puede analizar los mismos datos que nosotros, pero requere un fichero de informe binario como entrada. Podemos generar un fichero de informa binario de esta forma.
java -Xrunhprof:file=TableExample3.hprof,format=b
TableExample3
Para generar el informe binario, cerramos la ventana TableExample3. El fichero de informe binario TableExample3.hprof se crea al salir del programa. La Herramienta de An�lisis de Heap arranca un servidor HTTP que analiza el fichero de perfil binario y muestra el resultado en un HTML que podemos ver en un navegador.
Podemos obtener una copia de la Herramienta de An�lisis de Heap de la site java.sun.com. Una vez instalado, ejecutamos los scripts shell y batch en el directorio bin instalado para poder ejecutar el servidor de la Herramienta de An�lisis de Heap de esta forma.
>hat TableExample3.hprof
Started HCODEP server on port 7000
Reading from /tmp/TableExample3.hprof...
Dump file created Tue Jan 05 13:28:59 PST 1999
Snapshot read, resolving...
Resolving 17854 objects...
Chasing references,
expect 35 dots.......................
Eliminating duplicate
references.........................
Snapshot resolved.
Server is ready.
La salida de arriba nos dice que nuestro servidor HTTP se ha arrancado en el puerto 7000. Para ver este informe introducimos la URL http://localhost:7000 o http://your_machine_name:7000 en nuestro navegador Web. Si tenemos problema en arrancar el servidor usando el script, podemos alternativamente ejecutar la aplicaci�n incluyendo el fichero de clases hat.zip en nuestro CLASSPATH y usar el siguiente comando.
java hat.Main TableExample3.hprof
La vista del informe por defecto contiene una lista de todas las clases. En la parte de abajo de est� p�gina inicial est�n las dos opciones b�sicas de informes.
Show all members of the rootset Show instance counts for all classes
Si seleccionamos el enlace Show all members of the rootset, veremos un alista de las siguientes referencias porque estas referencias apuntan a picos potenciales de memoria.
Java Static References Busy Monitor References JNI Global References JNI Local References System Class References
Lo que vemos aqu� son ejemplares en la aplicaci�n que tienen referencias a objetos que tienen un riesgo de no se recolectados para la basura. Esto puede ocurrir algunas veces en el caso del JNI su se asigna memoria para un objeto, la memoria se deja para que la libere el recolector de basura, y el recolector de basura no teine la informaci�n que necesita para hacerlo. En esta lista de referencias, estamos principalmente interesados en un gran n�mero de referencias a objetos o a objetos de gran tama�o.
El otro informe clave es el Show instance counts for all classes. Este lista los n�meros de llamadas a un m�todo particular. Los objetos array String y Character, [S y [C, est�n siempre en la parte superior de esta lista, pero algunos objetos son un poco m�s intrigantes. �Por qu� hay 323 ejemplares de java.util.SimpleTimeZone, por ejemplo?
5109 instances of class java.lang.String
5095 instances of class [C
2210 instances of class java.util.Hashtable$Entry
968 instances of class java.lang.Class
407 instances of class [Ljava.lang.String;
323 instances of class java.util.SimpleTimeZone
305 instances of class
sun.java2d.loops.GraphicsPrimitiveProxy
304 instances of class java.util.HashMap$Entry
269 instances of class [I
182 instances of class [Ljava.util.Hashtable$Entry;
170 instances of class java.util.Hashtable
138 instances of class java.util.jar.Attributes$Name
131 instances of class java.util.HashMap
131 instances of class [Ljava.util.HashMap$Entry;
130 instances of class [Ljava.lang.Object;
105 instances of class java.util.jar.Attributes
Para obtener m�s informaci�n sobre los ejemplares SimpleTimeZone, pulsamos sobre el enlace (la l�nea que empieza por 323). Esto listar� las 323 referencias y calcular� cu�nta memoria ha sido utilizada. en este ejemplo, se han utilizado 21964 bytes.
Instances of java.util.SimpleTimeZone class java.util.SimpleTimeZone java.util.SimpleTimeZone@0x004f48c0 (68 bytes) java.util.SimpleTimeZone@0x003d5ad8 (68 bytes) java.util.SimpleTimeZone@0x004fae88 (68 bytes) ..... Total of 323 instances occupying 21964 bytes.
Si pulsamos sobre uno de estos ejemplares SimpleTimeZone, veremos donde fue asignado este objeto.
Object allocated from.
java.util.TimeZoneData.<clinit>(()V) :
TimeZone.java line 1222
java.util.TimeZone.getTimeZone((Ljava/lang/String;)
Ljava/util/TimeZone;) :
TimeZone.java line (compiled method)
java.util.TimeZone.getDefault(
()Ljava/util/TimeZone;) :
TimeZone.java line (compiled method)
java.text.SimpleDateFormat.initialize(
(Ljava/util/Locale;)V) :
SimpleDateFormat.java line (compiled method)
En este ejemplo el objeto fue asignado desde TimeZone.java. El fichero fuente de este fichero est�n el fichero est�ndard src.jar, y examinando este fichero, podemos ver que de hehco hay cerca de 300 de estos objetos en memoria.
static SimpleTimeZone zones[] = {
// The following data is current as of 1998.
// Total Unix zones: 343
// Total Java zones: 289
// Not all Unix zones become Java zones due to
// duplication and overlap.
//-------------------------------------------
new SimpleTimeZone(-11*ONE_HOUR,
"Pacific/Niue" /*NUT*/),
Desafortunadamente, no tenemos control sobre la memoria usada en este ejemplo, porque es asignada cuando el programa hizo la primera solicitud al timezone por defecto. Sin embargo, esta misma t�cnica puede aplicarse para analizar nuestra propia aplicaci�n donde probablemente podr�amos hacer algunas mejoras.
��D�nde Gasta el Tiempo la Aplicaci�?
De nuevo, podemos usar el par�metro -Xrunhprof para obtener informaci�n sobre el tiempo que gasta la aplicaci�n procesando un m�todo particular.
Podemos usar una o dos opciones de perfil de CPU para conseguir esto. La primera opci�n es cpu=samples. Esta opci�n devuelve el resultado de un muestreo de ejecuci�n de threads de la M�quina Virtual Java con un conteo estad�stico de la frecuencia de ocurrencia con que se usa un m�todo particular para encontrar secciones ocupadas de la aplicaci�n. La segunda opci�n es cpu=times, que mide el tiempo que tardan los m�todos individuales y genera un ranking del porcentaje total del tiempo de CPU ocupado por la aplicaci�n.
Usando la opci�n cpu=times, deber�amos ver algo como esto al final del fichero de salida.
CPU TIME (ms) BEGIN (total = 11080)
Fri Jan 8 16:40:59 1999
rank self accum count trace method
1 13.81% 13.81% 1 437 sun/
awt/X11GraphicsEnvironment.initDisplay
2 2.35% 16.16% 4 456 java/
lang/ClassLoader$NativeLibrary.load
3 0.99% 17.15% 46 401 java/
lang/ClassLoader.findBootstrapClass
Si constrastamos esto con la salida de cpu=samples, veremos la diferencia entre la frecuencia de ejecuci�nde un m�todo durante la ejecuci�n de la aplicaci�n comparada con el tiempo que tarda ese m�todo.
CPU SAMPLES BEGIN (total = 14520)
Sat Jan 09 17:14:47 1999
rank self accum count trace method
1 2.93% 2.93% 425 2532 sun/
awt/windows/WGraphics.W32LockViewResources
2 1.63% 4.56% 237 763 sun/
awt/windows/WToolkit.eventLoop
3 1.35% 5.91% 196 1347 java/
text/DecimalFormat.<init>
El m�todo W32LockView, que llama a una rutina de bloqueo de ventana nativa, se llama 425 veces. Por eso cuando aparecen en los threads activos porque tambi�n toman tiempo para completarse. En contraste, el m�todo initDisplay s�lo se le llama una vez, pero es el m�todo que tarda m�s tiempo en completarse en tiempo real.
�Herramientas de Rendimiento de Sistema Operativo
Algunas veces los cuellos de botella del rendimiento ocurren al nivel del sistema operativo. Esto es porque la JVM depende en muchas operacioens de las librer�as del sistema operativo para funcionalidades como el acceso a disco o el trabajo en red. Sin embargo, lo que ocurre despu�s de que la JVM haya llamado a estas librer�as va m�s alla de las herramientas de perfilado de la plataforma Java.
Aqu� hay una lista de herramietnas que podemos usar para analizar problemas de rendimiento en algunos sistemas operativos m�s comunies.
�Plataforma Solaris
System Accounting Reports, sar, informa de la actividad del sistema en t�rminos de I/O de disco, actividad del programa de usuario, y actividad a nivel del sistema. Si nuestra aplicaci�n usa una cantidad de memoria excesiva, podr�a requerir espacio de intercambio en disco, por lo que veriamos grandes porcentajes en la columna WIO. Los programas de usuario que se quedan en un bucle ocupado muestran un alto porcentaje en la columna user.
developer$ sar 1 10
SunOS developer 5.6 Generic_105181-09 sun4u
02/05/99
11:20:29 %usr %sys %wio %idle
11:20:30 30 6 9 55
11:20:31 27 0 3 70
11:20:32 25 1 1 73
11:20:33 25 1 0 74
11:20:34 27 0 1 72
El comando truss sigue y guarda los detalles de cada llamada al sistema por la JVM al kernel Solaris. Un forma com�n de usar truss es.
truss -f -o /tmp/output -p <process id>
El par�metro -f sigue cualquier proceso hijo que haya creado, el par�metro -o escribe la salida en el fichero nombrado, y el par�metro -p sigue un programa en ejecuci�n desde sis ID de proceso. De forma alternativa podemos reemplazar -p <process id> con la JVM, por ejemplo.
truss -f -o /tmp/output java MyDaemon
El /tmp/output es usado para almacenar la salida de truss, lo que se deber�a parecer a esto.
15573: execve("/usr/local/java/jdk1.2/solaris/
bin/java", 0xEFFFF2DC,
0xEFFFF2E8) argc = 4
15573: open("/dev/zero", O_RDONLY) = 3
15573: mmap(0x00000000, 8192,
PROT_READ|PROT_WRITE|PROT_EXEC,
MAP_PRIVATE, 3, 0) = 0xEF7C0000
15573: open("/home/calvin/java/native4/libsocket.so.1",
O_RDONLY) Err#2 ENOENT
15573: open("/usr/lib/libsocket.so.1",
O_RDONLY) = 4
15573: fstat(4, 0xEFFFEF6C) = 0
15573: mmap(0x00000000, 8192, PROT_READ|PROT_EXEC,
MAP_SHARED, 4, 0) = 0xEF7B00 00
15573: mmap(0x00000000, 122880, PROT_READ|PROT_EXEC,
MAP_PRIVATE, 4, 0) = 0xEF7 80000
15573: munmap(0xEF78E000, 57344) = 0
15573: mmap(0xEF79C000, 5393,
PROT_READ|PROT_WRITE|PROT_EXEC,
MAP_PRIVATE|MAP_FIXED, 4, 49152)
= 0xEF79C000
15573: close(4) = 0
En la salida de truss, buscamos los ficheros que fallaran al abrirlos debido a problemas de acceso, como un error ENOPERM, o un error de fichero desaparecido ENOENT. Tambi�n podemos seguir los datos leidos o escrito con los par�metros de truss: -rall para seguir todos los datos le�dos, o -wall para seguir todos los datos escritos por el programa. Con estos par�metros, es posible analizar datos enviados a trav�s de la red o a un disco local.
�Plataforma Linux
Linux tiene un comando trace llamado strace. Sigue las llamadas del sistema al kernel Linux. Este ejemplo sigue el ejemplo SpreadSheet del directorio demo del JDK.
$ strace -f -o /tmp/output
java sun.applet.AppletViewer
example1.html
$ cat /tmp/output
639 execve("/root/java/jdk117_v1at/java/
jdk117_v1a/bin/java", ["java",
"sun.applet.AppletViewer ",
"example1.html"], [/* 21 vars */]) = 0
639 brk(0) = 0x809355c
639 open("/etc/ld.so.preload", O_RDONLY) = -1
ENOENT (No such file or directory)
639 open("/etc/ld.so.cache", O_RDONLY) = 4
639 fstat(4, {st_mode=0, st_size=0, ...}) = 0
639 mmap(0, 14773, PROT_READ, MAP_PRIVATE,
4, 0) = 0x4000b000
639 close(4) = 0
639 open("/lib/libtermcap.so.2", O_RDONLY) = 4
639 mmap(0, 4096, PROT_READ, MAP_PRIVATE,
4, 0) = 0x4000f000
Para obtener informaci�n del sistema similar al comando sar de Solaris, lee los contenidos del fichero /proc/stat. El formato de este fichero se describe en las p�ginas del manual proc. Miramos la l�nea cpu para obtener la hora del sistema de usuario.
cpu 4827 4 1636 168329
En el ejemplo anterior, la salida cpu indica 48.27 segundos de espacio de usuario, 0.04 de prioridad m�xima, 16.36 segundos procesando llamadas al sistema, y 168 segundos libre. Esta es una ejecuci�n total, las entradas para cada proceso est�n disponibles en /proc/<process_id>/stat.
�Plataforma Windows95/98/NT
No hay herramientas de an�lisis de rendimiento est�ndard incluidas en estas plataformas, pero si hay herramientas de seguimiento disponibles mediante recursos freeware o shareware como http://www.download.com .
An�lisis de memoria: Memory meter
�Cach� en Aplicaciones Cliente/Servidor
El cach� es una de las primera t�cnicas usadas para aumetnar el rendimiento de navegadores y servidores web. El cach� del navegador hace innecesarios los bloqueos de red porque una copia reciente del fichero se mantiene en el cach� local, y el cach� del servidor reduce el coste de la carga de ficheros desde disco para cada petici�n. Esta secci�n explica c�mo podes usar el cach� de forma similar para mejorar el rendimiento en muchas aplicaciones cliente/servidor escritas en lenguaje Java.
El API java.util.Collections disponible en el SDK Java� 2 hace sencilla la implementaci�n del cach�. Este API proporciona la clase HashMap, que funciona bien para cachear un objeto, y la clase LinkedList, que funciona bien en combinaciones con la clase HashMap para cachear muchos objetos.
�Cach� de un Objeto
Un objeto HashMap almacena objetos en una pareja clave valor. cuando ponemos un datp en un HashMap, le asignamos una clave y luego usamos esa clave para recuperar el dato.
Un objeto HashMap es muy similar a un Hashtable y puede ser usado para mantener una copia temporal de resultados generados pr�viamente. Los objetos mantenidos en el cach� HashMap podr�a, por ejemplo, ser una lista de subastas completadas.
En este caso, los resultados de una consulta JDBC podr�an solicitarse cientos de veces en un segundo por personas que est�n esperando conocer la puja m�s alta, pero la lista de resultados completa s�lo cambia una vez por minuto cuando se ompleta una subasta. Podemos escribir nuestro programa para recuperar los objetos que no han cambiado desde el cach� de resultados en vez de solicitar a la base de datos cada vez y obtener un significante aumento de rendimiento.
Este ejemplo de c�digo ejecuta una consulta a la base de datos por cada minuto, y devuelve copias cacheadas para las solicitudes que vienen entre consultas.
import java.util.*;
import java.io.*;
class DBCacheRecord {
Object data;
long time;
public DBCacheRecord(Object results, long when) {
time=when;
data=results;
}
public Object getResults() {
return data;
}
public long getLastModified() {
return time;
}
}
public class DBCache {
Map cache;
public DBCache() {
cache = new HashMap();
}
public Object getDBData(String dbcommand) {
if(!cache.containsKey(dbcommand)) {
synchronized(cache) {
cache.put(dbcommand, readDBData(dbcommand));
}
} else {
if((new Date().getTime() ) -
((DBCacheRecord)cache.get(
dbcommand)).getLastModified()>=1000) {
synchronized(cache) {
cache.put(dbcommand, readDBData(dbcommand));
}
}
}
return ((DBCacheRecord)cache.get(
dbcommand)).getResults();
}
public Object readDBData(String dbcommand) {
/*Insert your JDBC code here For Example.
ResultSet results=stmt.executeQuery(dbcommand);
*/
String results="example results";
return(new DBCacheRecord(results,new
Date().getTime()));
}
public static void main(String args[]) {
DBCache d1=new DBCache();
for(int i=1;i<=20;i++) {
d1.getDBData(
"select count(*) from results where
TO_DATE(results.completed) <=SYSDATE");
}
}
}
�Cache de Muchos Objetos
Algunas veces queremos cachear m�s de un objeto. Por ejemplo, podr�amos querer mantener los ficheros accedidos m�s recientemente en el cach� de un servidor web. Si usamos un objeto HashMap para un prop�sito como este, continuar� creciendo y usando mucha memoria.
Si nuestra m�quina tiene una gran cantidad de memoria y s�lo un peque�o n�mero de objetos que cachear entonces un creciente HashMap podr�a no ser un problema. Sin embargo, si estamos intentar cachear muchos objetos entonces podr�amos queres s�lo mantener los objetos m�s recientes en el cach� proporcionando el mejor uso de la m�moria de la m�quina. Podemos combinar un objeto HashMap con un LinkedList para crear un cach� llamado "Most Recently Used" (MRU).
Con un cach� MRU, podemos situar una restricci�n sobre los objetos que permanecen en el cach�, y por lo tanto, control sobre el tama�o del cach�. Hay tres operaciones principales que puede realizar un cach� MRU.
- Si el cach� no est� lleno, los nuevos objetos que no est�n en el cach� se insertan en la parte superior de la lista.
- Si el cach� no est� lleno y el objeto a inserta ya est� en el cach�, se mueve a la parte superior del cach�.
- Si el cach� est� lleno y se inserta un nuevo objeto, el �ltimo objeto del cach� es eliminado y el nuevo objeto se pone en la parte superior de la lista.
Este diagrama muestra c�mo trabajan juntos LinkedList y HashMap para implementar las operaciones descritas arriba.

Cach� MRU con LinkedList y HashMap
El LinkedList proporciona el mecanismo de cola, y las entradas de la LinkedList contienen la clave de los datos en el HashMap. Para a�adir una nueva entrada en la parte superior de la lista, se llama al m�todo addFirst.
- Si la lista ya est� llena, se llama al m�todo removeLast y a entrada de datos tambi�n se elimina del HashMap.
- Si una entrada ya existe en la lista, se elimina con una llamada al m�todo remove y se inserta en la parte superior de la lista con una llamada al m�todo addFirst.
El API Collectios no implementa bloqueos, por eso si eliminados o a�adimos entradas a objetos LinkedList o HashMap, necesitamos bloquear los accesos a esos objetos. Tambi�n podemos usar un Vector o ArrayList para obtener el mismo resultado mostrado en el c�dido de abajo del LinkedList.
Este ejemplo de c�digo usa un cach� MRU para mantener un cach� de ficheros cargados desde disco. Cuando se solicita un fichero, el programa chequea para ver si el fichero est� en el cach�. Si el fichero no est� en el cach�, el programa lee el fichero desde el disco y sit�a una copia en el cach� al principio de la lista.
Si el fichero est� en el cach�, el programa compara la fecha de modificaci�n del fichero y la entrada del cach�.
- Si la entrada del cach� es m�s vieja, el programa lee el fichero del disco, elimina la copia del cach�, y sit�a una nueva copia en el cach� en la parte superior del LinkedList.
- Si el fichero es m�s viejo que el cach�, el programa obtiene el fichero del cach� y mueve la copia del cach� a la parte superior de la lista.
import java.util.*;
import java.io.*;
class myFile {
long lastmodified;
String contents;
public myFile(long last, String data) {
lastmodified=last;
contents=data;
}
public long getLastModified() {
return lastmodified;
}
public String getContents() {
return contents;
}
}
public class MRUCache {
Map cache;
LinkedList mrulist;
int cachesize;
public MRUCache(int max) {
cache = new HashMap();
mrulist= new LinkedList();
cachesize=max;
}
public String getFile(String fname) {
if(!cache.containsKey(fname)) {
synchronized(cache) {
if(mrulist.size() >=cachesize) {
cache.remove(mrulist.getLast());
mrulist.removeLast();
}
cache.put(fname, readFile(fname));
mrulist.addFirst(fname);
}
} else {
if((new File(fname).lastModified())>
((myFile)cache.get(fname)).getLastModified()) {
synchronized(cache) {
cache.put(fname, readFile(fname));
}
}
synchronized(cache) {
mrulist.remove(fname);
mrulist.addFirst(fname);
}
}
return ((myFile)cache.get(fname)).getContents();
}
public myFile readFile(String name) {
File f = new File(name);
StringBuffer filecontents= new StringBuffer();
try {
BufferedReader br=new BufferedReader(
new FileReader(f));
String line;
while((line =br.readLine()) != null) {
filecontents.append(line);
}
} catch (FileNotFoundException fnfe){
return (null);
} catch ( IOException ioe) {
return (null);
}
return (new myFile(f.lastModified(),
filecontents.toString()));
}
public void printList() {
for(int i=0;i<mrulist.size();i++) {
System.out.println("item "+i+"="+mrulist.get(i));
}
}
public static void main(String args[]) {
// Number of entries in MRU cache is set to 10
MRUCache h1=new MRUCache(10);
for(int i=1;i<=20;i++) {
// files are stored in a subdirectory called data
h1.getFile("data"+File.separatorChar+i);
}
h1.printList();
}
}