IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Tomcat et TomEE Java Discussion :

problème de fuite de mémoire d'une application Java sous tomcat interrogee à partir d'un client Java


Sujet :

Tomcat et TomEE Java

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre éclairé
    Profil pro
    Inscrit en
    Septembre 2006
    Messages
    729
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2006
    Messages : 729
    Par défaut problème de fuite de mémoire d'une application Java sous tomcat interrogee à partir d'un client Java
    Bonjour,

    j'utilise TOMCAT pour déployer une application SpringBoot / Java. j'ai implémenté un web service dans cette application

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
     
        @PostMapping("/removeRoles")
        public String removeRoles(@RequestBody String path, HttpServletRequest request, HttpServletResponse response) throws RecordNotFoundException, CustomException, NotFoundException, IOException {
     
            String decodedPath = "";
            try {
                decodedPath = java.net.URLDecoder.decode(path, StandardCharsets.UTF_8.name());
                decodedPath = decodedPath.substring(0,decodedPath.length()-1);
            } catch (UnsupportedEncodingException e) {
                // not going to happen - value came from JDK's own StandardCharsets
            }
            DateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd_HH_mm_ss");
            String currentDateTime = dateFormatter.format(new Date());
            Path javaPath = Paths.get(decodedPath + "\\log-removeRoles-"+currentDateTime+".txt");
            try {
                File myObj = new File(decodedPath + "\\log-removeRoles-"+currentDateTime+".txt");
                if (myObj.createNewFile()) {
                    System.out.println("File created: " + myObj.getName());
                } else {
                    System.out.println("File already exists.");
                }
            } catch (IOException e) {
                System.out.println("An error occurred.");
                e.printStackTrace();
            }
     
                scriptService.removeRoles(javaPath);
     
            return "SUCCESS";
        }
    Le service removeRoles( String javaPath) est le suivant

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
        public void removeRoles(Path javaPath) throws RecordNotFoundException, CustomException, NotFoundException, IOException {
            PrintStream originalOut = System.out;  // original PrintStream
            PrintStream out = new PrintStream(Files.newOutputStream(javaPath), true); // new PrintSream to redirect System.out.println towards file which path is "javaPath"
            System.setOut(out);
            List<RequestEntity> allRequest = requestRepository.findAllByStatusDone();  // find all requests which status is DONE
            System.out.println("TOTAL COUNT="+allRequest.size());  // print the size of the collection
            System.setOut(originalOut);
            RoleEntity role;
            String groupAD;
            List<EmployeeDto> listEmployees;
            List<String> findADEmployeesByAffectedUser;
            int count = 1;
            RequestHasStatus requestHasStatus;
            StatusEntity statusEntityREMOVED = requestService.readStatusByName(Status.REMOVED.getStat());
            EmployeeEntity userTrigger= getEmployee("mdupont", "Michel", "DUPONT", "michel.dupont@organization.fr");
            for (RequestEntity requestEntity : allRequest) {
                System.setOut(originalOut);
                role = requestEntity.getRole();
                groupAD = role.getGroupAD();
                listEmployees = lDAPService.getEmployeesResults(groupAD);  // request the list of employees that belongs to Group "groupAD" in Active Directory
                final RequestEntity requestEntityTemp = requestEntity;
                findADEmployeesByAffectedUser = listEmployees.stream().map((EmployeeDto emp) -> emp.getAccount()).filter((String account) -> account.indexOf(requestEntityTemp.getAffectedUser().getAccountAD()) > -1).collect(Collectors.toList());
                if (findADEmployeesByAffectedUser.isEmpty()) {
                    List<RequestHasStatus> listRequestHasStatus = requestHasStatusRepository.getRequestHasStatusByRequestAndStatus(requestEntity.getId(), statusEntityREMOVED.getId()); // list of RequestHasStatus object which identifier is requestEntity.getId() and status is REMOVED
                    if (listRequestHasStatus.isEmpty()) {  // if the list is empty
                        requestHasStatus = new RequestHasStatus();
                        requestHasStatus.setRequest(requestEntity);
                        requestHasStatus.setStatus(statusEntityREMOVED);
                        requestHasStatus.setReason("request that are in App and not in AD in September 2023");
                        requestHasStatus.setDate(new Date());
                        requestHasStatus.setTriggeredBy(userTrigger);
                        requestHasStatus = requestHasStatusRepository.saveAndFlush(requestHasStatus);  // create RequestHasStatus Object with status REMOVED
                        requestEntity.getRequestHasStatuses().add(requestHasStatus);
                        requestEntity.setCurrentStatusId(statusEntityREMOVED.getId());
                        requestEntity = requestRepository.saveAndFlush(requestEntity); // update requestEntity with the new status
                        System.setOut(out);
                        System.out.println("COUNT=" + count + "  Account User=" + requestEntity.getUser().getAccountAD() + "  Role Group AD=" + groupAD + " Status REMOVED ("+requestHasStatus.toString()+") ADDED "); // print in the file which path is javaPath
                    } else {
                        System.setOut(out);
                        System.out.println("COUNT=" + count + "  Account User=" + requestEntity.getUser().getAccountAD() + "  Role Group AD=" + groupAD + "EXISTS in Active Directory");
                    }
                }
                System.setOut(out);
                System.out.println("COUNT=" + count);
                count++;
            }
        }
    son but est de rajouter un statut REMOVED pour les requêtes qui sont dans App et pas dans l'Active Directory


    J'ai un client Java qui est le suivant

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
        public static void main(String[] args) {
    
                try {
                    String currentLocation = System.getProperty("user.dir");
                    System.out.println("CURRENT LOCATION="+currentLocation);
    
                    URL url = new URL("http://ip:8080/removeRoles");
     
                    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                    conn.setDoOutput(true);
                    conn.setRequestMethod("POST");
                    conn.setRequestProperty("Accept", "application/json");
                    OutputStream os = conn.getOutputStream();
                    OutputStreamWriter osw = new OutputStreamWriter(os, "UTF-8");
                    osw.write(currentLocation);
                    osw.flush();
                    osw.close();
                    os.close();  //don't forget to close the OutputStream
    
                    if (conn.getResponseCode() != 200) {  // the request is sent at this line
                        throw new RuntimeException("Failed : HTTP error code : "
                                + conn.getResponseCode());
                    }
    
                    BufferedReader br = new BufferedReader(new InputStreamReader(
                            (conn.getInputStream())));
    
                    String output;
                    System.out.println("Output from Server .... \n");
                    while ((output = br.readLine()) != null) {
                        System.out.println(output);
                    }
    
                    conn.disconnect();
    
                } catch (MalformedURLException e) {
    
                    e.printStackTrace();
    
                } catch (IOException e) {
    
                    e.printStackTrace();
    
                }
            
    }

    MON PROBLEME
    --------------------
    Quand je déploie la partie backend sous TOMCAT et regarde tomcat.exe dans le task manager, la mémoire utilisée se stabilise à environ 350.000 K.
    Quand je lance le client (création d'une connection à partir de HttpURLConnection ), la mémoire du processus tomcat.exe augmente de 2.000K to 10.000K chaque seconde jusqu'à ce que TOMCAT crash
    Quel peut être la cause de la fuite de mémoire ?

  2. #2
    Membre très actif

    Profil pro
    Inscrit en
    Janvier 2009
    Messages
    486
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2009
    Messages : 486
    Billets dans le blog
    5
    Par défaut
    Je pense que le problème est que tout doit être fermé.

    Quand un machin est autoclosable ( https://docs.oracle.com/en/java/java...Closeable.html ), il faut utiliser le try-with-ressource.

    Exemple d'un Autoclosable: OutputStream.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    try(Outputstream out =...){
     
    }
    Une fois le code de le try utilisé, la méthode close sera automatiquement appelée, et les ressources automatiquement libéré.

    Une question m'interpelle néanmoins: Pourquoi le HttpURLConnection n'est pas Autoclosable:
    http://igm.univ-mlv.fr/~juge/javadoc...onnection.html
    http://igm.univ-mlv.fr/~juge/javadoc...onnection.html

    Néanmoins, le HttpURLConnection a une méthode close à également appeler.

    Selon la Javadoc:
    Each HttpURLConnection instance is used to make a single request but the underlying network connection to the HTTP server may be transparently shared by other instances. Calling the close() methods on the InputStream or OutputStream of an HttpURLConnection after a request may free network resources associated with this instance but has no effect on any shared persistent connection. Calling the disconnect() method may close the underlying socket if a persistent connection is otherwise idle at that time.

  3. #3
    Membre éclairé
    Profil pro
    Inscrit en
    Septembre 2006
    Messages
    729
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2006
    Messages : 729
    Par défaut
    Bonjour, je penses avoir trouvé où se trouve le problème mais ne sait pas encore le résoudre


    quand je fais appel au LDAP, j'utilise un com.unboundid.ldap.sdk.LDAPConnectionPool et il semble que ça utilise de la mémoire

    Comme je fais des appels au LDAP via cet LDAPConnectionPool en boucle ça utilise beaucoup de mémoire

    il semble que si j'utilise LDAPConnectionPool.close() après chaque appel d'une recherche ça libère la mémoire. Donc comment réouvrir le LDAPConnectionPool après avoir fait un close() ?

    J'utilise SpringBoot

  4. #4
    Membre très actif

    Profil pro
    Inscrit en
    Janvier 2009
    Messages
    486
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2009
    Messages : 486
    Billets dans le blog
    5
    Par défaut
    Selon la javadoc, LDAPConnectionPool est Autoclosable:
    https://docs.ldap.com/ldap-sdk/docs/...ctionPool.html

    Il faut donc utiliser un try-with-ressource pour le fermer après utilisation:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    try(LDAPConnectionPool pool = ...){
    //Traitement
    }
    Ensuite, si on utilise Spring, je rappelle qu'il existe Spring LDAP:
    https://spring.io/projects/spring-ldap

  5. #5
    Membre très actif

    Profil pro
    Inscrit en
    Janvier 2009
    Messages
    486
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2009
    Messages : 486
    Billets dans le blog
    5
    Par défaut
    Autre chose, si on a un traitement en BDD, on n'a pas besoin d'ouvrir 20 fois une connexion.

    En gros, à ne pas faire:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    for(...){
      try(Connexion con = ...){
     
      }
     
    }
    Préférer:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
     
    try(Connexion con = ...){
     
      for(...){
     
      }
     
    }
    Idem si on se connecte à un LDAP.

  6. #6
    Membre très actif

    Profil pro
    Inscrit en
    Janvier 2009
    Messages
    486
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2009
    Messages : 486
    Billets dans le blog
    5
    Par défaut
    Si tu utilise LDAP, et vu que Spring-LDAP est peu connu, ce lien pourrait te servir:
    https://www.baeldung.com/spring-ldap

    Cordialement.

  7. #7
    Membre éclairé
    Profil pro
    Inscrit en
    Septembre 2006
    Messages
    729
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2006
    Messages : 729
    Par défaut
    Bonjour Philippe,
    merci pour ton message, pour la fuite de mémoire, j'ai trouvé la solution.

    La raison de la fuite de mémoire est le fait que une connection du pool de connection pour LDAP prend de la mémoire à chaque fois et comme je fait appel au LDAP en boucle cela demande de plus en plus de mémoire.

    La solution que j'ai trouvé est de fermer l'objet LDAPConnectionPool à chaque fois que j'utilise une méthode de cet objet et de le recréer tout de suite après.

    Pour l'intégrer dans mon code, j'utilise un wrapper dont le code est le suivant:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
     
    public class CustomLDAPConnectionPool {
     
        public LDAPConnection lDAPConnection;
        public int numConnection;
     
        public LDAPConnectionPool connection;
     
        public CustomLDAPConnectionPool(LDAPConnection connection, int numConnection) throws LDAPException {
            this.lDAPConnection = connection;
            this.numConnection = numConnection;
            this.connection = new LDAPConnectionPool(connection, numConnection);
        }
     
        public SearchResult search(SearchRequest request) throws LDAPException {
            SearchResult result = this.connection.search(request);
            this.connection.close();
            this.connection = new LDAPConnectionPool(this.lDAPConnection, this.numConnection);
            return result;
        }
     
        public SearchResultEntry searchForEntry(@NotNull SearchRequest searchRequest) throws LDAPException {
            SearchResultEntry result = this.searchForEntry(searchRequest);
            this.connection.close();
            this.connection = new LDAPConnectionPool(this.lDAPConnection, this.numConnection);
            return result;
        }
    }

  8. #8
    Membre très actif

    Profil pro
    Inscrit en
    Janvier 2009
    Messages
    486
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2009
    Messages : 486
    Billets dans le blog
    5
    Par défaut
    Le LDAP Template doit te permettre de faire tout ça:

    https://docs.spring.io/spring-ldap/d...pTemplate.html

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Réponses: 4
    Dernier message: 05/01/2012, 17h26
  2. problème chargement d'un combo à partir d'une base de données
    Par reverse_engineer dans le forum Struts 1
    Réponses: 3
    Dernier message: 10/07/2008, 07h15
  3. Réponses: 1
    Dernier message: 04/10/2007, 16h35
  4. problème de mémoire pour le processus w3wp.exe
    Par nasser.h dans le forum IIS
    Réponses: 4
    Dernier message: 07/02/2007, 14h51
  5. Réponses: 10
    Dernier message: 17/11/2006, 15h20

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo