1 /*
   2  * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 /*
  25  * @test
  26  * @bug 8030099
  27  * @summary Memory usage of java process increases
  28             after calling Win32ShellFolder:listFiles
  29             multiple times on some directory with
  30             large number of files/folders
  31  * @modules java.desktop/sun.awt.shell
  32  * @requires (os.family == "windows")
  33  * @run main/timeout=1000 ShellFolderMemoryLeak
  34  */
  35 import java.io.BufferedReader;
  36 import java.io.File;
  37 import java.io.FileNotFoundException;
  38 import java.io.IOException;
  39 import java.io.InputStream;
  40 import java.io.InputStreamReader;
  41 import java.util.logging.Level;
  42 import java.util.logging.Logger;
  43 import sun.awt.shell.ShellFolder;
  44 
  45 public class ShellFolderMemoryLeak {
  46 
  47     private final static String tempDir = System.getProperty("java.io.tmpdir");
  48     private static Process process;
  49     public static void main(String[] args) throws Exception {
  50         if (args.length == 0) {
  51             boolean testResultParallel
  52                     = createChildProcessWithParallelCollector();
  53             String result = "";
  54             if (!testResultParallel) {
  55                 result = "Test failed with Parallel collector";
  56             }
  57             boolean testResultDefault
  58                     = createChildProcessWithDefaultCollector();
  59             if (!testResultDefault && !testResultParallel) {
  60                 result += " and with default collector both.";
  61             } else if (!testResultDefault) {
  62                 result = "Test failed with default collector";
  63             }
  64             if (!"".equals(result)) {
  65                 throw new RuntimeException(result);
  66             }
  67         } else {
  68             testListFile(args[args.length - 1]);
  69         }
  70     }
  71 
  72     public static boolean createChildProcessWithDefaultCollector()
  73             throws Exception {
  74         String testDirectory = "TestDirectory1";
  75         testDirectory = tempDir + testDirectory +File.separator;
  76         createTestData(testDirectory);
  77         return runProcess("", testDirectory);
  78     }
  79 
  80     public static boolean createChildProcessWithParallelCollector()
  81             throws Exception {
  82         String testDirectory = "TestDirectory2";
  83         testDirectory = tempDir + testDirectory +File.separator;
  84         createTestData(testDirectory);
  85         return runProcess(" -XX:+UseParallelGC", testDirectory);
  86     }
  87 
  88     public static boolean runProcess(String arg1, String arg2) throws Exception {
  89         String javaPath = System.getProperty("java.home");
  90         String classPathDir = System.getProperty("java.class.path");
  91 
  92         //creating java process which run same class with different Xmx value
  93         String command = javaPath + File.separator + "bin" + File.separator
  94                 + "java -Xmx256M" + arg1 + " -cp "
  95                 + classPathDir
  96                 + " -XaddExports:java.desktop/sun.awt.shell=ALL-UNNAMED"
  97                 + " ShellFolderMemoryLeak " + arg2;
  98         process = Runtime.getRuntime().exec(command);
  99         BufferedReader input = null;
 100         InputStream errorStream = null;
 101         String line = null;
 102         try {
 103             int exitVal = process.waitFor();
 104             input = new BufferedReader(new InputStreamReader(
 105                     process.getInputStream()));
 106             while ((line = input.readLine()) != null) {
 107             }
 108             errorStream = process.getErrorStream();
 109             if (checkExceptions(errorStream) || exitVal != 0) {
 110                 return false;
 111             }
 112         } catch (IllegalThreadStateException e) {
 113             throw new RuntimeException(e);
 114         } finally {
 115             if (input != null) {
 116                 input.close();
 117             }
 118             if (errorStream != null) {
 119                 errorStream.close();
 120             }
 121             process.destroy();
 122         }
 123         return true;
 124     }
 125 
 126     public static boolean checkExceptions(InputStream in) throws IOException {
 127         String tempString;
 128         int count = in.available();
 129         boolean exception = false;
 130         while (count > 0) {
 131             byte[] b = new byte[count];
 132             in.read(b);
 133             tempString = new String(b);
 134             if (!exception) {
 135                 exception = tempString.contains("RunTimeException");
 136             }
 137             count = in.available();
 138         }
 139         return exception;
 140     }
 141 
 142     private static void createTestData(String testDirectory) {
 143         String folder = "folder_";
 144         File testFolder = new File(testDirectory);
 145         if (testFolder.exists()) {
 146             clearTestData(testDirectory);
 147         } else {
 148             if (testFolder.mkdir()) {
 149                 for (int inx = 0; inx < 100; inx++) {
 150                     new File(testFolder + File.separator + folder + inx).mkdir();
 151                 }
 152             } else {
 153                 throw new RuntimeException("Failed to create testDirectory");
 154             }
 155         }
 156     }
 157 
 158     public static void deleteDirectory(File file)
 159             throws IOException {
 160 
 161         if (file.isDirectory()) {
 162             if (file.list().length == 0) {
 163                 file.delete();
 164             } else {
 165                 String files[] = file.list();
 166                 for (String temp : files) {
 167                     File fileDelete = new File(file, temp);
 168                     deleteDirectory(fileDelete);
 169                 }
 170                 if (file.list().length == 0) {
 171                     file.delete();
 172                 }
 173             }
 174         }
 175     }
 176 
 177     private static void testListFile(String testDirectory) {
 178         try {
 179             int mb = 1024 * 1024;
 180             ShellFolder folder = ShellFolder.getShellFolder(
 181                     new File(testDirectory));
 182             Runtime instance = Runtime.getRuntime();
 183 
 184             //Memory used before calling listFiles
 185             long startmem = instance.totalMemory() - instance.freeMemory();
 186             long start = System.currentTimeMillis();
 187             long endmem = 0;
 188 
 189             //Calling listFiles for 5 minutes with sleep of 10 ms.
 190             while ((System.currentTimeMillis() - start) < 300000) {
 191                 try {
 192                     folder.listFiles();
 193                     Thread.sleep(10);
 194                     endmem = instance.totalMemory() - instance.freeMemory();
 195                 } catch (InterruptedException ex) {
 196                     Logger.getLogger(ShellFolderMemoryLeak.class.getName())
 197                             .log(Level.SEVERE, "InterruptedException", ex);
 198                 }
 199             }
 200 
 201             //Total increase in memory after 5 minutes
 202             long result = (endmem - startmem) / mb;
 203 
 204             if (result > 100) {
 205                 clearTestData(testDirectory);
 206                 throw new RuntimeException("Test Failed");
 207             }
 208             clearTestData(testDirectory);
 209         } catch (FileNotFoundException ex) {
 210             if(process != null && process.isAlive()) {
 211                 process.destroy();
 212             }
 213             Logger.getLogger(ShellFolderMemoryLeak.class.getName())
 214                     .log(Level.SEVERE, "File Not Found Exception", ex);
 215         }
 216     }
 217 
 218     private static void clearTestData(String testDirectory) {
 219         File testFolder = new File(testDirectory);
 220         try {
 221             deleteDirectory(testFolder);
 222         } catch (IOException ex) {
 223             Logger.getLogger(ShellFolderMemoryLeak.class.getName())
 224                     .log(Level.SEVERE, "Unable to delete files", ex);
 225         }
 226     }
 227 }