Parece que las personas se cansaron del manejo manual de la memoria, por lo que inventaron la recolección de basura y la vida fue razonablemente buena. Pero, ¿qué pasa con todos los otros tipos de recursos? ¿Descriptores de archivo, sockets o incluso datos creados por el usuario como conexiones de bases de datos?
Esto se siente como una pregunta ingenua, pero no puedo encontrar ningún lugar donde alguien lo haya hecho. Consideremos los descriptores de archivo. Digamos que un programa sabe que solo se le permitirá tener 4000 fds disponibles cuando se inicie. Cada vez que realiza una operación que abrirá un descriptor de archivo, ¿qué pasaría si lo hiciera?
- Verifique para asegurarse de que no esté a punto de agotarse.
- Si es así, active el recolector de basura, que liberará un montón de memoria.
- Si parte de la memoria liberada contiene referencias a descriptores de archivos, ciérrelos de inmediato. Sabe que la memoria pertenecía a un recurso porque la memoria vinculada a ese recurso se registró en un "registro de descriptor de archivo", a falta de un término mejor, cuando se abrió por primera vez.
- Abra un nuevo descriptor de archivo, cópielo en una nueva memoria, registre esa ubicación de memoria en el 'registro de descriptor de archivo' y devuélvala al usuario.
Por lo tanto, el recurso no se liberaría de inmediato, pero se liberaría cada vez que se ejecutara el gc, lo que incluye, como mínimo, justo antes de que el recurso estuviera a punto de agotarse, suponiendo que no se esté utilizando por completo.
Y parece que eso sería suficiente para muchos problemas de limpieza de recursos definidos por el usuario. Me las arreglé para encontrar un solo comentario aquí que hace referencias a hacer una limpieza similar a esto en C ++ con un hilo que contiene una referencia a un recurso y lo limpia cuando solo tiene una sola referencia restante (del hilo de limpieza), pero puedo ' No encuentre ninguna evidencia de que esto sea una biblioteca o parte de un idioma existente.
fuente
Existen muchas técnicas de programación para ayudar a administrar este tipo de recursos.
Los programadores de C ++ a menudo usan un patrón llamado Adquisición de recursos es Inicialización , o RAII para abreviar. Este patrón asegura que cuando un objeto que retiene recursos queda fuera de alcance, cerrará los recursos a los que se aferraba. Esto es útil cuando la vida útil del objeto corresponde a un alcance particular en el programa (por ejemplo, cuando coincide con el momento en que un marco de pila particular está presente en la pila), por lo que es útil para los objetos que apuntan las variables locales (puntero variables almacenadas en la pila), pero no tan útiles para los objetos que apuntan los punteros almacenados en el montón.
Java, C # y muchos otros lenguajes proporcionan una forma de especificar un método que se invocará cuando un objeto ya no esté vivo y esté a punto de ser recogido por el recolector de basura. Ver, por ejemplo, finalizadores
dispose()
, y otros. La idea es que el programador pueda implementar dicho método para cerrar explícitamente el recurso antes de que el objeto sea liberado por el recolector de basura. Sin embargo, estos enfoques tienen algunos problemas que puede leer en otro lugar; por ejemplo, el recolector de basura podría no recolectar el objeto hasta mucho más tarde de lo que desea.C # y otros lenguajes proporcionan una
using
palabra clave que ayuda a garantizar que los recursos se cierren después de que ya no sean necesarios (para que no se olvide de cerrar el descriptor de archivo u otro recurso). Esto suele ser mejor que confiar en el recolector de basura para descubrir que el objeto ya no está vivo. Ver, por ejemplo, /programming//q/75401/781723 . El término general aquí es un recurso gestionado . Esta noción se basa en RAII y finalizadores, y los mejora de alguna manera.fuente
Toda la memoria es igual, si pido 1K, no me importa de dónde viene el 1K en el espacio de direcciones.
Cuando solicito un identificador de archivo, quiero un identificador para el archivo que deseo abrir. Tener un identificador de archivo abierto en un archivo, a menudo bloquea el acceso al archivo por otros procesos o máquinas.
Por lo tanto, los identificadores de archivos deben cerrarse tan pronto como no sean necesarios, de lo contrario, bloquean otros accesos al archivo, pero la memoria solo necesita recuperarse cuando comienza a quedarse sin él.
Ejecutar un pase de GC es costoso y solo se hace "cuando sea necesario", no es posible predecir cuándo otro proceso necesitará un identificador de archivo que su proceso ya no esté utilizando, pero aún está abierto.
fuente
Supongo que la razón por la cual no se ha abordado mucho esto para otros recursos es exactamente porque se prefiere que la mayoría de los otros recursos se publiquen lo antes posible para que cualquiera pueda reutilizarlos.
Tenga en cuenta, por supuesto, que su ejemplo podría proporcionarse ahora utilizando descriptores de archivo "débiles" con las técnicas de GC existentes.
fuente
Verificar si la memoria ya no es accesible (y, por lo tanto, se garantiza que ya no se usará) es bastante fácil. La mayoría de los otros tipos de recursos pueden manejarse con más o menos las mismas técnicas (es decir, la adquisición de recursos es inicialización, RAII y su contrapartida de liberación cuando el usuario es destruido, lo que lo vincula con la administración de memoria). En general, es imposible hacer algún tipo de liberación "justo a tiempo" (verifique el problema de detención, tendría que descubrir que se utilizó algún recurso por última vez). Sí, a veces se puede hacer automáticamente, pero es un caso mucho más desordenado como la memoria. Por lo tanto, se basa en la intervención del usuario en su mayor parte.
fuente