Aby zapobiec wyciekowi pamięci, sterownik JDBC został wymuszony niezarejestrowany


325

Otrzymuję ten komunikat po uruchomieniu aplikacji internetowej. Działa dobrze, ale pojawia się ten komunikat podczas zamykania.

SEVERE: Aplikacja internetowa zarejestrowała sterownik JBDC [oracle.jdbc.driver.OracleDriver], ale nie wyrejestrowała go, gdy aplikacja internetowa została zatrzymana. Aby zapobiec wyciekowi pamięci, sterownik JDBC został wymuszony niezarejestrowany.

Każda pomoc doceniona.


Odpowiedzi:


301

Od wersji 6.0.24 Tomcat jest wyposażony w funkcję wykrywania wycieków pamięci , która z kolei może prowadzić do tego rodzaju komunikatów ostrzegawczych, gdy w aplikacji internetowej znajduje się sterownik zgodny z JDBC 4.0, /WEB-INF/libktóry automatycznie rejestruje się podczas uruchamiania aplikacji internetowej za pomocą ServiceLoaderinterfejsu API , ale który nie wyrejestrował się sam podczas zamykania aplikacji internetowej. Ten komunikat jest czysto nieformalny, Tomcat już odpowiednio podjął działanie zapobiegające wyciekowi pamięci.

Co możesz zrobić?

  1. Zignoruj ​​te ostrzeżenia. Tomcat dobrze wykonuje swoją pracę. Rzeczywisty błąd znajduje się w czyimś kodzie (sterownik JDBC, o którym mowa), a nie w twoim. Ciesz się, że Tomcat wykonał swoje zadanie poprawnie i poczekaj, aż dostawca sterowników JDBC go naprawi, abyś mógł zaktualizować sterownik. Z drugiej strony nie należy upuszczać sterownika JDBC w aplikacji internetowej /WEB-INF/lib, ale tylko w serwerze /lib. Jeśli nadal przechowujesz go w aplikacji internetowej /WEB-INF/lib, powinieneś ręcznie zarejestrować go i wyrejestrować za pomocą ServletContextListener.

  2. Zmień wersję na Tomcat 6.0.23 lub starszą, aby nie przejmować się tymi ostrzeżeniami. Ale po cichu wycieknie pamięć. Nie jestem pewien, czy to dobrze wiedzieć. Tego rodzaju wycieki pamięci są jedną z głównych przyczyn OutOfMemoryErrorproblemów podczas wdrażania Tomcat.

  3. Przenieś sterownik JDBC do /libfolderu Tomcat i uzyskaj źródło danych w puli połączeń do zarządzania sterownikiem. Zauważ, że wbudowany DBCP Tomcat nie wyrejestrowuje sterowników poprawnie przy zamknięciu. Zobacz także błąd DBCP-322, który jest zamknięty jako WONTFIX. Wolisz zastąpić DBCP inną pulą połączeń, która działa lepiej niż DBCP. Na przykład HikariCP , BoneCP lub może Tomcat JDBC Pool .


25
To dobra rada. To nie ostrzeżenie o wycieku pamięci, to ostrzeżenie, że Tomcat podjął jakieś wymuszone działania, aby zapobiec wyciekowi
mat b

49
Jeśli jednak należy wybrać opcję (1), dlaczego Tomcat rejestruje je jako SZYBKIE? SEVERE to dla mnie „strona administratora”, a nie „ignorowanie”.
Peter Becker

7
Dlaczego nie zrobić tego sam - zamiast oczekiwać, że Tomcat to zrobi. Moim zdaniem nie jest zadaniem Tomcata czyszczenie naszego bałaganu. Zobacz moją odpowiedź poniżej.
sparkyspider

2
@sproketboy: Huh? Czy przypisałeś artefakty JDBC jako pola klasy, która z kolei jest przechowywana w sesji HTTP?
BalusC

2
Domyślam się, że to zazwyczaj powód 3 (posiadanie biblioteki w WAR w przeciwieństwie do lib).
lapo,

160

W metodzie kontekstu detektora kontekstu serwletu ręcznie wyrejestruj sterowniki:

// This manually deregisters JDBC driver, which prevents Tomcat 7 from complaining about memory leaks wrto this class
Enumeration<Driver> drivers = DriverManager.getDrivers();
while (drivers.hasMoreElements()) {
    Driver driver = drivers.nextElement();
    try {
        DriverManager.deregisterDriver(driver);
        LOG.log(Level.INFO, String.format("deregistering jdbc driver: %s", driver));
    } catch (SQLException e) {
        LOG.log(Level.SEVERE, String.format("Error deregistering driver %s", driver), e);
    }
}

3
To działa! javabeat.net/servletcontextlistener-example może pomóc we wdrożeniu detektora kontekstu serwletu
Vadim Zin4uk

17
Jest to potencjalnie niebezpieczne w środowisku współdzielonym, ponieważ możesz nie chcieć wyrejestrować wszystkich dostępnych sterowników JDBC. Zobacz moją odpowiedź na bezpieczniejsze podejście.
daiscog

85

Chociaż Tomcat siłą wyrejestrowuje dla ciebie sterownik JDBC, dobrą praktyką jest czyszczenie wszystkich zasobów utworzonych przez twoją aplikację internetową po zniszczeniu kontekstu, na wypadek, gdybyś przeniósł się do innego kontenera serwletu, który nie wykonuje kontroli zapobiegania wyciekowi pamięci, którą wykonuje Tomcat.

Metodologia wyrejestrowania ogólnego sterownika jest jednak niebezpieczna. Niektóre sterowniki zwracane przez tę DriverManager.getDrivers()metodę mogły zostać załadowane przez nadrzędny moduł ClassLoader (tj. Moduł ładujący kontener serwletu), a nie moduł ClassLoader kontekstu aplikacji sieci Web (np. Mogą znajdować się w folderze lib kontenera, a nie w aplikacji WWW, a zatem udostępnione w całym kontenerze ). Wyrejestrowanie ich wpłynie na wszystkie inne aplikacje internetowe, które mogą ich używać (lub nawet sam kontener).

Dlatego przed wyrejestrowaniem należy sprawdzić, czy ClassLoader dla każdego sterownika jest ClassLoader aplikacji internetowej. Zatem w metodzie contextDestroyed () obiektu ContextListener:

public final void contextDestroyed(ServletContextEvent sce) {
    // ... First close any background tasks which may be using the DB ...
    // ... Then close any DB connection pools ...

    // Now deregister JDBC drivers in this context's ClassLoader:
    // Get the webapp's ClassLoader
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    // Loop through all drivers
    Enumeration<Driver> drivers = DriverManager.getDrivers();
    while (drivers.hasMoreElements()) {
        Driver driver = drivers.nextElement();
        if (driver.getClass().getClassLoader() == cl) {
            // This driver was registered by the webapp's ClassLoader, so deregister it:
            try {
                log.info("Deregistering JDBC driver {}", driver);
                DriverManager.deregisterDriver(driver);
            } catch (SQLException ex) {
                log.error("Error deregistering JDBC driver {}", driver, ex);
            }
        } else {
            // driver was not registered by the webapp's ClassLoader and may be in use elsewhere
            log.trace("Not deregistering JDBC driver {} as it does not belong to this webapp's ClassLoader", driver);
        }
    }
}

Nie powinno być if (cl.equals(driver.getClass().getClassLoader())) {?
user11153

6
@ user11153 Nie, sprawdzamy, czy jest to dokładnie ta sama instancja ClassLoader, a nie to, czy są to dwie odrębne instancje o równej wartości.
daiscog

4
Chociaż inni wydają się poprawnie obsługiwać dany problem, powodują problemy, gdy plik wojenny zostanie usunięty, a następnie zastąpiony. W takim przypadku sterowniki są wyrejestrowane i nigdy nie wracają - tylko ponowne uruchomienie Tomcat może wydostać się z tej dziury. To rozwiązanie pozwala uniknąć tego piekła.
OldCurmudgeon

Tutaj w większości takie same, ale z dodatkowym kodem obsługi MySQL / MariaDB github.com/spring-projects/spring-boot/issues/2612
gavenkoa

2
Jeśli użyjesz H2 lub PostgreSQL, spowoduje to, że sterownik nie zostanie ponownie zarejestrowany po przeładowaniu. Oba sterowniki utrzymują wewnętrzny stan rejestracji, który nie jest kasowany, jeśli sterownik został właśnie wyrejestrowany z DriverManager. Bardziej szczegółowy komentarz zostawiłem
Marcel Stör

26

Widzę, że często pojawia się ten problem. Tak, Tomcat 7 automatycznie go wyrejestrowuje, ale czy NAPRAWDĘ przejmuje kontrolę nad twoim kodem i dobrą praktyką kodowania? Z pewnością chcesz wiedzieć, że dysponujesz odpowiednim kodem, aby zamknąć wszystkie obiekty, zamknąć wątki puli połączeń z bazą danych i pozbyć się wszystkich ostrzeżeń. Z pewnością tak.

Tak to robię.

Krok 1: Zarejestruj słuchacza

web.xml

<listener>
    <listener-class>com.mysite.MySpecialListener</listener-class>
</listener>

Krok 2: Zaimplementuj nasłuchiwanie

com.mysite.MySpecialListener.java

public class MySpecialListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        // On Application Startup, please…

        // Usually I'll make a singleton in here, set up my pool, etc.
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        // On Application Shutdown, please…

        // 1. Go fetch that DataSource
        Context initContext = new InitialContext();
        Context envContext  = (Context)initContext.lookup("java:/comp/env");
        DataSource datasource = (DataSource)envContext.lookup("jdbc/database");

        // 2. Deregister Driver
        try {
            java.sql.Driver mySqlDriver = DriverManager.getDriver("jdbc:mysql://localhost:3306/");
            DriverManager.deregisterDriver(mySqlDriver);
        } catch (SQLException ex) {
            logger.info("Could not deregister driver:".concat(ex.getMessage()));
        } 

        // 3. For added safety, remove the reference to dataSource for GC to enjoy.
        dataSource = null;
    }

}

Skomentuj i / lub dodaj ...


4
Ale DataSourceNIE ma closemetody
Jim

9
należy implementować javax.servlet.ServletContextListener, a nie rozszerza ApplicationContextListener?
egaga

2
Czy istnieje przyczyna kolejności tych operacji w metodzie contextDestroyed? Dlaczego robisz krok 1. Przed robi krok 2., gdzie initContext, envContexti datasourcenie są wymienione w ogóle? Pytam, ponieważ nie rozumiem kroku 3.
Matthaeus

4
@ matthaeus Myślę, że krok 1. nie jest konieczny, lookupwydaje się , że po prostu dostaje kilka przedmiotów, których nie potrzebujesz. Krok 3. jest całkowicie bezużyteczny. Z pewnością nie zapewnia żadnego bezpieczeństwa i wygląda na coś, co zrobiliby początkujący, którzy nie rozumieją, jak działa GC. Po prostu pójdę ze stackoverflow.com/a/5315467/897024 i usunę wszystkie sterowniki.
kapex

4
@kapep Usunięcie wszystkich sterowników jest niebezpieczne, ponieważ niektóre mogą być współużytkowane przez kontener. Zobacz moją odpowiedź na podejście, które usuwa tylko sterowniki ładowane przez ClassLoader twojej aplikacji internetowej.
daiscog

14

Jest to kwestia wyłącznie rejestracji / wyrejestrowania sterownika w sterowniku mysql lub w programie ładującym webapp-tomcats. Skopiuj sterownik mysql do folderu lib tomcats (więc jest ładowany bezpośrednio przez jvm, a nie przez tomcat), a wiadomość zniknie. To sprawia, że ​​sterownik mysql jdbc jest zwalniany tylko podczas zamykania JVM, a wtedy nikt nie dba o wycieki pamięci.


2
to nie działa ... Próbowałem skopiować sterownik jdbc, powiedziałeś: TOMCAT_HOME / lib / postgresql-9.0-801.jdbc4.jar - Tomcat 7.0 \ lib \ postgresql-9.0-801.jdbc4.jar bez wyników ...
FAjir,

5
@Florito - musisz go również usunąć z aplikacji internetowych WEB-INF / lib
Collin Peters

8

Jeśli otrzymujesz ten komunikat od wojny zbudowanej przez Maven, zmień zakres sterownika JDBC na dostarczony i umieść jego kopię w katalogu lib. Lubię to:

<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <version>5.1.18</version>
  <!-- put a copy in /usr/share/tomcat7/lib -->
  <scope>provided</scope>
</dependency>

8

Rozwiązanie dla wdrożeń na aplikację

To jest słuchacz, który napisałem w celu rozwiązania problemu: automatycznie wykrywa, czy sterownik się zarejestrował i działa odpowiednio. To

Ważne: należy go używać TYLKO, gdy słoik sterownika jest wdrożony w WEB-INF / lib , a nie w Tomcat / lib, jak wielu sugeruje, aby każda aplikacja mogła zadbać o własny sterownik i działać na nietkniętym Tomcat . Tak powinno być IMHO.

Wystarczy skonfigurować odbiornik w pliku web.xml przed innymi i cieszyć się.

dodaj u góry strony web.xml :

<listener>
    <listener-class>utils.db.OjdbcDriverRegistrationListener</listener-class>    
</listener>

zapisz jako utils / db / OjdbcDriverRegistrationListener.java :

package utils.db;

import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Enumeration;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

import oracle.jdbc.OracleDriver;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Registers and unregisters the Oracle JDBC driver.
 * 
 * Use only when the ojdbc jar is deployed inside the webapp (not as an
 * appserver lib)
 */
public class OjdbcDriverRegistrationListener implements ServletContextListener {

    private static final Logger LOG = LoggerFactory
            .getLogger(OjdbcDriverRegistrationListener.class);

    private Driver driver = null;

    /**
     * Registers the Oracle JDBC driver
     */
    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        this.driver = new OracleDriver(); // load and instantiate the class
        boolean skipRegistration = false;
        Enumeration<Driver> drivers = DriverManager.getDrivers();
        while (drivers.hasMoreElements()) {
            Driver driver = drivers.nextElement();
            if (driver instanceof OracleDriver) {
                OracleDriver alreadyRegistered = (OracleDriver) driver;
                if (alreadyRegistered.getClass() == this.driver.getClass()) {
                    // same class in the VM already registered itself
                    skipRegistration = true;
                    this.driver = alreadyRegistered;
                    break;
                }
            }
        }

        try {
            if (!skipRegistration) {
                DriverManager.registerDriver(driver);
            } else {
                LOG.debug("driver was registered automatically");
            }
            LOG.info(String.format("registered jdbc driver: %s v%d.%d", driver,
                    driver.getMajorVersion(), driver.getMinorVersion()));
        } catch (SQLException e) {
            LOG.error(
                    "Error registering oracle driver: " + 
                            "database connectivity might be unavailable!",
                    e);
            throw new RuntimeException(e);
        }
    }

    /**
     * Deregisters JDBC driver
     * 
     * Prevents Tomcat 7 from complaining about memory leaks.
     */
    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {
        if (this.driver != null) {
            try {
                DriverManager.deregisterDriver(driver);
                LOG.info(String.format("deregistering jdbc driver: %s", driver));
            } catch (SQLException e) {
                LOG.warn(
                        String.format("Error deregistering driver %s", driver),
                        e);
            }
            this.driver = null;
        } else {
            LOG.warn("No driver to deregister");
        }

    }

}

Wskazówka: Od wersji Servlet 3.0 możesz dodawać adnotacje do swojej klasy @WebListeneri pomijać web.xmlkonfigurację.
Basil Bourque,

To prawda, po prostu upewnij się, że jest odbierany z priorytetem wystarczająco wysokim, aby nikt nie musiał używać sterownika przed ani po nim.
Andrea Ratto

6

Dodam do tego coś, co znalazłem na forach wiosennych. Jeśli przeniesiesz słoik sterownika JDBC do folderu tomcat lib, zamiast instalować go za pomocą aplikacji internetowej, ostrzeżenie wydaje się znikać. Mogę potwierdzić, że to zadziałało dla mnie

http://forum.springsource.org/showthread.php?87335-Failure-to-unregister-the-MySQL-JDBC-Driver&p=334883#post334883


1
Myślę, że oferuje to lepsze rozwiązanie - wystarczy podklasować menedżera danych DatasourceManager i zastąpić metodę close, aby dodać wyrejestrowanie. Wtedy Spring sobie z tym poradzi, gdy zniszczy swój kontekst, a Tomcat nie poda WIELKIEGO dziennika i nie trzeba przenosić sterownika JDBC do katalogu lib. Oto oryginalna wiadomość ze starego wiosennego forum
Adam

6

Odkryłem, że wdrożenie prostej metody destroy () w celu wyrejestrowania sterowników JDBC działa dobrze.

/**
 * Destroys the servlet cleanly by unloading JDBC drivers.
 * 
 * @see javax.servlet.GenericServlet#destroy()
 */
public void destroy() {
    String prefix = getClass().getSimpleName() +" destroy() ";
    ServletContext ctx = getServletContext();
    try {
        Enumeration<Driver> drivers = DriverManager.getDrivers();
        while(drivers.hasMoreElements()) {
            DriverManager.deregisterDriver(drivers.nextElement());
        }
    } catch(Exception e) {
        ctx.log(prefix + "Exception caught while deregistering JDBC drivers", e);
    }
    ctx.log(prefix + "complete");
}

5
Jest to potencjalnie niebezpieczne w środowisku współużytkowanym, ponieważ możesz nie chcieć wyrejestrować wszystkich dostępnych sterowników JDBC. Zobacz moją odpowiedź na bezpieczniejsze podejście. Ponadto tak naprawdę należy to zrobić w ServletContextListener, a nie w przeliczeniu na serwlet, ponieważ sterownik JDBC jest współużytkowany przez wszystkie serwlety w aplikacji internetowej.
daiscog

3

Aby zapobiec wyciekom pamięci, po prostu wyrejestruj sterownik podczas zamykania kontekstu.

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.mywebsite</groupId>
    <artifactId>emusicstore</artifactId>
    <version>1.0-SNAPSHOT</version>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.7.0</version>
                <configuration>
                    <source>1.9</source>
                    <target>1.9</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <!-- ... -->

        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>4.0.1.Final</version>
        </dependency>

        <dependency>
            <groupId>org.hibernate.javax.persistence</groupId>
            <artifactId>hibernate-jpa-2.0-api</artifactId>
            <version>1.0.1.Final</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.11</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/javax.servlet/servlet-api -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.5</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

</project>

MyWebAppContextListener.java

package com.emusicstore.utils;

import com.mysql.cj.jdbc.AbandonedConnectionCleanupThread;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Enumeration;

public class MyWebAppContextListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        System.out.println("************** Starting up! **************");
    }

    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {
        System.out.println("************** Shutting down! **************");
        System.out.println("Destroying Context...");
        System.out.println("Calling MySQL AbandonedConnectionCleanupThread checkedShutdown");
        AbandonedConnectionCleanupThread.checkedShutdown();

        ClassLoader cl = Thread.currentThread().getContextClassLoader();

        Enumeration<Driver> drivers = DriverManager.getDrivers();
        while (drivers.hasMoreElements()) {
            Driver driver = drivers.nextElement();

            if (driver.getClass().getClassLoader() == cl) {
                try {
                    System.out.println("Deregistering JDBC driver {}");
                    DriverManager.deregisterDriver(driver);

                } catch (SQLException ex) {
                    System.out.println("Error deregistering JDBC driver {}");
                    ex.printStackTrace();
                }
            } else {
                System.out.println("Not deregistering JDBC driver {} as it does not belong to this webapp's ClassLoader");
            }
        }
    }

}

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                             http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <listener>
        <listener-class>com.emusicstore.utils.MyWebAppContextListener</listener-class>
    </listener>

<!-- ... -->

</web-app>

Źródło, które zainspirowało mnie do tej poprawki.


2

Miałem podobny problem, ale dodatkowo pojawiał się błąd Java Heap Space za każdym razem, gdy modyfikowałem / zapisywałem strony JSP z uruchomionym serwerem Tomcat, dlatego kontekst nie został w pełni naładowany.

Moje wersje to Apache Tomcat 6.0.29 i JDK 6u12.

Uaktualnienie JDK do wersji 6u21 zgodnie z sugestią w sekcji Odnośniki w adresie URL http://wiki.apache.org/tomcat/MemoryLeakProtection rozwiązało problem przestrzeni stosu Java (kontekst ładuje się teraz OK), chociaż błąd sterownika JDBC nadal występuje.


0

Znalazłem ten sam problem z Tomcat w wersji 6.026.

Użyłem Mysql JDBC.jar w bibliotece WebAPP, a także w TOMCAT Lib.

Aby naprawić powyższe, usuwając Jar z folderu lib TOMCAT.

Rozumiem więc, że TOMCAT prawidłowo obsługuje wyciek pamięci JDBC. Ale jeśli słoik Jdbc MYSQL zostanie zduplikowany w aplikacjach WebApp i Tomcat Lib, Tomcat będzie w stanie obsłużyć słoik obecny w folderze Tomcat Lib.


0

Napotkałem ten problem, kiedy wdrażałem moją aplikację Grails na AWS. Jest to kwestia domyślnego sterownika JDBC sterownik org.h2 . Jak widać na Datasource.groovy w folderze konfiguracji. Jak widać poniżej:

dataSource {
    pooled = true
    jmxExport = true
    driverClassName = "org.h2.Driver"   // make this one comment
    username = "sa"
    password = ""
}

Skomentuj te wiersze, gdziekolwiek jest mowa o org.h2.Driver w pliku , jeśli nie korzystasz z tej bazy danych. W przeciwnym razie musisz pobrać plik jar bazy danych.

Dzięki .


0

Ten błąd przytrafił mi się w aplikacji Grails ze sterownikiem JTDS 1.3.0 (SQL Server). Problem polegał na niepoprawnym logowaniu w SQL Server. Po rozwiązaniu tego problemu (w SQL Server) moja aplikacja została poprawnie wdrożona w Tomcat. Wskazówka: Widziałem błąd w pliku stacktrace.log

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.