Este artículo presenta los principios de la gestión de la memoria de Java y las causas de la memoria en detalle también proporciona algunas soluciones para resolver las filtraciones de memoria de Java.
Mecanismo de gestión de memoria de Java
En C ++, si se necesita una memoria para asignar dinámicamente una memoria, el programador debe ser responsable de todo el ciclo de vida de esta memoria. Desde la aplicación para la asignación, el uso, hasta la versión final. Este proceso es muy flexible, pero muy engorroso. El lenguaje Java ha hecho su propia optimización para la gestión de la memoria, que es el mecanismo de recolección de basura. Casi todos los objetos de memoria en Java se asignan en la memoria de almacenamiento de almacén (excepto los tipos de datos básicos), y GC (Gage Collection) es responsable de la memoria de reciclaje automáticamente que ya no se usa.
Lo anterior es la situación básica del mecanismo de gestión de la memoria Java. Pero si solo entendemos esto, aún encontraremos filtraciones de memoria en el desarrollo real del proyecto. Algunas personas pueden dudar de que, dado que el mecanismo de recolección de basura de Java puede reciclar automáticamente la memoria, ¿por qué todavía habrá fugas de memoria? En esta pregunta, necesitamos saber cuándo GC recicla los objetos de memoria y qué tipo de objetos de memoria serán considerados "ya no usan" por GC.
El acceso a los objetos de memoria en Java utiliza métodos de referencia. En el código Java, mantenemos una variable de referencia de un objeto de memoria. En los programas Java, esta variable de referencia en sí misma se puede almacenar en la memoria de Heap y en la memoria de la pila de código (igual que el tipo de datos básicos). Los subprocesos de GC comienzan al seguimiento de las variables de referencia en la pila de código para determinar qué memoria se está utilizando. Si el hilo GC no puede rastrear una memoria de montón de esta manera, entonces el GC cree que esta memoria ya no se usará (porque el código ya no puede acceder a esta memoria).
A través de este método de administración de memoria de gráfico dirigido, cuando un objeto de memoria pierde todas las referencias, GC puede reciclarlo. Por el contrario, si el objeto todavía tiene una referencia, GC no lo reciclará, incluso si la máquina virtual Java tira fuera de MemoryError.
Fugas de memoria de Java
En términos generales, hay dos situaciones para fugas de memoria. En un caso, en el lenguaje C/C ++, toda la memoria asignada en el montón se eliminará (como la reasignación del puntero) cuando no se libera; memoria y su método de acceso (referencia). El primer caso es que se ha resuelto bien en Java debido a la introducción del mecanismo de recolección de basura. Por lo tanto, las filtraciones de memoria en Java se refieren principalmente al segundo caso.
Tal vez solo hablar sobre el concepto es demasiado abstracto, puede echar un vistazo a tal ejemplo:
La copia del código es la siguiente:
Vector V = nuevo vector (10);
para (int i = 1; i <100; i ++) {
Objeto o = nuevo objeto ();
V.Add (O);
o = nulo;
}
En este ejemplo, hay referencias al objeto vectorial V y referencias al objeto Objeto o en la pila de código. En el bucle for, generamos continuamente nuevos objetos, luego los agregamos al objeto vectorial y luego vaciamos la referencia O. La pregunta es, si GC ocurre después de que la referencia O esté vacía, ¿el objeto que creamos será reciclado por GC? La respuesta es no. Debido a que GC rastrea las referencias en la pila de código, encontrará una referencia V y continúa rastreando, encontrará que hay una referencia al objeto del objeto en el espacio de memoria señalado por la referencia V. Es decir, aunque la referencia O ha estado vacía, todavía hay otras referencias en el objeto del objeto y se puede acceder, por lo que el GC no puede liberarlo. Si el objeto del objeto no tiene efecto en el programa después de este bucle, entonces creemos que se produjo una fuga de memoria en este programa Java.
Aunque las filtraciones de memoria Java son menos destructivas para las filtraciones de memoria en C/C ++, el programa aún puede ejecutarse normalmente en la mayoría de los casos, excepto en algunos casos en los que el programa se bloquea. Sin embargo, cuando los dispositivos móviles tienen restricciones estrictas en la memoria y la CPU, el desbordamiento de la memoria Java conducirá a la ineficiencia de los programas y la ocupación de grandes cantidades de memoria no deseada. Esto hará que el rendimiento de toda la máquina se deteriore, y en casos severos también hará que se arroje el OutOfMemoryError, lo que hace que el programa se bloquee.
Evite las filtraciones de memoria en general
En general, sin involucrar estructuras de datos complejas, la memoria de Java se manifiesta a medida que el ciclo de vida de un objeto de memoria excede el tiempo que el programa lo necesita. A veces lo llamamos "libre de objetos".
Por ejemplo:
La copia del código es la siguiente:
Public Class FileSearch {
Contenido de byte privado [];
archivo privado mfile;
Public Filesearch (archivo de archivo) {
mfile = archivo;
}
Public Boolean Hasstring (String Str) {
int size = getFilesize (mfile);
contenido = nuevo byte [tamaño];
loadFile (mfile, contenido);
Cadena s = nueva cadena (contenido);
devolver s.contains (str);
}
}
En este código, hay una función de función en la clase FileSearch para determinar si el documento contiene la cadena especificada. El proceso es cargar primero en la memoria y luego hacer juicios. Sin embargo, el problema aquí es que el contenido se declara como una variable de instancia, no como una variable local. Entonces, después de que esta función regrese, los datos de todo el archivo todavía existe en la memoria. Es obvio que ya no necesitamos estos datos en el futuro, lo que conduce a un desperdicio de memoria irrazonable.
Para evitar fugas de memoria en este caso, debemos administrar nuestra memoria asignada con el pensamiento de administración de memoria C/C ++. Primero, es aclarar el alcance efectivo del objeto de memoria antes de declarar la referencia del objeto. Los objetos de memoria que son válidos dentro de una función deben declararse como variables locales, y aquellos con el mismo ciclo de vida que la instancia de clase deben declararse como variables de instancia ... y así sucesivamente. En segundo lugar, recuerde vaciar manualmente el objeto de memoria cuando ya no es necesario.
Problema de fuga de memoria en estructuras de datos complejas
En proyectos reales, a menudo utilizamos algunas estructuras de datos más complejas para almacenar en caché la información de datos necesaria durante la operación del programa. A veces, debido a la complejidad de la estructura de datos, o tenemos algunas necesidades especiales (por ejemplo, la mayor cantidad de información de caché posible para mejorar la velocidad de ejecución del programa, etc.), es difícil para nosotros tratar los datos En la estructura de datos. En este momento, podemos usar un mecanismo especial en Java para evitar la fuga de memoria.
Hemos introducido antes que el mecanismo GC de Java se basa en el mecanismo de referencia que rastrea la memoria. Antes de eso, las referencias que usamos solo definieron una forma de "objeto o;". De hecho, esta es solo una situación predeterminada en el mecanismo de referencia de Java, y hay algunos otros métodos de referencia además. Al usar estos mecanismos de referencia especiales y combinar con el mecanismo GC, podemos lograr algunos de los efectos que necesitamos.
Varios métodos de referencia en Java
Hay varias formas diferentes de citar en Java, a saber: citas fuertes, citas blandas, citas débiles y citas virtuales. A continuación, primero entendemos la importancia de estos métodos de cita en detalle.
Cita fuerte
Las citas utilizadas en el contenido que presentamos antes eran todas citas fuertes, que son las citas más comunes utilizadas. Si un objeto tiene una referencia fuerte, es similar a una necesidad diaria esencial, y el recolector de basura nunca lo reciclará. Cuando el espacio de memoria es insuficiente, la máquina virtual Java preferiría lanzar un error OutOfMemoryError para que el programa termine anormalmente que los objetos de reciclaje con fuertes referencias para resolver el problema de la memoria.
Softreference
Un uso típico de la clase Softreference es para cachés sensibles a la memoria. El principio de Softreference es garantizar que todas las referencias suaves se borren antes de que el JVM informa una memoria insuficiente al mantener una referencia a un objeto. El punto clave es que el recolector de basura puede (o no) liberar objetos accesibles suaves en tiempo de ejecución. Si se libera un objeto depende del algoritmo del recolector de basura y la cantidad de memoria disponible cuando el recolector de basura se está ejecutando.
Referencia débil
Un uso típico de la clase de referencia débil es normalizar el mapeo (mapeo canonicalizado). Además, las referencias débiles también son útiles para objetos con vidas relativamente largas y baja sobrecarga de recreación. El punto clave es que si se encuentra un objeto débilmente accesible durante la ejecución del recolector de basura, se liberará el objeto a la que se le diebe la referencia. Sin embargo, tenga en cuenta que el recolector de basura puede tener que ejecutarse varias veces antes de que pueda encontrar y liberar objetos débilmente accesibles.
Fantomreferencia
La clase Phantomreference solo se puede usar para rastrear las próximas colecciones de objetos referenciados. Del mismo modo, también se puede usar para realizar operaciones de compensación pre-mortem. Phantomreference debe usarse con la clase RefectionQueue. Se requiere referencequeue porque puede actuar como un mecanismo de notificación. Cuando el recolector de basura determina que un objeto es un objeto de acceso virtual, el objeto Phantomreference se coloca en su referencia. Poner el objeto Phantomreference en el referencequeue es una notificación que indica que el objeto a la que se hace referencia el objeto Phantomreference ha terminado y está disponible para la recolección. Esto le permite tomar medidas justo antes de que la memoria ocupada por el objeto sea reciclada. Reference y referencequeue se utilizan junto con RefectionQueue.
GC, referencia e interacción referencequeue
A. GC no puede eliminar la memoria de los objetos con referencias fuertes.
B. GC encontró una memoria de objeto con solo referencias suaves, luego:
① El campo de referencia del objeto Softreference se establece en NULL, de modo que el objeto ya no se refiere al objeto Heap.
② El objeto Heap mencionado por Softreference se declara como finalizable.
③ Cuando se ejecuta el método Finalize () del objeto Heap y se libera la memoria ocupada por el objeto, el objeto Softreference se agrega a su referencia (si este último existe).
C. GC descubre una memoria de objeto con solo referencias débiles, luego:
① El campo de referencia del objeto WeakReference se establece en NULL, de modo que el objeto ya no se refiere al objeto Heap.
② El objeto de montón referenciado por débilreferencia se declara como finalizable.
③ Cuando se ejecuta el método Finalize () del objeto Heap y la memoria ocupada por el objeto se libera, el objeto WeakReference se agrega a su referencia (si este último existe).
D. GC descubre una memoria de objeto que solo tiene referencias virtuales, luego:
① El objeto Heap mencionado por Phantomreference se declara como finalizable.
② Se agrega fantomreferencia a su referencia laquea antes de que se libere el objeto Heap.
Vale la pena señalar los siguientes puntos:
1. GC no encontrará objetos de memoria de referencia suave en general.
2. El descubrimiento de GC y la liberación de referencias débiles no son de inmediato.
3. Cuando se agregan referencias suaves y referencias débiles a referencequeue, las referencias a la memoria real se han establecido en vacío y se ha lanzado la memoria relevante. Al agregar referencia virtual a referenceQueue, la memoria aún no se ha lanzado y aún se puede acceder.
A través de la introducción anterior, creo que tiene una cierta comprensión del mecanismo de citas de Java y las similitudes y diferencias de varios métodos de citas. Solo un concepto puede ser demasiado abstracto.
La copia del código es la siguiente:
String Str = New String ("Hello");
Referencequeue <String> rq = nuevo referencequeue <string> ();
Débilreference <string> wf = new WeakReference <String> (str, rq);
str = nulo;
Cadena str1 = wf.get ();
// Si el objeto "hola" no se recicla, rq.poll () devuelve nulo
Referencia <? Extiende la cadena> ref = rq.poll ();
En el código anterior, preste atención a los dos lugares ⑤⑥. Si el objeto "Hello" no se recicla wf.get () devolverá el objeto de cadena "Hello", rq.Poll () devolverá nulo; NULL, RQ.Poll () Devuelve un objeto de referencia, pero no hay referencia al objeto STR en este objeto de referencia (PhantomReference es diferente de la referencia débil y Softreferencia).
Aplicación conjunta del mecanismo de cita y estructuras de datos complejas
Al comprender el mecanismo GC, el mecanismo de referencia y la combinación con referencequeue, podemos implementar algunos tipos de datos complejos que eviten el desbordamiento de la memoria.
Por ejemplo, Softreference tiene las características de construir un sistema de caché, por lo que podemos implementar un sistema de caché simple en combinación con tablas hash. Esto no solo garantiza que se pueda almacenar en caché tanta información, sino que también garantiza que la máquina virtual Java no tire deMemoryError debido a la fuga de memoria. Este mecanismo de almacenamiento en caché es particularmente adecuado para situaciones en las que los objetos de memoria tienen un ciclo de vida largo y el tiempo para generar objetos de memoria es relativamente largo, como imágenes de portada de la lista de caché, etc. Para algunos casos en que el ciclo de vida es largo, pero la sobrecarga de la generación de objetos de memoria no es grande, el uso de la referencia débil puede lograr una mejor gestión de la memoria.
Se adjunta una copia del código fuente de SofthashMap.
La copia del código es la siguiente:
paquete com.
//: Softhashmap.java
importar java.util.
import java.lang.ref.
importar android.util.log;
Softhashmap de clase pública extiende AbstractMap {
/** El hashmap interno que sostendrá el Softreference.
mapa final privado hash = new Hashmap ();
/** El número de referencias "difíciles" para mantener internamente.
Private final int hard_size;
/** La lista FIFO de referencias duras, orden del último acceso.
Private final LinkedList hardcache = new LinkedList ();
/** cola de referencia para objetos de Softreference borrados.
REFERENCIA PRIVADOQUEUE QUEUE = new RefectionQueue ();
// Número de referencia fuerte
public softhashmap () {this (100);
Public Softhashmap (int hardsize) {hard_size = hardsize;
Public Object get (clave de objeto) {
Resultado del objeto = nulo;
// obtenemos la softreference representada por esa clave
Softreference Soft_ref = (Softreference) Hash.get (Key);
if (soft_ref! = null) {
// Desde el Softreference obtenemos el valor, que puede ser
// nulo si no estaba en el mapa, o se eliminó en
// El método ProcessQueue () definido a continuación
resultado = soft_ref.get ();
if (result == null) {
// Si el valor ha sido recolectado de basura, retire el
// Entrada desde el hashmap.
hash.remove (clave);
} demás {
// Ahora agregamos este objeto al comienzo de la difícil
// la cola de referencia.
// una vez, porque las búsquedas de la cola FIFO son lentas, así que
// No queremos buscar a través de él cada vez que eliminen
// duplicados.
// Mantenga el objeto de uso reciente en la memoria
hardcache.addfirst (resultado);
if (hardcache.size ()> hard_size) {
// Eliminar la última entrada si la lista es más larga que Hard_Size
hardcache.removelast ();
}
}
}
resultado de retorno;
}
/** Definimos nuestra propia subclase de Softreference que contiene
No solo el valor sino también la clave para que sea más fácil de encontrar
la entrada en el hashmap después de que se ha recogido basura.
Clase estática privada SoftValue extiende Softreference {
Clave de objeto final privado;
/** ¿Sabía que una clase exterior puede acceder a datos privados
¡Miembros y métodos de una clase interna?
Pensé que era solo la clase interior que podía acceder al
La información privada de la clase exterior también puede
acceder a miembros privados de una clase interior dentro de su interior
clase. */
SoftValue privado (objeto k, clave de objeto, referencequeue q) {
super (k, q);
this .key = key;
}
}
/** Aquí pasamos por la referencia y retiramos la basura
Objetos de valor blando recolectado del hashmap mirándolos
Uso del miembro de Data SoftValue.Key.
public void ProcessQueue () {
Softvalue SV;
while ((sv = (softValue) queue.poll ())! = null) {
if (sv.get () == null) {
Log.e ("Processqueue", "nulo");
} demás {
Log.e ("processqueue", "no nulo");
}
hash.remove (sv.key);
Log.e ("Softhashmap", "Release" + SV.Key);
}
}
/** Aquí colocamos el par de valores clave en el hashmap usando
un objeto de valor blando.
Public Object Put (clave de objeto, valor de objeto) {
ProcessQueue ();
Log.e ("Softhashmap", "Pon en" Key ");
return hash.put (clave, nuevo softValue (valor, clave, cola));
}
Public Object Remete (clave de objeto) {
ProcessQueue ();
devolver hash.remove (clave);
}
public void clear () {
hardcache.clear ();
ProcessQueue ();
Hash.Clear ();
}
public int size () {
ProcessQueue ();
devolver hash.size ();
}
Public Set EntrySet () {
// no, no, puede que no hagas eso !!!
tirar nueva no compatible conperationException ();
}
}