JRE dinâmico: Estressando a máquina

Ao estudar a possibilidade de utilização do WebLogic Server como um Grails EmbeddableServer, substituindo Tomcat ou Jetty na execução de ‘grails run-app’, deparei-me com algumas limitações. Certas características do ambiente de execução Java não estão preparadas para serem alteradas após o início da JVM (ou estão?). Apesar de todo alarde, é apenas software e usá-lo de forma pouco convencional não desencadeará o fim do mundo.

As soluções (truques ou workarounds, se preferirem) a seguir necessitam de um SecurityManager ou de políticas de segurança permissivas o suficiente. Podem ser implementadas em Groovy ou Java “puro”. Basicamente faz-se uso de AcessibleObject.setAcessible e de como algumas classes das bibliotecas base de Java funcionam internamente, em particular as implementações disponíveis com o JDK ou JRE da Oracle/Sun e com o OpenJDK.

1. Adicionar, dinamicamente, paths ao CLASSPATH (java.class.path)

change_classpath.groovy

// groovy change_classpath.groovy

def changeClasspath = {paths ->
    def systemClassLoader = ClassLoader.systemClassLoader
    assert systemClassLoader.metaClass.respondsTo(systemClassLoader, 'addURL', java.net.URL)
    def pathSeparator = File.pathSeparator
    def classpathExtension = new StringBuilder()
    for (path in paths) {
        def lib = new File(path); assert lib.exists()
        systemClassLoader.addURL(lib.toURI().toURL())
        classpathExtension.append(pathSeparator); classpathExtension.append(lib.canonicalPath)
    }
    // Required under certain conditions
    System.setProperty('java.class.path', System.getProperty('java.class.path', '') + classpathExtension.toString())    
}

def classes = ['Lib1', 'Lib2', 'Lib3', 'Lib4']
for (className in classes) {
    try {Class.forName(className); assert false} catch(ClassNotFoundException e) {assert true}
}

def extraPaths = ['lib1.jar', 'lib2classes']
extraPaths.addAll((new File('extraLibs').list() as List).collect {'extraLibs/' + it})
changeClasspath(extraPaths)

for (className in classes) {
    def lib = Class.forName(className)
    lib.doSomething()
    println "${className}.classLoader ${lib.classLoader}"
}

ChangeClasspath.java

// java -ea ChangeClasspath

import java.lang.reflect.Method;
import java.io.File;
import java.util.ArrayList;
import java.util.List;

public class ChangeClasspath {

    public static void main(String [] args) throws java.net.MalformedURLException, NoSuchMethodException, IllegalAccessException, java.lang.reflect.InvocationTargetException, ClassNotFoundException {
        String [] classes = {"Lib1", "Lib2", "Lib3", "Lib4"};
        for (String className : classes) {
            try {Class.forName(className); assert false;} catch(ClassNotFoundException e) {assert true;}
        }

        List <String> extraPaths = new ArrayList();
        extraPaths.add("lib1.jar"); extraPaths.add("lib2classes");
        for (String path : new File("extraLibs").list()) {
            extraPaths.add("extraLibs/" + path);
        }
        
        changeClasspath(extraPaths);

        for (String className : classes) {
            Class lib = Class.forName(className);
            Method doSomethingMethod = lib.getDeclaredMethod("doSomething");
            doSomethingMethod.invoke(null);
            System.out.println(className + ".classLoader " + lib.getClassLoader());
        }
    }

    private static void changeClasspath(List <String> paths) throws NoSuchMethodException, IllegalAccessException, java.net.MalformedURLException, java.lang.reflect.InvocationTargetException{
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        assert systemClassLoader instanceof java.net.URLClassLoader;

        String pathSeparator = File.pathSeparator;
        
        StringBuilder classpathExtension = new StringBuilder();
        Method addURLMethod = java.net.URLClassLoader.class.getDeclaredMethod("addURL", java.net.URL.class);
        addURLMethod.setAccessible(true);

        for (String path : paths) {
            File lib = new File(path); assert lib.exists();
            addURLMethod.invoke(systemClassLoader, lib.toURI().toURL());
        }
        // Required under certain conditions
        System.setProperty("java.class.path", System.getProperty("java.class.path", "") + classpathExtension.toString());
    }

}

Continue reading

Groovy Closure: Pensamentos sobre a evolução

Cada construção a seguir tem características próprias, com benefícios e limitações. No entanto, é difícil não notar certas semelhanças conceituais ou de mecanismos de implementação entre elas.

C – function_pointer.c

#include

/* gcc --ansi function_pointer.c -o function_pointer ; ./function_pointer */
int add(int a, int b);
int multiply(int a, int b);

typedef int (*math_operation)(int, int);

int main(int argc, char *argv[]) {
  math_operation ops[2] = {add, multiply};
  int i = 0;
  int result = -1;

  for (i = 0; i < 2; i++) {
    result = ops[i](4, 7);
    printf("%i\n", result);
  }

  return 0;
}

int add(int a, int b) {
  return a+b;
}

int multiply(int a, int b) {
  return a*b;
}

C++ – virtual_function.cpp
Continue reading

Groovy SQL e batch updates (microbenchmark)

Antes de dizer que uma ferramenta não é adequada a determinada tarefa, aprenda o máximo que puder sobre a mesma.

Os testes a seguir foram executados usando-se o snapshot da versão 1.8.1 de Groovy, que inclui a melhoria GROOVY-4798. Ao analisar os resultados, tenha em mente que:

  • Microbenchmarks são apenas microbenchmarks;
  • Os valores devem ser comparados entre si e não considerados em termos absolutos;
  • Os respectivos servidores de diferentes RDMBS estavam em localidades geográficas distintas, inviabilizando a comparação entre valores que não pertençam ao mesmo grupo/RDBMS: Enquanto o servidor de PostgreSQL estava no mesmo computador em que os testes foram executados, o servidor de Oracle estava a dezenas de quilômetros de distância.


OS: x86 Windows XP 5.1
JVM: Sun Microsystems Inc. 1.6.0_23

=============================
== Testing oracle10g ==
=============================
== without batch (Statement)
1385 of 1385 rows inserted in 24375 (ms)
== without batch (PreparedStatement)
1385 of 1385 rows inserted in 22141 (ms)
== with batch (Statement)
1385 of 1385 rows inserted in 22312 (ms)
== with batch (PreparedStatement)
1385 of 1385 rows inserted in 140 (ms)
Continue reading

Ajustes de desempenho do GORM/Hibernate para processamento em lote

Para casos nos quais o GORM não seja imprescindível, pode-se utilizar Groovy SQL diretamente:

class BookService {
  static transactional = true

  def dataSource

  def list = {
    def sql = new groovy.sql.Sql(dataSource)
    // (...)
    sql.eachRow('select * from book')
    // (...)
    sql.withBatch(1000) {stmt -> /* (...) */}
    // (...)
    sql.withBatch(1000, 'insert into catalog values (?)') {
       stmt.addBatch('Main catalog') // ***
       stmt.addBatch('Presale catalog')
       // (...)
    }
  }
  // (...)
}

*** Prévia de GROOVY-4798.

Para os demais casos, com base na experiência obtida durante a migração de uma aplicação Java “batch” (processamento em lote, sem GUI) usando Oracle como RDBMS, a lista a seguir foi elaborada. A aplicabilidade de cada item depende da ocasião e deve ser analisada cuidadosamente.

1. Habilitar, durante o desenvolvimento, o log  de sentenças SQL executadas pelo Hibernate

// grails-app/config/Config.groovy
log4j = {
 /* (...) */
 debug  'org.hibernate.SQL' /*,
 'org.hibernate.transaction',
 'org.hibernate.jdbc',
 trace 'org.hibernate.type'*/
}

ou modificar o DataSource.groovy.

O log de outros componentes também pode ser habilitado:

  • org.hibernate.transaction – Demarcação de transação;
  • org.hibernate.type – Parâmetros utilizados nos comandos DML;
  • org.hibernate.jdbc – Demarcação de batch updates e outras informações.

Este item auxilia na identificação de pontos de melhoria ou atenção.

2. Desabilitar cache de segundo nível do Hibernate

// grails-app/config/DataSource.groovy
hibernate {
  /* (...) */
  cache.use_second_level_cache = false
  cache.use_query_cache = false
  cache.provider_class = 'net.sf.ehcache.hibernate.EhCacheProvider'
}

3. Evitar verificações redundantes, especialmente as que implicarem em acesso adicional ao banco de dados

DomainClass.save(validate: false)

e

// grails-app/conf/Config.groovy
grails.gorm.failOnError=true

4. Utilizar batch updates, ajustando a quantidade de sentenças

// grails-app/config/DataSource.groovy
hibernate {
  /* (...) */
  jdbc.batch_size=50
}

Continue reading

Investigando e solucionando problemas: Dividir para conquistar

Nada é mais direto e efetivo, na exposição de um problema, do que escrever uma aplicação simples, que possa ser re-executada em qualquer ambiente. Nada.

Se o problema for simples o suficiente, ilustre-o usando código-fonte executável no Groovy web console.

Ao de solicitar ajuda, nas listas de e-mail sobre Groovy e Grails, disponibilize tal aplicação ou fonte executável. Não atribua a outra pessoa a ingrata tarefa de ajudá-lo a montar um enorme quebra-cabeças, tendo o acesso limitado a 1 ou 2 peças.

Caso a aplicação original seja complexa, tente isolar o problema. Extrapole a idéia do dividir para conquistar: Escreva uma aplicação básica e adicione cada vez mais componentes, até Continue reading

Preâmbulo

Tempo é um recurso valioso. Por isso princípios como KISS e DRY são importantes e aplicáveis aqui.

Desculpo-me previamente pelas simplificações extremas e utilização excessiva de hyperlinks. Sintam-se à vontade para discutirem o que julgarem relevante, usando os meios adequados.

Por que Groovy e Grails?

Groovy = Java++

Grails = (Spring Framework + Hibernate)++

Com o tempo e o amadurecimento do desenvolvedor e da própria tecnologia:

Groovy = Java**

Grails = (Spring Framework + Hibernate)**

Escreve-se o mesmo tipo de aplicação, em um tempo significativamente menor.

E se eu descobrir que parte do código em Groovy está afetando o desempenho da minha aplicação?

Continue reading