Mecanismo de operación de la máquina virtual de Java, gestión de memoria, recolección de basura y cuatro referencias

Este artículo explica los puntos principales son los siguientes:

En primer lugar, ¿cuál es la JVM

Dos, el mecanismo operativo JVM

En tercer lugar, el mecanismo de gestión de memoria de Java

Cuatro, la recolección de basura de Java

Cinco, el algoritmo de recolección de basura de Java

Seis, clases java de cuatro tipos de referencias

A, JVM es y qué papel

JVM es un ordenador virtual que se pueden ejecutar código Java.

máquina virtual Java incluye un conjunto de instrucciones de código de bytes, un conjunto de registros, una pila, un método de recolección de basura montón y un dominio de almacenamiento.

código fuente de Java se compila primero después de los archivos de código de bytes correspondientes .class (una para cada archivo fuente para generar un java .class archivos de código de bytes correspondiente), resultando .class archivos por la JVM en el intérprete de código de bytes, y es la de Java instrucciones de máquina virtual de código de bytes conjunto .... compilado en código máquina en una máquina en particular.

archivos fuente de Java -> Generador -> archivos de código de bytes -> Jvm -> Codificación

Por lo tanto, el más importante es el papel de la JVM para cada sistema operativo para desarrollar su intérprete correspondiente, siempre y cuando se tiene una versión correspondiente del sistema operativo JVM, por lo que este código de compilador de Java puede estar en funcionamiento, esta es la primera de Java puede ser compilado, la razón corriendo por todos lados.

Java JVM cuando el programa comienza su ejecución, y que estaba funcionando, se detiene cuando termina el programa.

Dos, el mecanismo operativo JVM

1, el sistema estructural JVM

En primer lugar, Java archivos de código fuente (.java de extensión) son de Java compilador para compilar el archivo de código de bytes (sufijo .class), y luego en el cargador de clases JVM carga el código de bytes para cada archivo de clase, después se cargó, a que se refiere JVM motor de ejecución de ejecución (procedimiento que comprende además realizar compilador bytecode en código máquina), cuando se ejecuta motor de ejecución de JVM primera bytecode archivos de clase escaneada cuatro veces para garantizar la seguridad del tipo definido, a continuación, comprobar los datos de referencia nula transfronterizo, recolección de basura automática. A lo largo de toda la ejecución del programa, JVM será utilizada para los datos de programa de tienda y la información relacionada usado durante necesidad de ejecución un poco de espacio, este espacio se denomina generalmente como Runtime Data Area (área de datos en tiempo de ejecución), que es, a menudo decimos que la JVM memoria

cargador de clases en el cargador de clases de arranque (no heredadas cargador de clases, que forma parte de la máquina virtual, responsable de cargar el Java bibliotecas del núcleo de la implementación del código nativo, incluyendo la carga JAVA_HOME en jre / lib / rt.jar en toda clase); el cargador de clases de extensión es (responsable del directorio de extensiones para encontrar la JVM para cargar la biblioteca de extensiones de Java, incluyendo JAVA_HOME en jre / lib / ext paquete / frasco bajo xx.jar o -Djava.ext.dirs directorio especificado); la clase del cargador de aplicaciones ( ClassLoader.getSystemClassLoader () es responsable de cargar la ruta de clases de Java ruta de clase en clase)

mecanismo de carga Clase del proceso:

1, de carga : Carga encontrar archivo binario, clase flujo de bytes binaria adquirida por el nombre completo de una clase, y la estructura de almacenamiento estático representado por el flujo de bytes en la estructura de datos en tiempo de ejecución del método de la zona; pila en Java la generación de un representante de los objetos esta clase java.lang.Class, tales como un método de acceso a la zona de datos de la entrada.

2, la verificación : flujo de bytes clase con el fin de asegurar que la información contenida en el archivo para cumplir con los requisitos de la máquina virtual actual, la verificación completa de las cuatro etapas siguientes: la verificación del formato, la verificación de metadatos, validación y verificación de código de bytes de referencias simbólicas.

3. Preparación : Preparar etapa es la etapa de la asignación formal de la memoria y establecer el valor inicial de la variable de clase es una variable de clase, se asignará que la memoria en el área de método

4, el análisis sintáctico : análisis sintáctico etapa es una máquina virtual a un simbólicos referencias piscina constante en referencia directa al proceso de

5, la inicialización : fase de inicialización es inicializar las variables de clase y otros recursos de acuerdo con los procedimientos especificados por el plan programador subjetiva, es decir, durante la ejecución del constructor de la clase () método

En tercer lugar, el mecanismo de gestión de memoria de Java

memoria JVM dividida en zonas separadas 5, la pila de memoria, memoria de pila, el área de método y el método de área local, el contador de programa

memoria de pila:

       1, almacenar el objeto mismo, variables miembro memoria

       2, la memoria se asigna dinámicamente (asignación de operación de memoria) de

       3, JVM solamente una memoria de pila, cuando los datos dentro de todos los hilos comparten el

       4, la memoria de pila por la JVM la GC recuperó

       5, los más propensos a OOM

memoria de pila:

       1, el método de almacenamiento de los tipos de bases de datos locales de las variables y de encargo referencia de objeto , es una referencia al objeto en mente, el objeto en sí mismo se encuentra en la memoria de pila.

       2, una rosca correspondiente a una pila, cada uno de la pila de datos son privados y no se puede acceder otras pilas.

       3, una pila puede tener una pluralidad de marcos de pila, un marco de pila corresponde a un método de la primera llamada .

       4, la memoria de pila se libera automáticamente después del final del método, la memoria de la que se creará recupera automáticamente

Cada llamada de un método, que el método asignará un marco de pila de memoria, la memoria de pila se utiliza para almacenar este método se define principalmente tipos de datos básicos de las variables y devuelve el resultado, si el objeto personalizado, el objeto en sí es sin duda la memoria asignada al montón de ellos, pero se hace referencia al objeto en esta memoria de pila, después de la aplicación del método, la memoria de pila también se ha recuperado (pop), todas las variables locales definidas ciclo de vida se ha acabado, la memoria automática directamente liberar, el objeto de referencia no existe, y por lo tanto, el método de tipo de referencia objeto definido en el montón no tendrá referencias a ella, de modo que cuando la GC de exploración, se dispondrá como la recogida de basura para el comienzo de la descarga la memoria ocupada por objetos

montones de métodos nativos:

       pilas de método nativo y funciones de la pila JVM muy similares, tabla almacena variables locales los métodos nativos (C / C ++), el método de la información local del operando pila y similares.

área de método:

      El área de almacenamiento método ya está cargado de datos de máquina virtual:

     1,  cuota de hilos

     2, el tipo de datos almacenados: tipo de información, una constante, las variables estáticas, el compilador tiempo para compilar el código, y similares.

Contador de Programa:

      En el modelo conceptual de la intérprete de bytecode JVM está trabajando por el cambio del valor del contador para seleccionar la siguiente instrucción de código de bytes a ser ejecutado . Ramas, bucles, saltos, manejo de excepciones, reanudar el hilo y otras funciones básicas necesitan confiar en que el contador completa.

     Multithreading es el hilo JVM alternativamente conmutado por el tiempo y la distribución ejecución procesador de lograr, para la conmutación entre las piezas de contador de hilo puede ser restaurado a la posición correcta se lleva a cabo, de modo que cada hilo tendrá un contador de programa independiente .

java objeto es creado e inicializado:

Una vez creado un objeto Java, tendrá su propia pila de memoria en un área, entonces el proceso es para inicializar el objeto. Típicamente por el objeto a ser construido se inicializa , la configuración es el mismo método especial no devuelve un valor al nombre de la clase; si una clase no define un constructor, el sistema generará automáticamente un constructor predeterminado no acepta ningún parámetro; pero si ha definido un constructor (independientemente de si hay parámetros), el compilador no creará automáticamente un constructor por defecto, podemos hacer varias sobrecargas para el constructor (es decir, la lista de argumentos aprobó una serie o un orden diferente), también puede llamar a otro constructor en un constructor, pero sólo se llama una vez, y el constructor debe estar colocado en el comienzo mismo, de lo contrario el compilador se quejará.

A continuación, la inicialización miembro de la clase es cómo hacerlo? Orden de qué se trata? java todas las variables antes de su uso deben obtener correctamente inicializado, incluso las variables locales del método, si no inicializado en tiempo de compilación se produce un error, y si la variable es un miembro de la clase, incluso si lo hace asignación no inicialización, el sistema también uno de su valor inicial, el valor inicial de, por ejemplo char, tipo int es 0, entonces la referencia de objeto no es predeterminado inicializa en NULL.

Clase secuencia de inicialización miembro de resumen: construcción general de nuevo, después de mi difunto padre después de la primera subclase clase estática, consulte el mismo nivel de orden de escritura

1. En primer lugar ejecutar un bloque de variables estáticas padre y el código, a continuación, realizar las variables estáticas de subclases y bloque de código
       2 para llevar a cabo una variable y el padre bloque de código de clase normal, a continuación, realizar el constructor de la clase padre (método estático) 
       3. La primera sub ejecución Variables comunes y el bloque de código de clase, a continuación, realizar constructor sub-clase (método estático) 
       método 4.static inicializa antes con el método convencional, la inicialización estática se lleva a cabo sólo en el momento necesario y inicializan sólo una vez.

Nota: El constructor de una subclase, independientemente del constructor que no toma ningún argumento, se irá al constructor por defecto sin parámetros para encontrar la clase padre. , Entonces la subclase debe llamar a los parámetros del constructor de la clase padre del niño llave con la cena si ningún constructor sin argumentos de los padres, de lo contrario no se compila.

Cuatro, la recolección de basura de Java

java gestión automática de memoria, la gestión automatizada de los dos aspectos principales: una es el objeto automáticamente asignar memoria , uno es automáticamente reclama objetos de memoria, y regiones de memoria de estos dos problemas implicados es el modelo de memoria de Java de la pila de área . Sabemos que la recolección de basura es una característica importante del lenguaje Java, que puede prevenir con eficacia las pérdidas de memoria para asegurar el uso eficaz de la memoria, de modo que los programadores de Java en la preparación del programa ya no hay necesidad de considerar el problema de la gestión de memoria

principales procesos de recolección de basura de Java: Cuando se activa el GC (en cualquier momento puede desencadenar), el recolector de basura se escanear toda el área de memoria, si encuentra un objeto particular no tiene memoria cuando se hace referencia a ella, que la GC será ocupado por el objeto la recuperación apagado durante GC activa, además de los hilos de GC están bloqueados, lo que provocó GC sólo después de la finalización de otros hilos continuará.

A partir de este proceso nos podemos encontrar tres problemas:

1. ¿Qué tipo de memoria pueden ser recuperados (dos algoritmos clásicos de si un objeto puede ser reciclado: Referencia de conteo y análisis de accesibilidad algoritmo)

2, cuando se recupera la memoria (al escanear GC)

3, cómo se recupera de la memoria (utilizando el algoritmo de recolección de basura --- se mencionará más adelante)

El método de la zona de recuperación:

recuperación de la memoria método de la zona objetivo es principalmente para  el reciclaje de piscina constante  y  descarga de tipo . constante reciclaje de los residuos y el reciclaje de objeto Java montón es muy similar. Para recuperar la piscina literal constante, por ejemplo, si una cadena "abc" ha entrado en la piscina constante, pero el sistema actual no tiene ningún un objeto String se llama el "abc", en otras palabras, no hay una cadena constante referencia de objeto "abc" del grupo de constantes, no hay otras referencias a estos literal, si el reciclaje de memoria se produce en este momento, y si es necesario, el sistema constante "abc" será "invitados" a la piscina constante. Símbolos otras clases (interfaces), métodos, campos en la referencia de la piscina constante también es similar.

Constant se determina si o no una "constante residuos" es relativamente simple, y para determinar si una clase es "clase inútil" es relativamente muchas condiciones muy duras. Clase necesita para cumplir con las tres condiciones siguientes con el fin de ser considerado como "clase inútil":

  • Todas las instancias de la clase han sido recuperados, que se amontone la Java cualquier instancia de la clase no existe;

  • ClassLoader cargado clase se ha recuperado;

  • Java.lang.Class correspondiente a la clase de objeto no se hace referencia en cualquier lugar, no por el método de acceso a la clase reflejada en cualquier lugar.

Cómo determinar si un objeto se puede reciclar?

1, la notación de referencia: determinar el número de referencias a los objetos

algoritmo de recuento de referencia para determinar si un objeto se determina por el número de objetos de referencia se puede recuperar.

algoritmo de recuento de referencias es una estrategia temprana en el recolector de basura. En este método, cada instancia de objeto tiene un recuento de referencia de la pila. Cuando se crea un objeto, y la instancia de objeto se asigna a una variable de referencia, el recuento de referencia de la instancia de objeto se establece en 1. Cuando cualquier otra variable se asigna como una referencia al objeto, la referencia de objeto para el recuento de ejemplo se incrementa 1 (a = b, b de la instancia de objeto de contador de referencia más 1), pero una referencia a una instancia de objeto excede el ciclo de vida de o cuando se establece un nuevo valor, la instancia de la cuenta de referencia objeto por 1. En particular, cuando una instancia de objeto se basura recogida, cualquier instancia de objeto que hace referencia a un contadores de referencia se decrementa. Cualquier recuento de referencia de una instancia de objeto 0 puede ser basura recogida.

colector recuento de referencias puede ser rápidamente ejecutado y entretejido en el programa se está ejecutando, las necesidades de los programas más favorable desde hace mucho tiempo no va a ser interrumpida por el entorno en tiempo real, pero es difícil de resolver el problema de la circulación mutua entre las referencias a objetos. Como se muestra en los siguientes procedimientos esquemáticos y recuento de referencia entre el objeto y objA objB nunca es 0, entonces los dos objetos nunca pueden ser recuperados.

public class ReferenceCountingGC {
  
        public Object instance = null;
 
        public static void testGC(){
 
            ReferenceCountingGC objA = new ReferenceCountingGC ();
            ReferenceCountingGC objB = new ReferenceCountingGC ();
 
            // 对象之间相互循环引用,对象objA和objB之间的引用计数永远不可能为 0
            objB.instance = objA;
            objA.instance = objB;
 
            objA = null;
            objB = null;
 
            System.gc();
    }
}

El código se objA y la más retrasada dos objB asignados a null, y que es el punto objA objB ha sido ya no se puede acceder al objeto, sino porque se refieren a la otra, haciendo que se contador de referencia no es 0, entonces el recolector de basura es que nunca se recuperarán.

algoritmo de análisis 2, asequibilidad: determinar si las cadenas de referencia objeto hasta

algoritmo de análisis de alcanzabilidad determina si el objeto es referenciado por una cadena de seguimiento para determinar si el objeto se puede recuperar.

algoritmo de análisis de alcanzabilidad se introduce desde la teoría matemática discreta del gráfico, el programa de todas las referencias a la relación visto como un mapa, a través de una serie de objetos llamados "Roots GC" como punto de partida, estos nodos desde el principio hacia abajo buscar, llamada ruta de búsqueda de cadena de referencia atravesado (cadena de referencia). Cuando un objeto no está conectado a cualquier cadena Roots GC de referencia (en las palabras de la teoría de grafos, es el objeto a GC Roots inalcanzables), entonces se demuestra que este objeto no está disponible, como se muestra en la figura. En Java, se puede utilizar como un objeto GC Root incluyen los siguientes:

  • Máquina de pila virtual (tabla de variables marco de pila local) en la referencia de objeto;

  • El método del objeto en unos referencias atributo de clase estática;

  • Un método de región constante de referencia de objeto;

  • método nativo apila los objetos locales método de referencia;

Cinco, el algoritmo de recolección de basura de Java

1, el algoritmo de etiquetado claro

Este es el algoritmo de recolección de basura más básica, que es la razón más básica es porque es más fácil de implementar, es también las ideas más simples. Marcos - algoritmo de barrido se divide en dos etapas: la marca de fase y fase de limpieza. tarea fase de marca es marcar todos los objetos que deben ser recuperados, la fase de limpieza es para recuperar el espacio marcado ocupada por el objeto . La figura mediante el siguiente procedimiento:

Marcos - algoritmo de barrido tiene dos problemas principales:

  • Eficiencia: la marca y la eficiencia de barrido de los dos procesos no es muy alta;

  • problema de espacio: marca - algoritmo de compensación no requiere objeto en movimiento, y el objeto no se procesa sólo para sobrevivir, se producirá un gran número de marcas de fragmentación de memoria discreta después de desechos espaciales de compensación puede causar demasiada después de que el programa se está ejecutando cuando la necesidad de asignar objetos de gran tamaño, no puede encontrar suficiente memoria contigua y tuvo que disparar otra operación de recolección de basura con antelación.

2, la replicación algoritmo

Con el fin de abordar el algoritmo deficiencias de marca y barrido, Copia de algoritmo que fue apagado. Se divide en la capacidad de memoria disponible por dos de igual tamaño, utiliza sólo uno de ellos. Cuando esta pieza de la memoria se agota, la copia también será objeto de sobrevivir a otra pieza en la parte superior del espacio de memoria que se ha utilizado una vez y luego salga limpio, por lo que no es propenso a los problemas de fragmentación de memoria. La figura mediante el siguiente procedimiento:

Copiar las ventajas y desventajas del algoritmo:

Ventajas: una operación eficiente y no propensos a la fragmentación de la memoria

Desventajas: el uso de espacio de memoria para hacer un alto precio, debido a que la memoria RAM disponible reduce a la mitad. Obviamente, el número de objetos en vivo con la eficiencia del algoritmo de copia cuánto de una gran relación, si sobreviven muchos objetos, entonces la eficiencia de los algoritmos de copia se reducirá considerablemente.

3, marcando Algoritmo de clasificación

Con el fin de abordar las deficiencias Copia de algoritmos, hacer pleno uso de espacio de memoria, que puso algoritmo de Mark-compacto hacia adelante. La fase marca de algoritmo y Mark-Sweep mismo, pero después de que el indicador de finalización, no se recicla directamente al limpiar el objeto, pero el objeto se mueve a un extremo de la supervivencia, y desprender la suciedad fuera de la final de la parte exterior de memoria de la frontera . La figura mediante el siguiente procedimiento:

4, el algoritmo de colección generacional

Para un sistema grande, cuando los objetos y métodos para crear variables tiempo relativamente largo, objeto de memoria montón será más y más, si el objeto se analiza uno por uno si recuperado, es inevitable que resultará en la ineficiencia. algoritmo de recolección generacional se basa en este hecho: ciclo de vida (supervivencia) de diferentes objetos no es la misma, pero diferente del ciclo de vida de un objeto situado en las diferentes áreas del montón, por lo que la memoria del montón en diferentes regiones adoptan diferentes estrategias para el reciclaje se puede mejorar la eficiencia de la JVM. máquinas virtuales uso comercial contemporáneos son algoritmo de recolección generacional: la nueva generación de la baja tasa de supervivencia del objeto, en el uso de algoritmo de replicación; alta tasa de supervivencia de edad, utilice algoritmo de etiquetado claro o etiquetas para organizar algoritmo. la memoria del montón Java generalmente se divide en la nueva generación, y la generación permanente de módulo de tres años de edad, como se muestra a continuación:

1). New Generation (Generación Young)

  Nueva Generación meta es recoger rápido de la corto ciclo de vida de esos objetos, en circunstancias normales, todos los objetos recién generadas están en la primera de la nueva generación. La nueva memoria generación de acuerdo con 8: 1: 1 en una región eden y (survivor0, survivor1) La zona de dos superviviente, la mayoría de los objetos generados en la zona de Eden. Durante la recolección de basura, el primer distrito eden objetos vivos copiados en el área survivor0, zona eden después vació, cuando el área survivor0 también está lleno, entonces el área de la zona Edén y copia de supervivencia survivor0 objetos a la zona survivor1, a continuación, se vació y el Eden área survivor0, esta área survivor0 vez que se vacía, entonces cambian los papeles survivor0 área área y survivor1 (es decir, el siguiente basura escaneará la zona de Eden y recuperación de la zona survivor1), que es, manteniendo el área survivor0 está vacío, y así sucesivamente. En particular, cuando el área de almacenamiento survivor1 no es suficiente para objetos vivos región y área survivor0 eden, será en vivo objetos colocados directamente en la vieja era. Si el año de edad estaba lleno, que dará lugar a una FullGC, es la nueva generación, el año de edad son reciclados. Nota, GC Cenozoico también se llama MinorGC, frecuencia de ocurrencia MinorGC es relativamente alta, por lo que no es necesariamente el único desencadenante zona de Eden es completa.


2) (Generación antiguo de antiguo)

  La tienda de edad, son algunas de ciclo de vida más largo del objeto, como se describió anteriormente, como el objeto pasó por N veces después de la recolección de basura está todavía vivo en la nueva generación será colocado en la vieja era. Además, la memoria de la vejez es también mucho más grande que la nueva generación (más o menos la proporción es de 1: 2), cuando el año de edad gatillo completo Mayor CG (Full GC), de edad el objeto sobrevivió mucho tiempo, por lo que la frecuencia de aparición de relativamente baja FullGC .


3) Generación Permanente (Generación Permanente)

  Se utiliza principalmente para el almacenamiento de la generación permanente de archivos estáticos, como las clases y métodos Java. Cuando la generación permanente de ningún impacto significativo en la recolección de basura, pero algunas aplicaciones se pueden generar de forma dinámica, o llame a alguna clase, tales como el uso de un reflexivo, la agencia dinámica, CGLIB y otro marco de código de bytes, en este momento necesidad de establecer una proporción relativamente grande de generación permanente de espacio para almacenar la carrera durante la nueva clase.

Seis, java 4 en los tipos de referencia

1, referencias fuertes

ubicuo fuerte en la referencia se refiere al código de programa, similar a "Object obj = new Object () " de tales referencias. Mientras hay referencias fuertes, el recolector de basura nunca objetos referenciados recuperado fuera. Si la memoria es insuficiente, JVM prefiere tirar OutOfMemoryError error de desbordamiento de memoria no se recupera fuertemente referencias, si desea recuperar JVM fuerte referencia al tipo de objeto, cambiará su referencia en null , referencia nula JVM se recuperará esta formación en el momento adecuado .

2, referencias suaves

No usa para describir algunas de las referencias con suaves, pero no es obligatorio objeto. Para referencias suaves asociadas con el objeto en el proceso de análisis GC, sólo cuando no hay suficiente espacio en la memoria, el objeto será recuperado de las referencias suaves . Si la recuperación todavía no es suficiente memoria, se lanzará la memoria excepción de desbordamiento. Después de JDK 1.2, que ofrece clases SoftReference para implementar referencias suaves.

referencias suaves son adecuados para el almacenamiento en caché en la memoria es suficiente que los valores de referencia directamente a través del suave, sin tener que datos de la consulta de fuentes reales, puede mejorar significativamente el rendimiento del sitio web, cuando no hay suficiente memoria, lo que permite JVM para el reciclaje, eliminando así la caché, que cuando sólo datos de la consulta de fuentes reales

3, las referencias débiles

referencias débiles también se utilizan para describir objetos no esenciales, pero su fuerza es más débil que el número de referencia blando está asociada con una referencia débil a un objeto sólo puede sobrevivir hasta que se produce la siguiente recogida de basura. Cuando el trabajo de recolector de basura, sin tener en cuenta la adecuación de la memoria actual sólo se recuperará objetos perdidos están asociados con referencias débiles . Después de JDK 1.2, proporcionado clases WeakReference para implementar débil.

Como puede verse, el objeto de referencia asociado débil, se recuperará en una llamada posterior para el recolector de basura,

referencias débiles pueden evitar pérdidas de memoria en la función de devolución de llamada, ya que la función de devolución de llamada es clases internas anónimas, unas clases internas no estáticos llevará a cabo una fuerte referencia a la clase exterior de forma predeterminada , cuando la JVM en el momento de la recuperación fuera de la clase, en cuyo caso la función de devolución de llamada en un hilo cuando la devolución de llamada, JVM no se recogió fuera de clase, lo que resulta en una pérdida de memoria.

4, la referencia phantom

referencia virtual es una referencia a la relación más débil. Si hay un objeto de referencia fantasma ha completamente no afectará su tiempo de supervivencia, si un objeto está asociado con una referencia virtual, la máquina virtual a continuación, en cualquier momento puede ser recuperado, la referencia virtual no puede ser utilizado solo y debe ser usado en conjunción con una cola de referencia . Después de JDK 1.2, proporcionado clases PhantomReference para implementar referencia virtual.

Cuando el recolector de basura está listo para recuperar un objeto, si se encuentra asociado con la referencia virtual. En el pasado se recuperaría se añade esta referencia fantasma a la cola de referencia, el programa puede determinar si unirse a la referencia fantasma referencia de cola de entender que se hace referencia si los objetos a ser recuperados, si realmente desea que se recupere, que pueden hacer algunos trabajos de acabado antes de reciclar.

 

Publicado 23 artículos originales · ganado elogios 19 · vistas 2134

Supongo que te gusta

Origin blog.csdn.net/huyinda/article/details/104769688
Recomendado
Clasificación