Optimización de montón y memoria
Hoy probé la función de clasificación de datos automático de un proyecto, clasificando decenas de miles de registros e imágenes en la base de datos. Cuando la operación estaba cerca del final, Java.lang.OutOfMemoryError, se reveló un error en el espacio de montón Java. En el pasado, rara vez encontré tales errores de memoria en los programas de escritura, porque Java tiene un mecanismo de recolector de basura, por lo que no le he prestado mucha atención. Hoy busqué información en línea y la resolví sobre esta base.
1. Pila y pila
Construido con montón con nuevo, el recolector de basura es responsable del reciclaje
1. Cuando el programa comienza a ejecutarse, el JVM obtiene algo de memoria del sistema operativo, parte de la cual es la memoria del montón. La memoria del montón generalmente se organiza hacia arriba en la parte inferior de la dirección de almacenamiento.
2. El montón es un área de datos de "tiempo de ejecución", y el objeto instanciado en la clase asigna espacio desde el montón;
3. La asignación del espacio en el montón se establece a través de instrucciones como "Nuevo". El montón es el tamaño de memoria asignado dinámicamente, y la vida útil no necesita ser contada al compilador de antemano;
4. A diferencia de C ++, Java administra automáticamente el montón y la pila, y el recolector de basura puede reciclar automáticamente la memoria del montón que ya no se usa;
5. La desventaja es que, dado que la memoria se asigna dinámicamente en tiempo de ejecución, la velocidad de acceso a la memoria es más lenta.
Pila: almacenar tipos básicos y tipos de referencia, rápido
1. La estructura de datos de primera entrada y salida generalmente se usa para guardar parámetros y variables locales en el método;
2. En Java, todas las variables de tipos básicos (corto, int, largo, byte, flotante, doble, booleano, char) y tipos de referencia se almacenan en la pila;
3. El espacio vital de los datos en la pila se encuentra generalmente en los ámbitos actuales (el área encerrada por {...};
4. La velocidad de acceso de la pila es más rápida que el montón, solo solo de los registros directamente ubicados en la CPU;
5. Los datos en la pila se pueden compartir, y múltiples referencias pueden apuntar a la misma dirección;
6. La desventaja es que el tamaño de los datos y la vida útil de la pila deben determinarse y carecer de flexibilidad.
2. Configuración de memoria
1. Verifique el estado de la memoria de la máquina virtual
long maxControl = runtime.getRuntime (). maxMemory (); // Obtenga la cantidad máxima de memoria que la máquina virtual puede controlar Long Currentuse = runtime.getRuntime (). TotalMemory (); // Obtenga la cantidad de memoria utilizada actualmente por la virtual
Por defecto, maxControl = 66650112b = 63.5625m de Java Virtual Machine;
Si no hace nada, el UCTINEUSE se mide en mi máquina = 5177344b = 4.9375m;
2. Comando para establecer el tamaño de la memoria
-Xms <size> Establecer el tamaño inicial del montón Java: Establezca el tamaño de la memoria del montón de inicialización JVM; Este valor se puede establecer igual que -xmx para evitar la memoria de redistribución de JVM cada vez que se completa la recolección de basura.
-Xmx <size> Establezca el tamaño máximo del montón de Java: Establezca el tamaño de memoria de montón máximo del JVM;
-Xmn <size>: establezca el tamaño de la generación joven, todo el tamaño del montón = el tamaño de la generación joven + el tamaño de la generación anterior + el tamaño de la última generación.
-Xss <size> Establecer el tamaño de la pila de subprocesos Java: Establezca el tamaño de la memoria de la pila de subprocesos JVM;
3. Operaciones específicas (1) Configuración de memoria JVM:
Abierto MyEClipse (Eclipse) Window-Preferences-Java Instalado Jres-Edit-Default VM Argumentos
Enter: -xmx128m -xms64m -xmn32m -xss16m
(2) Configuración de memoria IDE:
Modifique la configuración en -vmargs en myeclipse.ini (o eclipse.ini en el directorio raíz de eclipse):
(3) Configuración de memoria de Tomcat
Abra la carpeta bin en el directorio raíz de Tomcat y edite Catalina. Bat
Modificar a: establecer java_opts = -xms256m -xmx512m
3. Análisis de error de MemoryError en Java Heap
Cuando se inicia el JVM, se usa la memoria del montón establecida por el parámetro -xms. A medida que el programa continúa y crea más objetos, el JVM comienza a expandir la memoria del montón para contener más objetos. El JVM también usa un recolector de basura para reciclar la memoria. Cuando casi se alcanza la memoria máxima establecida por -xmx, si no se puede asignar más memoria al nuevo objeto, el JVM lanzará java.lang.outOfMemoryError y el programa se bloqueará. Antes de tirar OutOfMemoryError, el JVM intentará liberar suficiente espacio con el recolector de basura, pero lanzará este error cuando descubra que todavía no hay suficiente espacio. Para resolver este problema, debe ser claro sobre la información sobre los objetos del programa, como qué objetos han creado, qué objetos ocupan cuánto espacio, etc. puede usar un perfilador o un analizador de montón para manejar los errores OutOfMemoryError. "Java.lang.OutofMemoryError: Java Heap Space" significa que el montón no tiene suficiente espacio y no puede continuar expandiéndose. "Java.lang.OUTOFMemoryError: Permgen Space" significa que la generación permanente está llena, y su programa ya no puede cargar la clase ni asignar una cadena.
4. Recolección de montón y basura
Sabemos que los objetos se crean en la memoria del montón, la recolección de basura es un proceso que elimina los objetos muertos del espacio del montón y devuelve esta memoria al montón. Para usar el recolector de basura, el montón se divide principalmente en tres áreas, a saber, nueva generación, generación antigua o generación titular, y espacio permanente. La nueva generación es un espacio utilizado para almacenar objetos recién creados y se usa cuando el objeto se crea recientemente. Si los usan durante mucho tiempo, el recolector de basura los trasladará a la antigua generación (o una generación titular). El espacio permanente es donde JVM almacena meta datos, como clases, métodos, grupos de cadenas y detalles a nivel de clase.
5. Resumen:
1. La memoria del montón de Java es parte de la memoria asignada al JVM por el sistema operativo.
2. Cuando creamos objetos, se almacenan en la memoria Java Heap.
3. Para facilitar la recolección de basura, el espacio del montón de Java se divide en tres áreas, llamadas generación de nueva generación, generación o generación teniente, y espacio permanente.
4. Puede ajustar el tamaño del espacio Java Heap utilizando las opciones de línea de comando JVM -xms, -xmx y -xmn.
5. Puede usar JConsole o Runtime.MaxMemory (), Runtime.totalMemory () y Runtime.FreemEmory () para ver el tamaño de la memoria de montón en Java.
6. Puede usar el comando "JMAP" para obtener el volcado de montón y usar "jhat" para analizar el volcado de montón.
7. El espacio de montón de Java es diferente del espacio de la pila. El espacio de la pila se usa para almacenar pilas de llamadas y variables locales.
8. El recolector de basura Java se usa para recuperar la memoria ocupada por objetos muertos (objetos que ya no se usan) y liberarla en el espacio de montón Java.
9. Al encontrarte con java.lang.ouToUfMemoryError, no tiene que preocuparse. A veces solo necesitas aumentar el espacio del montón. Pero si sucede con frecuencia, debe ver si hay una fuga de memoria en el programa Java.
10. Use herramientas de análisis de volcado Profiler y Heap para ver el espacio Java Heap, y puede ver cuánta memoria se asigna a cada objeto.
Explicación detallada del almacenamiento de la pila
Java Stack Storage tiene las siguientes características:
1. Se debe determinar el tamaño de los datos y el ciclo de vida en la pila.
Por ejemplo, el almacenamiento del tipo básico: int a = 1; Esta variable contiene un valor literal, A es una referencia al tipo INT, que apunta al valor literal de 3. Debido al tamaño y la vida útil de estos datos literales, estos valores litentes se definen fijamente en un bloque de programa, y después del bloqueo del programa, los valores literal desaparecen), existen en el pila de la velocidad de perseguir.
2. Los datos existentes en la pila se pueden compartir.
(1) Almacenamiento de datos de tipo básico:
como:
int a = 3; int b = 3;
El compilador primero procesa int a = 3; Primero, creará una referencia a la variable A en la pila, y luego averigüe si hay una dirección con un valor literal de 3. Si no se encuentra, abrirá una dirección con el valor literal de 3, y luego señale A a la dirección de 3. Luego procese int b = 3; Después de crear la variable de referencia de B, ya que ya hay un valor literal de 3 en la pila, B se apunta directamente a la dirección de 3. De esta manera, A y B apuntan a 3 al mismo tiempo.
Nota: Esta referencia literal es diferente a la de los objetos de clase. Suponiendo que las referencias de dos objetos de clase apunten a un objeto al mismo tiempo, si una variable de referencia del objeto cambia el estado interno del objeto, la otra variable de referencia del objeto refleja inmediatamente este cambio. En cambio, modificar su valor a través de una referencia literal no hará que otro valor se cambie en consecuencia. Como en el ejemplo anterior, después de definir los valores de A y B, deje a = 4; Entonces, B no será igual a 4, o igual a 3. Dentro del compilador, cuando se encuentre a = 4, volverá a buscar si hay un valor literal de 4 en la pila. Si no, vuelva a abrir la dirección para almacenar el valor de 4; Si ya existe, apunte directamente a esta dirección. Por lo tanto, el cambio en el valor A no afectará el valor b.
(2) Almacenamiento de datos de embalaje:
Clases que envuelven los tipos de datos básicos correspondientes, como entero, doble, cadena, etc. Todos estos datos de clase existen en el montón. Java usa la instrucción nueva () para mostrar el compilador y solo crea dinámicamente según sea necesario en el tiempo de ejecución, por lo que es más flexible, pero la desventaja es que toma más tiempo.
Por ejemplo: tome la cadena como ejemplo.
La cadena es un datos de embalaje especiales. Es decir, se puede crear en forma de cadena str = new String ("ABC"); o se puede crear en forma de cadena str = "ABC";. El primero es el proceso de creación de clase estandarizado, es decir, en Java, todo es un objeto, y un objeto es una instancia de la clase, todo creado en forma de nuevo (). Algunas clases en Java, como la clase DateFormat, pueden devolver una clase recientemente creada a través del método getInstance () de la clase, que parece violar este principio. En realidad, no es el caso. Esta clase usa el patrón Singleton para devolver una instancia de la clase, pero esta instancia se crea dentro de la clase a través de New (), que oculta este detalle desde el exterior.
Entonces, ¿por qué la instancia no se crea a través de New () en String Str = "ABC";? ¿Se violó el principio anterior? En realidad, no hay.
Acerca del trabajo interno de String Str = "ABC". Java convierte internamente esta declaración en los siguientes pasos:
a. Primero defina una variable de referencia de objeto llamada STR a la clase String: String Str;
b. Encuentre si hay una dirección con el valor "ABC" en la pila. Si no, abra una dirección con el valor literal "ABC", luego cree un nuevo objeto O de la clase de cadena y apunte el valor de cadena de o a esta dirección y anote el objeto de referencia o junto a esta dirección en la pila. Si hay una dirección con el valor "ABC", busque el objeto O y devuelva la dirección de O.
do. Punto a STR a la dirección del objeto O.
Vale la pena señalar que generalmente los valores de cadena en la clase de cadena se almacenan directamente. Pero en situaciones como String Str = "ABC";, su valor de cadena contiene una referencia a los datos presentes en la pila (es decir, String Str = "ABC"; tanto almacenamiento de pila como almacenamiento de montón).
Para ilustrar mejor este problema, podemos verificarlo a través de los siguientes códigos.
Cadena str1 = "ABC"; Cadena str2 = "ABC"; System.out.println (str1 == str2); //verdadero
(El valor de verdad se devuelve solo si ambas referencias apuntan al mismo objeto. Str1 y Str2 apuntan al mismo objeto)
El resultado muestra que el JVM creó dos referencias STR1 y STR2, pero solo se creó un objeto, y ambas referencias apuntaban a este objeto.
Cadena str1 = "ABC"; Cadena str2 = "ABC"; str1 = "bcd"; System.out.println (str1 + "," + str2); // BCD, ABC System.out.println (str1 == str2); //FALSO
Esto significa que el cambio en la asignación da como resultado el cambio en la referencia del objeto de clase, STR1 apunta a otro objeto nuevo, mientras que STR2 todavía apunta al objeto original. En el ejemplo anterior, cuando cambiamos el valor de Str1 a "BCD", el JVM descubrió que no hay una dirección para almacenar el valor en la pila, por lo que abrió esta dirección y creó un nuevo objeto cuyo valor de cadena apunta a esta dirección.
De hecho, la clase de cadena está diseñada para ser una clase inmutable. Si desea cambiar su valor, puede, pero el JVM crea en silencio un nuevo objeto basado en el nuevo valor en tiempo de ejecución (no se puede cambiar en función de la memoria original) y luego devuelve la dirección de este objeto a la referencia de la clase original. Aunque este proceso de creación es completamente automático, después de todo lleva más tiempo. En un entorno que sea más sensible a los requisitos de tiempo, tendrá ciertos efectos adversos.
Cadena str1 = "ABC"; Cadena str2 = "ABC"; str1 = "bcd"; Cadena str3 = str1; System.out.println (str3); // bcd string str4 = "bcd"; System.out.println (str1 == str4); //verdadero
La referencia al objeto STR3 apunta directamente al objeto apuntada por STR1 (tenga en cuenta que STR3 no crea un objeto nuevo). Después de que STR1 ha cambiado su valor, cree una referencia de cadena STR4 y apunte a un nuevo objeto creado al modificar el valor. Se puede encontrar que esta vez STR4 no creó un nuevo objeto, al darse cuenta de la compartir datos en la pila nuevamente.
Cadena str1 = nueva cadena ("ABC"); Cadena str2 = "ABC"; System.out.println (str1 == str2); //FALSOSe crearon dos referencias. Se crearon dos objetos. Las dos referencias apuntan a dos objetos diferentes.
Cadena str1 = "ABC"; Cadena str2 = nueva cadena ("ABC"); System.out.println (str1 == str2); //FALSOSe crearon dos referencias. Se crearon dos objetos. Las dos referencias apuntan a dos objetos diferentes.
Los dos códigos anteriores indican que mientras el objeto se cree con nuevo (), se creará en el montón y sus cadenas se almacenan por separado. Incluso si son los mismos que los datos en la pila, no se compartirán con los datos en la pila.
Resumir:
(1) Cuando definimos una clase usando un formato como String Str = "ABC";, siempre damos por sentado que creamos un objeto STR de la clase de cadena. ¡Preocuparse por la trampa! ¡El objeto puede no haber sido creado! Lo único que es seguro es que se crea una referencia a la clase de cadena. En cuanto a si esta referencia apunta a un nuevo objeto, debe considerarse en función del contexto, a menos que use el método nuevo () para crear explícitamente un nuevo objeto. Por lo tanto, para ser más precisos, creamos una variable de referencia STR a un objeto de la clase de cadena, que apunta a una clase de cadena con un valor de "ABC". Ser consciente de esto es útil para solucionar problemas de errores difíciles en los programas.
(2) usando String Str = "ABC"; puede mejorar la velocidad de ejecución del programa hasta cierto punto, porque el JVM decidirá automáticamente si es necesario crear un nuevo objeto basado en la situación real de los datos en la pila. Para el código de cadena str = new String ("ABC");, se crean nuevos objetos en el montón independientemente de si sus valores de cadena son iguales o no, si es necesario crear nuevos objetos, aumentando así la carga en el programa.
(3) Debido a la naturaleza inmutable de la clase de cadena (debido a que el valor de la clase de envoltorio no se puede modificar), cuando la variable de cadena debe transformarse con frecuencia, la clase StringBuffer debe considerarse para mejorar la eficiencia del programa.