Home | History | Annotate | Download | only in src
      1 /*
      2  * Copyright (C) 2009 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 import java.io.File;
     18 import java.lang.ref.WeakReference;
     19 import java.lang.reflect.Method;
     20 import java.lang.reflect.InvocationTargetException;
     21 
     22 public class Main {
     23     public static volatile boolean quit = false;
     24     public static final boolean DEBUG = false;
     25 
     26     private static final boolean WRITE_HPROF_DATA = false;
     27     private static final int TEST_TIME = 10;
     28     private static final String OUTPUT_FILE = "gc-thrash.hprof";
     29 
     30     public static void main(String[] args) {
     31         // dump heap before
     32 
     33         System.out.println("Running (" + TEST_TIME + " seconds) ...");
     34         runTests();
     35 
     36         Method dumpHprofDataMethod = null;
     37         String dumpFile = null;
     38 
     39         if (WRITE_HPROF_DATA) {
     40             dumpHprofDataMethod = getDumpHprofDataMethod();
     41             if (dumpHprofDataMethod != null) {
     42                 dumpFile = getDumpFileName();
     43                 System.out.println("Sending output to " + dumpFile);
     44             }
     45         }
     46 
     47         System.gc();
     48         System.runFinalization();
     49         System.gc();
     50 
     51         if (WRITE_HPROF_DATA && dumpHprofDataMethod != null) {
     52             try {
     53                 dumpHprofDataMethod.invoke(null, dumpFile);
     54             } catch (IllegalAccessException iae) {
     55                 System.err.println(iae);
     56             } catch (InvocationTargetException ite) {
     57                 System.err.println(ite);
     58             }
     59         }
     60 
     61         System.out.println("Done.");
     62     }
     63 
     64     /**
     65      * Finds VMDebug.dumpHprofData() through reflection.  In the reference
     66      * implementation this will not be available.
     67      *
     68      * @return the reflection object, or null if the method can't be found
     69      */
     70     private static Method getDumpHprofDataMethod() {
     71         ClassLoader myLoader = Main.class.getClassLoader();
     72         Class vmdClass;
     73         try {
     74             vmdClass = myLoader.loadClass("dalvik.system.VMDebug");
     75         } catch (ClassNotFoundException cnfe) {
     76             return null;
     77         }
     78 
     79         Method meth;
     80         try {
     81             meth = vmdClass.getMethod("dumpHprofData",
     82                     new Class[] { String.class });
     83         } catch (NoSuchMethodException nsme) {
     84             System.err.println("Found VMDebug but not dumpHprofData method");
     85             return null;
     86         }
     87 
     88         return meth;
     89     }
     90 
     91     private static String getDumpFileName() {
     92         File tmpDir = new File("/tmp");
     93         if (tmpDir.exists() && tmpDir.isDirectory()) {
     94             return "/tmp/" + OUTPUT_FILE;
     95         }
     96 
     97         File sdcard = new File("/sdcard");
     98         if (sdcard.exists() && sdcard.isDirectory()) {
     99             return "/sdcard/" + OUTPUT_FILE;
    100         }
    101 
    102         return null;
    103     }
    104 
    105 
    106     /**
    107      * Run the various tests for a set period.
    108      */
    109     public static void runTests() {
    110         Robin robin = new Robin();
    111         Deep deep = new Deep();
    112         Large large = new Large();
    113 
    114         /* start all threads */
    115         robin.start();
    116         deep.start();
    117         large.start();
    118 
    119         /* let everybody run for 10 seconds */
    120         sleep(TEST_TIME * 1000);
    121 
    122         quit = true;
    123 
    124         try {
    125             /* wait for all threads to stop */
    126             robin.join();
    127             deep.join();
    128             large.join();
    129         } catch (InterruptedException ie) {
    130             System.err.println("join was interrupted");
    131         }
    132     }
    133 
    134     /**
    135      * Sleeps for the "ms" milliseconds.
    136      */
    137     public static void sleep(int ms) {
    138         try {
    139             Thread.sleep(ms);
    140         } catch (InterruptedException ie) {
    141             System.err.println("sleep was interrupted");
    142         }
    143     }
    144 
    145     /**
    146      * Sleeps briefly, allowing other threads some CPU time to get started.
    147      */
    148     public static void startupDelay() {
    149         sleep(500);
    150     }
    151 }
    152 
    153 
    154 /**
    155  * Allocates useless objects and holds on to several of them.
    156  *
    157  * Uses a single large array of references, replaced repeatedly in round-robin
    158  * order.
    159  */
    160 class Robin extends Thread {
    161     private static final int ARRAY_SIZE = 40960;
    162     int sleepCount = 0;
    163 
    164     public void run() {
    165         Main.startupDelay();
    166 
    167         String strings[] = new String[ARRAY_SIZE];
    168         int idx = 0;
    169 
    170         while (!Main.quit) {
    171             strings[idx] = makeString(idx);
    172 
    173             if (idx % (ARRAY_SIZE / 4) == 0) {
    174                 Main.sleep(400);
    175                 sleepCount++;
    176             }
    177 
    178             idx = (idx + 1) % ARRAY_SIZE;
    179         }
    180 
    181         if (Main.DEBUG)
    182             System.out.println("Robin: sleepCount=" + sleepCount);
    183     }
    184 
    185     private String makeString(int val) {
    186         try {
    187             return new String("Robin" + val);
    188         } catch (OutOfMemoryError e) {
    189             return null;
    190         }
    191     }
    192 }
    193 
    194 
    195 /**
    196  * Allocates useless objects in recursive calls.
    197  */
    198 class Deep extends Thread {
    199     private static final int MAX_DEPTH = 61;
    200 
    201     private static String strong[] = new String[MAX_DEPTH];
    202     private static WeakReference weak[] = new WeakReference[MAX_DEPTH];
    203 
    204     public void run() {
    205         int iter = 0;
    206         boolean once = false;
    207 
    208         Main.startupDelay();
    209 
    210         while (!Main.quit) {
    211             dive(0, iter);
    212             once = true;
    213             iter += MAX_DEPTH;
    214         }
    215 
    216         if (!once) {
    217             System.err.println("not even once?");
    218             return;
    219         }
    220 
    221         checkStringReferences();
    222 
    223         /*
    224          * Wipe "strong", do a GC, see if "weak" got collected.
    225          */
    226         for (int i = 0; i < MAX_DEPTH; i++)
    227             strong[i] = null;
    228 
    229         Runtime.getRuntime().gc();
    230 
    231         for (int i = 0; i < MAX_DEPTH; i++) {
    232             if (weak[i].get() != null) {
    233                 System.err.println("Deep: weak still has " + i);
    234             }
    235         }
    236 
    237         if (Main.DEBUG)
    238             System.out.println("Deep: iters=" + iter / MAX_DEPTH);
    239     }
    240 
    241 
    242     /**
    243      * Check the results of the last trip through.  Everything in
    244      * "weak" should be matched in "strong", and the two should be
    245      * equivalent (object-wise, not just string-equality-wise).
    246      *
    247      * We do that check in a separate method to avoid retaining these
    248      * String references in local DEX registers. In interpreter mode,
    249      * they would retain these references until the end of the method
    250      * or until they are updated to another value.
    251      */
    252     private static void checkStringReferences() {
    253       for (int i = 0; i < MAX_DEPTH; i++) {
    254           if (strong[i] != weak[i].get()) {
    255               System.err.println("Deep: " + i + " strong=" + strong[i] +
    256                   ", weak=" + weak[i].get());
    257           }
    258       }
    259     }
    260 
    261     /**
    262      * Recursively dive down, setting one or more local variables.
    263      *
    264      * We pad the stack out with locals, attempting to create a mix of
    265      * valid and invalid references on the stack.
    266      */
    267     private String dive(int depth, int iteration) {
    268         try {
    269             String str0;
    270             String str1;
    271             String str2;
    272             String str3;
    273             String str4;
    274             String str5;
    275             String str6;
    276             String str7;
    277             String funStr = "";
    278             switch (iteration % 8) {
    279                 case 0:
    280                     funStr = str0 = makeString(iteration);
    281                     break;
    282                 case 1:
    283                     funStr = str1 = makeString(iteration);
    284                     break;
    285                 case 2:
    286                     funStr = str2 = makeString(iteration);
    287                     break;
    288                 case 3:
    289                     funStr = str3 = makeString(iteration);
    290                     break;
    291                 case 4:
    292                     funStr = str4 = makeString(iteration);
    293                     break;
    294                 case 5:
    295                     funStr = str5 = makeString(iteration);
    296                     break;
    297                 case 6:
    298                     funStr = str6 = makeString(iteration);
    299                     break;
    300                 case 7:
    301                     funStr = str7 = makeString(iteration);
    302                     break;
    303             }
    304 
    305             weak[depth] = new WeakReference(funStr);
    306             strong[depth] = funStr;
    307             if (depth+1 < MAX_DEPTH)
    308                 dive(depth+1, iteration+1);
    309             else
    310                 Main.sleep(100);
    311             return funStr;
    312         } catch (OutOfMemoryError e) {
    313             // Silently ignore OOME since gc stress mode causes them to occur but shouldn't be a
    314             // test failure.
    315         }
    316         return "";
    317     }
    318 
    319     private String makeString(int val) {
    320         try {
    321             return new String("Deep" + val);
    322         } catch (OutOfMemoryError e) {
    323             return null;
    324         }
    325     }
    326 }
    327 
    328 
    329 /**
    330  * Allocates large useless objects.
    331  */
    332 class Large extends Thread {
    333     public void run() {
    334         byte[] chunk;
    335         int count = 0;
    336         int sleepCount = 0;
    337 
    338         Main.startupDelay();
    339 
    340         while (!Main.quit) {
    341             try {
    342                 chunk = new byte[100000];
    343                 pretendToUse(chunk);
    344 
    345                 count++;
    346                 if ((count % 500) == 0) {
    347                     Main.sleep(400);
    348                     sleepCount++;
    349                 }
    350             } catch (OutOfMemoryError e) {
    351             }
    352         }
    353 
    354         if (Main.DEBUG)
    355             System.out.println("Large: sleepCount=" + sleepCount);
    356     }
    357 
    358     public void pretendToUse(byte[] chunk) {}
    359 }
    360