java - sobre - tesis de residuos solidos municipales
¿Por qué un objeto declarado en el método está sujeto a la recolección de basura antes de que regrese el método? (7)
¿Obj estará sujeto a la recolección de basura antes de que vuelva foo ()?
No puede estar seguro de que obj
será recolectado antes de que devuelva foo
pero ciertamente es elegible para la recolección antes de que devuelva foo
.
¿Alguien puede explicar por qué sucede esto?
Los GC recogen objetos inalcanzables. Es probable que tu objeto sea inalcanzable antes de que devuelva foo
.
El alcance es irrelevante. La idea de que obj
permanece en la pila hasta que devuelve foo
es un modelo mental excesivamente simplista. Los sistemas reales no funcionan así.
Considere un objeto declarado en un método:
public void foo() {
final Object obj = new Object();
// A long run job that consumes tons of memory and
// triggers garbage collection
}
¿Obj estará sujeto a la recolección de basura antes de que vuelva foo ()?
ACTUALIZACIÓN : Anteriormente, pensé que obj no está sujeto a la recolección de basura hasta que devuelva foo ().
Sin embargo, hoy me encuentro equivocado.
Pasé varias horas solucionando un error y finalmente descubrí que el problema es causado por la basura recolectada.
¿Alguien puede explicar por qué sucede esto? Y si quiero que obj se fije en cómo lograrlo?
Aquí está el código que tiene problema.
public class Program
{
public static void main(String[] args) throws Exception {
String connectionString = "jdbc:mysql://<whatever>";
// I find wrap is gc-ed somewhere
SqlConnection wrap = new SqlConnection(connectionString);
Connection con = wrap.currentConnection();
Statement stmt = con.createStatement(ResultSet.TYPE_FORWARD_ONLY,
ResultSet.CONCUR_READ_ONLY);
stmt.setFetchSize(Integer.MIN_VALUE);
ResultSet rs = stmt.executeQuery("select instance_id, doc_id from
crawler_archive.documents");
while (rs.next()) {
int instanceID = rs.getInt(1);
int docID = rs.getInt(2);
if (docID % 1000 == 0) {
System.out.println(docID);
}
}
rs.close();
//wrap.close();
}
}
Después de ejecutar el programa Java, imprimirá el siguiente mensaje antes de que se bloquee:
161000
161000
********************************
Finalizer CALLED!!
********************************
********************************
Close CALLED!!
********************************
162000
Exception in thread "main" com.mysql.jdbc.exceptions.jdbc4.CommunicationsException:
Y aquí está el código de la clase SqlConnection:
class SqlConnection
{
private final String connectionString;
private Connection connection;
public SqlConnection(String connectionString) {
this.connectionString = connectionString;
}
public synchronized Connection currentConnection() throws SQLException {
if (this.connection == null || this.connection.isClosed()) {
this.closeConnection();
this.connection = DriverManager.getConnection(connectionString);
}
return this.connection;
}
protected void finalize() throws Throwable {
try {
System.out.println("********************************");
System.out.println("Finalizer CALLED!!");
System.out.println("********************************");
this.close();
} finally {
super.finalize();
}
}
public void close() {
System.out.println("********************************");
System.out.println("Close CALLED!!");
System.out.println("********************************");
this.closeConnection();
}
protected void closeConnection() {
if (this.connection != null) {
try {
connection.close();
} catch (Throwable e) {
} finally {
this.connection = null;
}
}
}
}
A medida que se escribe su código, el objeto al que apunta "wrap" no debería ser elegible para la recolección de elementos no utilizados hasta que "wrap" salga de la pila al final del método.
El hecho de que se esté recopilando me sugiere que el código compilado no coincide con la fuente original y que el compilador ha realizado algunas optimizaciones, como cambiar esto:
SqlConnection wrap = new SqlConnection(connectionString);
Connection con = wrap.currentConnection();
a esto:
Connection con = new SqlConnection(connectionString).currentConnection();
(O incluso alinear todo) porque "wrap" no se usa más allá de este punto. El objeto anónimo creado sería elegible para GC inmediatamente.
La única forma de estar seguro es descompilar el código y ver qué se le ha hecho.
Aquí, obj
es una variable local en el método y se saca de la pila tan pronto como el método retorna o sale. Esto no deja forma de alcanzar el objeto Object
en el montón y por lo tanto será basura recolectada. Y el objeto Object
en el montón será GC''d solo después de que su obj
referencia salga de la pila, es decir, solo después de que el método finalice o regrese.
EDITAR: Para responder a su actualización,
UPDATE: Let me make the question more clear.
Will obj be subject to garbage collection before foo() returns?
obj
es solo una referencia al objeto real en el montón . Aquí obj
se declara dentro del método foo (). ¿Entonces su pregunta Will obj be subject to garbage collection before foo() returns?
no se aplica porque obj
va dentro del marco de la pila cuando el método foo()
está ejecutando y desaparece cuando el método finaliza.
Como estoy seguro de que sabes, en Java Garbage Collection y Finialization no son deterministas. Todo lo que puede determinar en este caso es cuándo la wrap
es elegible para la recolección de basura. Supongo que está preguntando si el wrap
solo es elegible para GC cuando el método retorna (y el wrap
queda fuera del alcance). Creo que algunas JVM (por ejemplo, HotSpot with -server
) no esperarán a que la referencia de objeto se extraiga de la pila, lo harán elegible para GC tan pronto como nada más lo haga referencia. Parece que esto es lo que estás viendo.
Para resumir, confía en que la finalización sea lo suficientemente lenta como para no finalizar la instancia de SqlConnection
antes de que el método finalice. El finalizador está cerrando un recurso del que SqlConnection
ya no es responsable. En su lugar, debe dejar que el objeto Connection
sea responsable de su propia finalización.
De acuerdo con la especificación actual, no hay ni siquiera un suceso, antes de ordenar desde la finalización hasta el uso normal. Por lo tanto, para imponer el orden, realmente necesita usar un candado, un elemento volátil o, si está desesperado, esconder una referencia accesible desde un dispositivo estático. Ciertamente, no hay nada especial sobre el alcance.
Debería ser raro que realmente necesite escribir un finalizador.
En realidad, hay dos cosas diferentes que suceden aquí. obj
es una variable de pila que se establece en una referencia al Object
, y el Object
se asigna en el montón. La pila simplemente se borrará (mediante aritmética del puntero de la pila).
Pero sí, el Object
sí será borrado por la recolección de basura. Todos los objetos asignados en el montón están sujetos a GC.
EDITAR: para responder a su pregunta más específica, la especificación de Java no garantiza la recopilación en un momento determinado (consulte la especificación de JVM ) (por supuesto, se recopilará después de su último uso). Por lo tanto, solo es posible responder para implementaciones específicas.
EDITAR: Aclaración, por comentarios
Estoy realmente asombrado por esto, pero tienes razón. Es fácilmente reproducible, no necesita perder el tiempo con las conexiones de bases de datos y cosas por el estilo:
public class GcTest {
public static void main(String[] args) {
System.out.println("Starting");
Object dummy = new GcTest(); // gets GC''d before method exits
// gets bigger and bigger until heap explodes
Collection<String> collection = new ArrayList<String>();
// method never exits normally because of while loop
while (true) {
collection.add(new String("test"));
}
}
@Override
protected void finalize() throws Throwable {
System.out.println("Finalizing instance of GcTest");
}
}
Se ejecuta con:
Starting
Finalizing instance of GcTest
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:2760)
at java.util.Arrays.copyOf(Arrays.java:2734)
at java.util.ArrayList.ensureCapacity(ArrayList.java:167)
at java.util.ArrayList.add(ArrayList.java:351)
at test.GcTest.main(GcTest.java:22)
Como dije, apenas puedo creerlo, pero no se puede negar la evidencia.
Sin embargo, tiene un sentido perverso, la máquina virtual se habrá dado cuenta de que el objeto nunca se usa y, por lo tanto, se deshace de él. Esto debe ser permitido por la especificación.
Volviendo al código de la pregunta, nunca debe confiar en finalize()
para limpiar sus conexiones, siempre debe hacerlo explícitamente.