2011-09-06 21 views
7

Tenemos una gran cantidad de código de la clase que tiene algunas repetitivo como el siguiente:¿Es eficiente obtener un registrador de un inicializador de variable final estático?

private static Logger logger = null; 

private static Logger getLogger() { 
    if (logger == null) { 
    logger = Logger.getLogger(MyClass.class); 
    } 
    return logger; 
} 

La idea es que la clase puede tener logs de depuración en el registrador. El primer código que necesita registrar algo invoca getLogger() y eso hace que el registrador exista.

Hay un par de cosas que no me gustan de este patrón. Primero, el singleton getLogger() no está sincronizado y sincronizándolo, mientras que el correcto representaría una carga en cada llamada subsiguiente sin ninguna razón.

Realmente quiero ser capaz de condensar abajo a esto:

private static final Logger logger = Logger.getLogger(MyClass.class); 

Entonces puede simplemente hacer referencia registrador de forma directa y ni siquiera se molestan con un captador Singleton.

El problema que me teme es que al hacer esto hago que se cree un registrador cuando se carga la clase, incluso si nunca se llama al registrador. Tengo más de 10.000 clases impares todas llamando a getLogger(), entonces, ¿cuántas instancias de Logger estoy creando aquí? Si mis propiedades log4j contienen algunos appenders, ¿estoy haciendo referencia al mismo registrador una y otra vez, o estoy creando 10.000 de estas cosas?

+4

¡Considera la seguridad de las hebras! –

+0

Estoy considerando la seguridad de hilos. Es por eso que quiero deshacerme del singleton roto y confiar en el cargador de clases intrínsecamente seguro para hilos. – locka

+0

AFAICS el problema es que si dos subprocesos intentan capturar el registrador al mismo tiempo, se crearán dos instancias 'Logger'. –

Respuesta

2

Si usa la configuración predeterminada de Log4j (es decir, LoggerRepository por defecto, DefaultCategoryFactory, etc.), entonces creará 10'000 instancias de Logger. ¿Cuánta memoria consumen? Nadie excepto God y su Profiler lo saben. (Y creo que solo el último te diría eso).

Si la huella de la memoria sería demasiado para su entorno, mover Registrador de inicialización de la clase interna estático como sigue:

static class LoggerHolder { 
    static Logger logger = Logger.getLogger(MyClass.class); 
} 

private static Logger getLogger() { 
    return LoggerHolder.logger; 
} 

De esa manera la instancia de Logger sólo será creada en el primer getLogger llamada. (Esta técnica se conoce como titular de inicialización bajo demanda (IODH), es segura para subprocesos y tiene una sobrecarga de sincronización cero).

¿Y puedo darle una sugerencia offtopic? Considere reemplazar Log4J con la combinación de bibliotecas SLF4J + Logback. Están escritos por los mismos autores y se describen como "a successor to the popular log4j project, picking up where log4j leaves off". Puede leer más en this SO thread.

+0

Excelente respuesta +1, aunque un punto si tengo 10,000 clases adicionales para almacenar mis instancias de registrador, ¿entonces no corro el riesgo de presionar el espacio de PermGen? – locka

+0

@locka Sí, 10000 clases adicionales ocuparán espacio en el espacio PermGen. Pero de nuevo solo la memoria Profiler te dirá los números reales ... Creé un proyecto de muestra con solo 10 000 clases ficticias con IODH para Logger. Y llame a getLogger() para todos ellos uno por uno. La mina OSX JVM consumió ~ 50Mb para PermGen, no una cantidad pequeña, pero tampoco una crucial. (Y si no llama a getLogger, la clase interna no se cargará). – Idolon

2

Solo creará los objetos si la clase está realmente inicializada, es decir, si es usado. En ese momento, ¿la pequeña sobrecarga de un solo objeto por clase realmente importa tanto?

Respuesta más simple: pruébelo de ambas maneras, y vea si puede observar diferencias significativas en el rendimiento/memoria/etc. Dudo que pueda, pero si puede, podrá decidir luego, según datos, cuál es el curso de acción más apropiado.

+0

Para probar sake, sugeriría usar la variable logger en cada clase, que hará que la diferencia de rendimiento/memoria sea más visible al comparar. – medopal

1

Está creando una instancia de registrador separada para cada clase que carga, sin embargo, creo que tienen una huella de memoria pequeña. Se sabe que Log4J está optimizado ampliamente, y dudo que no hayan pensado en el uso de la memoria.

En general, si tiene 10K clases diferentes, su aplicación es enorme. Dudo que note una diferencia significativa en el consumo de memoria. Pero lo mejor es, por supuesto, medirlo en ambos sentidos, en su entorno concreto.

0

Variables estáticas inicializadas una vez y el mismo objeto se utiliza para todas las instancias. Realmente no necesita ese método private static Logger getLogger() para el patrón singleton.

Simplemente hará que la carga sea floja, pero no creo que sea una gran ganancia. Miles de objetos se crean y destruyen en muy poco tiempo.

0

La forma que desee (campo final estático) es lo que generalmente se hace, y es recomendado por PMD. Si tiene un registrador, existe una buena posibilidad de que se use, por lo que inicializarlo perezosamente no obtendrá nada en términos de memoria, y ciertamente no mucho en rendimiento.

Lo capitalizaría LOGGER, sin embargo, ya que es una constante.

0

OP no sincronizado getLogger() método está bien, ince Logger es seguro para subprocesos. (afortunadamente, dado que Logger es mutable y está diseñado para ser utilizado simultáneamente, me sorprendería que su referencia no se pueda publicar de manera segura sin sincronización adicional.)

Ocasionalmente, la creación de 2 registradores para la misma clase tampoco es un problema .

Sin embargo, no es necesario preocuparse por el uso de la memoria. Tener un objeto extra para cada clase no debería ser una sobrecarga preocupante (con suerte, el objeto Logger no es demasiado grande)

Cuestiones relacionadas