Java, JDBC und ein Speicherleck

F

freakxnet

Schönen guten Tag,

irgendwie habe ich die Vermutung das im Java JDBC ein fieser Bug drinn hängt. Ich bin jetzt schon seit tagen am Rätseln warum ich so ein enormes Speicherleck habe.

Das leck tritt dann auf wenn ich auf der Datenbank relativ große Datenmengen auslese. Das Result wird dann aber nicht wieder frei gegeben. Aus diesem Grund habe ich mal eine kleine Testanwendung geschrieben was das Problem verdeutlicht.

Testsysteme:

1)

Windows XP Professional SP3
Wamp2
MYSQL 5.1.30

SUN Java 1.6

2)

Windows XP Professional SP3
PostgreSQL HTTPD Server
PostgreSQL 8.1

SUN Java 1.6

3)

Linux FEDORA Core 11
Apache2
MYSQL

OpenJDK 1.6

Nun ist es so das ich in meiner Datenbank eine Testtabelle (Defintion unten) angelegt habe welche ich mit Daten gefüllt habe (1 Mio. Datensätze). Dort führe ich dann das SELECT darauf aus. Leider ist es dann so das ja laut Java-Doku bei einem "CLOSE" alle Daten freigegeben werden. Jetzt "CLOSE" ich alles und aber leider wird der Speicher dann nicht mehr freigegeben. Kann es sein das ich etwas vergessen habe oder irgendwie etwas komplett verreiße? Nach meiner Aufassung müsste er doch wenn ich alles "CLOSE" die Speicher wieder freigeben. Dies ist aber eben nicht der Fall.

Falls da jemand eine Lösung oder eine Idee dazu hat wäre ich über einen Ratschlag sehr verbunden.

MFG freakxnet

====DAS JAVA PROG======

Code:
package src;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

/**
 *
 * @author freakxnet
 * 
 * TABLE DATA - MYISAM TABLE
 * 
 * -- phpMyAdmin SQL Dump
-- version 3.1.1
-- http://www.phpmyadmin.net
--
-- Host: localhost
-- Erstellungszeit: 30. September 2009 um 21:20
-- Server Version: 5.1.30
-- PHP-Version: 5.2.9-2

SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";

--
-- Datenbank: `test`
--

-- --------------------------------------------------------

--
-- Tabellenstruktur für Tabelle `test`
--

DROP TABLE IF EXISTS `test`;
CREATE TABLE IF NOT EXISTS `test` (
  `id` int(11) NOT NULL DEFAULT '0',
  `test1` varchar(255) NOT NULL,
  `test2` varchar(255) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `test1` (`test1`,`test2`)
) ENGINE=MEMORY DEFAULT CHARSET=latin1;

--
-- Daten für Tabelle `test`
--


 *
 */
public class DBTest {
	private Connection connection;
	
	public DBTest(){
		this.connectToDatabase();
		
		//this.fillTable();
		this.selectTable();
	}
	
	/**
	 * Connect to database
	 */
	private void connectToDatabase(){
		try {
			Class.forName("com.mysql.jdbc.Driver").newInstance();
			
			this.connection=DriverManager.getConnection("jdbc:mysql://localhost/sitedata","root", "root");
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Fill the table with random data
	 */
	private void fillTable(){
		//fill in one million entries
		int numEntrys=1000000;
		
		Statement statement=null;
		
		try {
			//create the statement
			statement=this.connection.createStatement();
			
			//start transaction
			statement.execute("begin");
			for(int cnt=0; cnt<numEntrys; cnt++){
				statement.execute("INSERT INTO test (test1, test2) VALUES('testValue1["+cnt+"]', 'testValue2["+cnt+"]');");
			}
			//commit data to table
			statement.execute("commit");
			
			//clears out all warnings
			statement.clearWarnings();
			//close the statement
			statement.close();
		} catch (SQLException e) {
			e.printStackTrace();
		}
		finally {
			try {
				statement.clearWarnings();
				statement.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
		
		//close the current connection
		try {
			this.connection.clearWarnings();
			this.connection.close();
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Selects a number of rows from database
	 */
	private void selectTable(){
		System.out.println("Start: "+(Runtime.getRuntime().freeMemory()));
		
		Statement statement;
		try {
			statement = this.connection.createStatement();
			
			ResultSet result=statement.executeQuery("SELECT * FROM test LIMIT 500000");
			
			System.out.println("After query: "+(Runtime.getRuntime().freeMemory()));
			
			result.clearWarnings();
			result.close();
			
			statement.clearWarnings();
			statement.close();
			
			this.connection.clearWarnings();
			this.connection.close();
		} catch (SQLException e) {
			e.printStackTrace();
		}
		
		System.out.println("End: "+(Runtime.getRuntime().freeMemory()));
		
		boolean running=true;
		while(running){
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
	public static void main(String args[]){
		new DBTest();
	}
}
 
Ok, gut also beim Debuggen ruft er alles ordnungsgemäß auf. Habe jetzt denke ich auch die Lösung gefunden. Ich habe noch ein System.gc() mit aufgerufen und es wurde der Speicher wieder frei gegeben. Was er ohne diesen Aufruf nicht gemacht hat. beziehungsweise würde er es dann eben später machen.

Was an der Stelle noch interessant ist wirkt sich das "Close" auf "PreparedStatements genauso aus wie auch auf "normale Statements"?
 
Ein System.gc() löst keine Speicherlecks. Und man sollte den generell nicht manuell nicht aufrufen, sondern nur in kaum vorhandenen Sonderfällen.

Also hast du überhaupt ein Speicherleck oder ist dein Speicher falsch eingestellt bzw. du verstehst nicht wie der GC/Speichermanagement funktioniert?
 
Nein selbstverständlich ist mir die Funktionsweise des Java-Speichermanagements sehr wohl bewusst. Aber wenn du dir die Mühe machen würdest mein Testprogramm einmal auszuprobieren und es auch vielleicht ein Stück zu erweitern oder eben den Speicher ein wenig herunterzusetzen würdest du sehen das der GC sobald der Speicher an seine Grenzen stößt eben nicht aufgerufen wird und der Speicher freigeräumt wird. Interessanter weise macht der bei mir nur wenn ich den dann von Hand aufrufe. In allen anderen fällen läuft der Speicher einfach voll und wird nie wieder frei geräumt was sich zwangsweise einen "Java-Heap-Space" Fehler manifestiert. Was auch wiederrum der Grund dafür ist das ich den an der Stelle einfach mal von Hand aufgerufen habe ;-).

Natürlich ist mir auch die Verwendung des "finally" bewusst. In diesem Fall habe ich es aber (Schande über mein Haupt) weil ich durch das debuggen von vornherein wusste das in diesem simplen Beispiel keine Exception geworfen wird. Zumindestens nicht bei mir. Das kann ja woanders wieder etwas anders aussehen. Aber ich denke das sollte jeder halbwegs erfahrene Programmierer noch selbst hinbekommen.

PS. : Ich bin nicht unbedingt ein Anfänger, meine Frage wäre ja letztendlich nur warum der den Speicher nicht wieder frei macht obwohl komplett alles geschlossen ist. Warum der GC es an der Stelle nicht schafft. Was ja nur bedeuten kann das es noch Referenzen auf den Objekten gibt und genau da liegt ja das Problem. Denn laut JAVA doku sollten alle Objekte spätestens mit dem "Connection.close" freigegeben werden. Und mir ist an der Stelle natürlich auch bewusst das wenn man sich das mit einem externen Tool anschaut der Speicherverbrauch für die Anwendung ersteinmal nur steigt solange bis der GC mal vorbeikommt und wieder freiräumt. Dann ist da auch eine Änderung im .z.b. Systemmonitor zu sehen.

Also verreisse ich hier nur irgendwas oder gibt der die Referenzen nur aus Boshaftigkeit nicht frei ;-) ?

UPDATE:

Mittlerweile hat sich das Problem in Wohlgefallen aufgelösst. Ich musste einsehen das ich es einfach total verissen habe und hätte mir einfach mal von Anfang an den VM-Speicher anschauen sollen. Naja auf jeden Fall geht es nun alles wie es soll.

Danke trotzdem an den Helfer.

MFG freakxnet
 
Zuletzt bearbeitet von einem Moderator:
Ich finde zwei Sachen großartig:

1.) JDBC blamen, ein Speicherleck zu haben (Gut, dass das so wenige Leute nutzen, wenn das so buggy ist...)
2.) Nachdem andere sinnvolle Hinweise geben, die unter anderem auf das Anfängerniveau des Codes eingehen (sowas dürfte ein Junior bei mir nicht mal committen), diese dann noch dumm anmachen ("führ doch mal den Code aus", "klar weiß ich was finally ist").

Darauf zwei Antworten:

1.) Du willst Hilfe. Wenn man nun darauf angewiesen ist, dass andere ihre Zeit Opfern ist man nicht unbedingt in der Position, groß Forderungen zu stellen. Wenn man es icht schafft, Code auf die Stellen runterzubrechen, die das Problem verursachen brauchst dich nicht wundern, wenn man erstmal grundsätzlich wird.
2.) Wenn du weißt, was finally tut, warum benutzt du es dann nicht richtig? In beiden Fällen wird es inkonsistent und dazu noch falsch benutzt. Dass man eine Connection nicht als Instanzvariable hält, weil dein Objekt dadurch inherent nicht threadsafe wird ist noch ein anderes Thema.

Conclusio:
Ich würde mich zurück halten, andere zu belehren und mit dem eigenen Wissen zu prahlen, wenn man hier a) um Hilfe fragt und b) hochgradig unterirdischen Code zeigt. Regel Nummer 1 beim arbeiten mit Javabibliotheken: "Never blame the library!".

In diesem Sinne
REINHAUN!
 

Neue Beiträge

Zurück