Análisis de la implementación de bloqueo de objetos en una máquina virtual

I. Introducción

Los problemas de sincronización de subprocesos se encuentran a menudo en el proceso de programación.Hay muchas soluciones a los problemas de sincronización en Java (sincronizado, JUC, operaciones atómicas, volátiles, variables de condición, etc.), entre las cuales sincronizado es el más conveniente, fácil de usar y es también el método más común en la programación de Java.El esquema de protección de la sección crítica más utilizado. Este artículo describe principalmente el conocimiento relevante de los bloqueos de objetos y presenta en detalle los principios de implementación de la máquina virtual para los métodos clave de sincronizado y Objeto.

2. Cómo usar el bloqueo de objetos de Java

2.1 Sincronización de métodos de instancia

d1d01f87728ad1735bb712b9907ff4af.png

sincronizado Modifica el método de instancia, la sincronización solo funciona en el método del objeto actual, y solo un subproceso puede ingresar a este método del objeto al mismo tiempo. No hay protección de exclusión mutua para esta función de diferentes objetos.

2.2 Sincronización de métodos estáticos

ef4342141d7934a83ba6dfd87b6df77a.png

sincronizado Modifica el método estático, la sincronización funciona en el método del objeto de clase actual y solo un subproceso puede ingresar al método al mismo tiempo.

2.3 Sincronización de bloques de código

66e05b57c9514e23867dc73650bbed7a.png

En la mayoría de los casos, no es necesario proteger todo el método. Cuando sincronizado modifica un bloque de código, el acceso al bloque de código depende del acceso exclusivo del bloqueo del objeto del objeto, y solo un subproceso mantiene el bloqueo del objeto del objeto al mismo tiempo. tiempo.

Más precisamente, la palabra clave sincronizada es efectiva dependiendo del bloqueo del objeto.La instrucción monitorear-entrar obj se genera al comienzo de cada bloque de sincronización sincronizada, y la instrucción monitorear-salir obj se genera al final del bloque de sincronización, donde obj es un objeto utilizado para controlar el acceso exclusivo. Solo un subproceso puede contener el bloqueo de objeto de obj a la vez. En 2.1, sincronizado se basa en objetos de columna reales, en 2.2, sincronizado se basa en objetos de clase y en 2.3, sincronizado se basa en objetos de objeto.

Cuando un objeto controla múltiples bloques de código, múltiples bloques de código también son acceso mutuamente excluyente, como el siguiente código:

a00feb728805163c754b21d85fe39c89.png

Aunque el bloque de código ① y el bloque de código ② están en las dos funciones, los objetos de los que depende la sincronización son ambos objetos, y estos dos bloques de código también tienen acceso mutuamente excluyente.

2.4 Cómo usar Object wait() y notificar()

Object, como clase base de todas las clases, implementa el método de objeto. El uso típico es el siguiente:

b5e9a2c2ffc6d65d850318b4ab3d2827.png

Después de que el subproceso 1 mantiene el bloqueo del objeto y llama al método object.wait(), el subproceso ingresa al estado ESPERANDO, libera el bloqueo del objeto y espera a que otros subprocesos lo despierten.

Cuando el subproceso 2 retiene el bloqueo del objeto del objeto y llama al método object.notify(), active el subproceso 1, el subproceso 1

Vuelva a adquirir el bloqueo de objeto para continuar con la ejecución. Descripción del método de clase de objeto:

954e562836002bf4156e61b5984f5643.png

3. Estructura de memoria de objetos de Android

3.1 Estructura de la memoria de objetos

cc6e3f61b546d835864543ae83a3c819.png

La memoria de objetos de instancia de una clase se compone principalmente de 3 partes:

1) Encabezado del objeto: el encabezado del objeto incluye dos campos kclass_ y monitor_, donde kclass_ almacena un puntero a un objeto de clase, a través del cual se puede encontrar la clase correspondiente del objeto, y monitor_ se usa para almacenar los datos de identificación del objeto. en tiempo de ejecución, por ejemplo: indicador GC, código hash, estado de bloqueo y otra información, que se analizará en detalle más adelante.

2) Datos de instancia, esta parte almacena valores de variables de instancia, los valores de variables de instancia de clase principal vienen primero, siguen las subclases y los valores de variables de instancia se ordenan en el siguiente orden:

34a01576e10f76483f392420f0cbb4e4.png

3) Relleno de alineación, el objeto se alinea de acuerdo con 8 bytes en la memoria, si la parte de datos de la instancia no está alineada de acuerdo con 8 bytes, el relleno se alinea con 8 bytes.

3.2 monitor_ análisis de campo

El campo monitor_ se define en art/runtime/mirror/object.h, el tipo es uint32_t y tiene principalmente las tres funciones de operación siguientes.

a8f87fe4a2cb32fc310141df49ca18d3.png

Los parámetros de entrada de las funciones SetLockWord y CasLockWord o el valor de retorno de la función GetLockWord en la función de operación contienen la variable LockWord, y la operación en el campo monitor_ se realiza a través del valor de LockWord.

Veamos de nuevo la definición de LockWord:

La definición de la clase LockWord está en el archivo art/runtime/lock_word.h A partir de los comentarios, podemos ver que existen principalmente 4 estados para el uso de LockWord, como sigue:

f04f08b0d796cd3a636cd5b17737d1f2.png

El diseño de LockWord es muy delicado, cada bit de datos de 32 bits se utiliza por completo y se distinguen bien los diferentes estados. Cada estado se describe en detalle a continuación:

  1. En el estado desbloqueado/delgado, los 31-30 bits son 00, y el estado predeterminado es el estado desbloqueado. Cuando el objeto realiza la sincronización de subprocesos, se convierte en un estado de bloqueo delgado. 27-16 bits registran el número de reingresos de bloqueo delgado, y 15-0 bits registran el tiempo de espera.Ahí está la identificación del hilo de la cerradura delgada.

  2. Los 31-30 bits son 01 en el estado de bloqueo completo.Cuando el objeto está bloqueado en el estado de bloqueo delgado y hay un subproceso nuevo (no propietario) compitiendo con él, después de un período de espera adecuado (llamada sched_yield, adquisición cíclica de el estado de bloqueo delgado), todavía no se puede obtener Si el objeto está bloqueado, se convertirá al estado de bloqueo pesado y se asignará un recurso de Monitor para el objeto.

  3. En el estado de estado hash, 31-30 bits son 10, y 27-0 bits almacenan el código hash del objeto. En otros modos, el código hash se almacenará en el objeto Monitor asociado con el objeto.

  4. En el estado del estado de la dirección de reenvío, 31-30 bits son 11. En la fase de copia del GC de copia concurrente, cuando se copia un objeto, apunta a la dirección del objeto copiado. Cuando el subproceso accede al objeto, accede al nuevo a través del dirección de reenvío.objeto.

El bit 29 es el bit de marca, que puede determinar rápidamente si se ha marcado para evitar el marcado repetido.

El bit 28 es el bit de barrera de lectura. Si el bit del objeto LockWorkd está configurado, entrará en la ruta lenta al acceder a los miembros del objeto y juzgará si el objeto necesita ser actualizado. Si necesita ser actualizado, se devolverá la dirección del objeto copiado.

Cuarto, análisis de código de bloqueo de objetos

4.1 Primero, veamos un fragmento de código

f6e99c99bab90d7a0c9f30104f652f15.png

Este código es relativamente simple, principalmente tiene los siguientes dos puntos centrales:

1) Durante la ejecución del subproceso principal, el objeto obj se utiliza para la sincronización del subproceso y se llama a la función obj.wait(), de modo que el subproceso se bloquea en el bloqueo del objeto obj y espera a que se active.

2) Se crea un subproceso anónimo en la función principal. El subproceso primero duerme durante 2000 ms y luego despierta el subproceso bloqueado en el bloqueo del objeto obj.

4.2 Compile TestDemo.java, el comando es el siguiente:

f90b5b4567f6fec132a24306bde5884e.png

1). Javac compila el archivo TestDemo.java en un archivo TestDemo*.class y cada clase genera un archivo de clase durante el proceso de compilación de Java.

2) El comando .d8 genera el archivo de formato dex (archivo ejecutable Dalvik) correspondiente después de compilar, refactorizar, reorganizar, comprimir y ofuscar el archivo TestDemo*.class.

3) El comando .dexdump.exe puede ver la información detallada del formato de archivo dex, como información de verificación, información de encabezado dex, información de CFG para generar dex, información de desmontaje dex, etc. dexdump.exe –comando de ayuda

Ver el desmontaje a través de dexdump.exe –d classes.dex

La información de la instrucción del método de ejecución es la siguiente:

abd7ca36d78bc2f800a90f7519fd6e08.png

La información de instrucciones de la función principal es la siguiente:

dec64ca23a1dc6a41127fad88a629dc8.png

Algunas instrucciones se analizan de la siguiente manera:

86d460240f73e18405a853454dd547db.png

Para obtener el formato detallado de las instrucciones de dex, puede leer la documentación oficial de Google:

https://source.android.com/docs/core/runtime/dalvik-bytecode

Este artículo se centra en la implementación detallada de monitor-enter, monitor-exit, Object.wait() y Object.notify() en la máquina virtual.

4.3. Análisis del proceso Object.wait()

La relación de llamada de Object.wait() es la siguiente:

655d2977d5b306f699254a7a11180135.png

La clase Object es la clase padre de todas las clases.El método público wait() se puede llamar en cualquier clase, y finalmente se llama al método estático de espera del archivo monitor.cc de la máquina virtual.

02eec4a45b3c0153d73f6448d419584b.png

Primero construya un objeto Handle h_obj que opere obj y notifique al sistema de depuración jvmti que se ha producido un evento JVMTI_EVENT_MONITOR_WAIT a través de la función ObjectWaitStart.

JVMTI (JVM Tool Interface) es una interfaz de programación nativa proporcionada por la máquina virtual Java, que se puede utilizar para desarrollar y monitorear la máquina virtual, ver el estado interno de la JVM y controlar la ejecución de la aplicación JVM. Las funciones que se pueden realizar incluyen, entre otras: depuración, monitoreo, análisis de hilos, herramientas de análisis de cobertura, etc.

1a5a23f89ade94d84880be570a1ecfc6.png

Primero obtenga el campo LockWord del objeto h_obj, y la función lock_word.GetState() obtiene el estado de bloqueo actual, principalmente en las siguientes situaciones:

1). hash o estado desbloqueado:

Debido a que llamar al método wait() debe mantener el bloqueo del objeto, estos dos estados no aparecerán y, de ser así, se lanzará una IllegalMonitorStateException.

2) Estado de bloqueo fino:

Cuando el subproceso que mantiene el bloqueo del objeto no es el subproceso a esperar, también se lanza una IllegalMonitorStateException.Cuando el subproceso que retiene el bloqueo es el mismo que el subproceso a esperar, es necesario inflar el bloqueo delgado en un bloqueo gordo.El proceso de inflar está en monitor- Analizar en el análisis del comando enter.

Cuando el inflado de bloqueo del objeto está en el estado de bloqueo completo, llame al método de instancia Wait del objeto Monitor para permitir que el subproceso entre en el estado de suspensión y espere.

4.4 Análisis del proceso Object.notify()

98f27a4e7581c56aeffe8134f5eacbb5.png

Aquí analizamos directamente la función DoNotify:

fc8a81f8bec0801189144e838a92ae25.png

Obtenga el estado de bloqueo del objeto obj actual a través de lock_word.GetState(), principalmente en las siguientes situaciones:

1) hash o estado desbloqueado:

Se lanza una IllegalMonitorStateException.

2) Estado de bloqueo fino:

Cuando el subproceso que contiene el bloqueo del objeto no es el subproceso que se debe notificar, también se lanza una IllegalMonitorStateException.Cuando el subproceso que contiene el bloqueo es el mismo que el subproceso que se debe notificar, significa que no hay ningún subproceso que deba ser notificado y despertado, y regresa directamente.

3) Estado de bloqueo de grasa:

En el proceso Object.notify(), si el parámetro notify_all es falso, llama directamente a mon->Notify(self);notify para activar el subproceso en espera.

4.5 análisis del proceso de entrada y supervisión

Para los modos de ejecución interpretada y ejecución de código de máquina, la función MonitorEnter del objeto Object en el archivo art/runtime/mirror/object-inl.h se llamará eventualmente.

3de767f89a3357d8dd538bbddcf84a6e.png

Analicemos la función MonitorEnter del método estático de la clase Monitor.

4d58ff49e23593a9b0ef32d7341a9a16.png

FakeLock se utiliza principalmente para comprobaciones de seguridad de subprocesos, principalmente en tiempo de compilación.

kExtraSpinIters define cuándo el bloqueo del objeto está en manos de otros subprocesos y es un bloqueo delgado, la cantidad de veces que los subprocesos de la competencia adquieren el bloqueo en un bucle.

279b5d109bf29453c2ac29710cfe5497.png

Obtenga el estado de bloqueo a través de lock_word.GetState(). Cuando el estado de bloqueo esté desbloqueado, conviértalo en un estado de bloqueo delgado y actualice el recuento de bloqueos a través de la operación cas.

e77e070819fe8205a432955b3e4d3952.png

Cuando el estado de bloqueo es un estado de bloqueo delgado, primero se adquiere la identificación del subproceso del propietario. Si la identificación del propietario es consistente con la identificación del subproceso de la competencia, hay dos situaciones de la siguiente manera:

  1. Si el conteo de bloqueos más 1 es menor o igual a (1<<12)-1(4095), actualice el conteo de bloqueos con el conteo de bloqueos+1.

  2. Si el recuento de bloqueos más 1 es mayor que (1<<12)-1 (el área de recuento de bloqueos no se puede almacenar), llame a la función InflateThinLocked para inflar el bloqueo delgado.

Las funciones relacionadas con Atrace* se utilizan principalmente para imprimir información relacionada con systrace, y trylock es falso aquí.

2818a4198396b297ffd5bacb53e83106.png

Cuando el estado de bloqueo es un estado de bloqueo delgado y la identificación del subproceso del propietario del bloqueo es inconsistente con la identificación del subproceso de la competencia, espere una cierta cantidad de tiempo.

El valor de runtime->GetMaxSpinsBeforeThinLockInflation() es 50, es decir, después de ejecutar 100 bucles para juzgar el estado del bloqueo y luego ejecutar 50 veces sched_yield(), el recurso de bloqueo no se ha obtenido. obtenido, la cerradura Para expandir. sched_yield() renunciará activamente al permiso de ejecución del hilo actual y reanudará la ejecución después de cierto tiempo.

b370d9e0b28b7baee40bb555b42adf56.png

Cuando el estado de bloqueo es un estado de bloqueo total, obtenga el objeto Monitor a través de lock_word.FatLockMonitor(); y deje que el subproceso ingrese al estado de espera a través de la función de bloqueo del objeto Monitor.

d61f1373f9fc637e6da9f8b8ef8a8e45.png

Cuando el estado de bloqueo ya está en estado hash, expanda directamente el bloqueo.

Veamos el proceso de expansión de bloqueo:

b093119e534b2fe432f49e8593a309d1.png

Hay dos casos de expansión de bloqueo delgado:

1) El valor del recuento de bloqueo supera los 4095. En este momento, el propietario del bloqueo es el subproceso actual, es decir, se expande directamente a través de la función Inflar

2). El propietario del bloqueo no es el subproceso actual. Suspender el subproceso propietario del bloqueo a través de SuspendThreadByThreadId (principalmente, tanto el subproceso propietario como el subproceso de expansión del bloqueo necesitan acceder a LockWord del objeto para evitar condiciones de carrera), y luego inflar a través de Inflar. Una vez completada la expansión, despierte el subproceso propietario de la cerradura.

Mira el proceso de Inflar:

93facda60aef310488f759d192a4e9ef.png

Obtenga un objeto Monitor m a través de la función MonitorPool::CreateMonitor, y actualice el campo LockWord del objeto a través de la función m->install(self) En este momento, la información del campo LockWord incluye el estado de fat lock, el estado del GC y el MonitorId y luego guarde m en monitor_list_ middle.

monitor_list_ almacena todos los objetos Monitor utilizados por la máquina virtual actual. En el proceso de GC, se accede a los objetos de los que depende Monitor a través de la lista vinculada. Si el objeto se convierte en un objeto basura, recicle el Monitor; de lo contrario, actualice la información del objeto del que depende el Monitor.

MonitorId se utiliza para identificar de forma única un monitor. Para obtener información sobre el método generado, consulte la implementación en monitor_pool.h.

Mire el proceso de Monitor::Lock nuevamente:

La implementación de esta función es relativamente larga y se omite el código relacionado con la depuración.

8a5bf1a8217f65c4f46fdfa9240a93b8.png

Primero, se presenta monitor_lock_, el miembro más importante de Monitor, es una instancia de Mutex, a través de la cual se realiza la lógica central relacionada con los bloqueos.

La función TryLock implementa principalmente una cierta espera de giro a través de la función de Mutex y establece el estado del bloqueo como el estado que tiene el subproceso.

monitor_lock_.ExclusiveLock(self);En la función ExclusiveLock de Mutex, el bloqueo de subprocesos se realiza a través de la llamada al sistema futex, y el código de llamada futex es el siguiente.

7ea5d09df0e69ac39c9a395d0192f047.png

4.6 análisis del proceso de salida del monitor

Tanto los modos de ejecución interpretados como los de ejecución de código de máquina llamarán a la función MonitorExit.

c52803ea13bf86226002a92faa47bc7a.png

Obtenga el estado de LockWord a través de lock_word.GetState(), y cuando el estado es hash o desbloqueado, se lanza una excepción a través de la función FailedUnlock.

2c70957059d037ae6c103d213fb27176.png

Cuando el estado de LockWord es bloqueo delgado, hay dos situaciones de la siguiente manera:

1) Si el propietario del bloqueo no es coherente con el hilo actual, se generará un error y se generará una excepción.

2). El propietario del bloqueo es el mismo subproceso que el subproceso actual. Cuando el bloqueo es reentrante, configure el recuento de bloqueo -1, de lo contrario, configúrelo en el estado desbloqueado.

d818235f26b579dc22396a0711f22a75.png

Cuando el estado de LockWord es fat lock, obtenga el objeto Monitor asociado con el objeto y llame a la función Desbloquear

311c4662ad9f501bb8011756996892eb.png

En la función Desbloquear, lock_count es 0, lo que indica que el subproceso ya no mantiene el bloqueo.

SignalWaiterAndReleaseMonitorLock activa el subproceso bloqueado en la cerradura.

V. Resumen

Este artículo explica brevemente el uso de bloqueos de objetos, la estructura de los objetos en la memoria y analiza el miembro clave LockWord en el encabezado del objeto.Finalmente, presenta las funciones de sincronizado, Object.wait() y Object.notify() en virtual maquinas proceso de implementacion.

referencias:

Comprensión profunda de la máquina virtual Android Java ART Deng Fanping

Comprensión profunda de la máquina virtual java Zhou Zhiming

Proveedores de facturas de máquinas virtuales Java en profundidad

Código fuente de Android T

acdee7079ac137a87a8e22c7232dbf54.gif

Mantenga presionado para seguir Kernel Craftsman WeChat

Tecnología Linux Kernel Black | Artículos técnicos | Tutoriales destacados

Supongo que te gusta

Origin blog.csdn.net/feelabclihu/article/details/129359071
Recomendado
Clasificación