1

Я пытаюсь реализовать многопоточность для некоторого параллелизма задач в программе, которую я пишу. Программа использует Spring framework и работает на Pivotal Cloud Foundry. Иногда это происходило, поэтому я вошел и просмотрел журналы и показатели производительности; когда я обнаружил, что он имеет утечку памяти. после некоторого тестирования я сузил виновника в моей реализации потоков. Мое понимание GC в JVM заключается в том, что он не будет избавляться от потока, который не является мертвым, и не будет уничтожать какой-либо объект, на который все еще ссылается другой объект или более поздняя строка исполняемого кода. Тем не менее, я не придерживаюсь ссылок на поток, и если я это делаю, я утверждаю, что он попал в мертвое состояние, как только он закончит работать, поэтому я не знаю, что вызывает утечку.Ячейка памяти потоковой передачи Java

Я написал чистый PoC, чтобы продемонстрировать утечку. он использует контроллер отдыха, поэтому я могу контролировать количество потоков, класс runnable, потому что моя реальная программа требует параметров, а строка занимает произвольное пространство в памяти, которое будет удерживаться другими полями в реальной программе (делает утечку больше очевидный).

package com.example; 
import org.springframework.web.bind.annotation.RequestMapping; 
import org.springframework.web.bind.annotation.RequestParam; 
import org.springframework.web.bind.annotation.RestController; 

@RestController 
public class LeakController { 

    @RequestMapping("/Run") 
    public String DoWork(@RequestParam("Amount") int amount, @RequestParam("Args") String args) 
    { 
     for(int i = 0; i < amount; i++) 
      new Thread(new MyRunnable(args)).start(); 
     return "Workin' on it"; 
    } 

    public class MyRunnable implements Runnable{ 
     String args; 
     public MyRunnable(String args){ this.args = args; } 
     public void run() 
     { 
      int timeToSleep = Integer.valueOf(args); 
      String spaceWaster = ""; 
      for (int i = 0; i < 10000; i ++) 
       spaceWaster += "W"; 
      System.out.println(spaceWaster); 
      try {Thread.sleep(timeToSleep);} catch (InterruptedException e) {e.printStackTrace();} 
      System.out.println("Done"); 
     } 
    } 
} 

Может кто-нибудь объяснить, почему эта программа утечки памяти?

Edit: Я получил несколько ответов о назначении Струнного против струнного здания и строки пула, так что я изменил код к следующим

 int[] spaceWaster = new int[10000]; 
     for (int i = 0; i < 10000; i ++) 
      spaceWaster[i] = 512; 
     System.out.println(spaceWaster[1]); 

и это все еще просачивается.

Редактировать: Приобретая некоторые реальные цифры для ответа на Voo, я заметил что-то интересное. вызов новых потоков начинает питаться памятью, но только до точки. после постоянного роста около 60 мб новая целочисленная программа перестает расти, независимо от того, насколько сильно она продвигается. Связано ли это с тем, как весенняя структура выделяет память?

Я также считаю, что есть заслуга в возвращении к примеру строки, поскольку она более тесно связана с моим реальным случаем использования; который должен выполнять операции регулярного выражения на входящем JSON, несколько сотен таких JSON в секунду. Имея это в виду, я изменил код:

@RestController 
public class LeakController { 

    public static String characters[] = { 
      "1","2","3","4","5","6","7","8","9","0", 
      "A","B","C","D","E","F","G","H","I","J","K","L","M", 
      "N","O","P","Q","R","S","T","U","V","W","X","Y","Z"}; 
    public Random rng = new Random(); 

    @RequestMapping("/Run") 
    public String GenerateAndSend(@RequestParam("Amount") int amount) 
    { 
     for(int i = 0; i < amount; i++) 
     { 
      StringBuilder sb = new StringBuilder(100); 
      for(int j = 0; j< 100; j++) 
       sb.append(characters[rng.nextInt(36)]); 
      new Thread(new MyRunnable(sb.toString())).start(); 
      System.out.println("Thread " + i + " created"); 
     } 
     System.out.println("Done making threads"); 
     return "Workin' on it"; 
    } 

    public class MyRunnable implements Runnable{ 
     String args; 
     public MyRunnable(String args){ this.args = args; } 
     public void run() 
     { 
      System.out.println(args); 
      args = args.replaceAll("\\d+", "\\[Number was here\\]"); 
      System.out.println(args); 
     } 
    } 
} 

Это новое приложение демонстрирует подобное поведение как целого, например, в том, что он растет около 50mb постоянно (после 2000 нитей) и суживается оттуда, пока я не наклоняю уведомление любой рост памяти с каждой новой партией из 1000 потоков (около 85 Мб после первоначальной развернутой памяти).

если я изменить его, чтобы удалить StringBuilder:

String temp = ""; 
for(int j = 0; j< 100; j++) 
    temp += characters[rng.nextInt(36)]; 
new Thread(new MyRunnable(temp)).start(); 

его утечки на неопределенный срок; Я предполагаю, что когда все 36^100 строк были сгенерированы после того, как они остановятся.

Объединив эти выводы, я думаю, что моя реальная проблема может быть как проблемой с пулом строк, так и проблемой с тем, как весна выделяет память. То, что я до сих пор не понимаю, заключается в том, что в моем реальном приложении, если я делаю runnable и call run() в основном потоке, память, похоже, не всплывает, но если я создаю новый поток и передаю ему runnable, тогда скачки памяти , Heres, что мой runnable выглядит в настоящее время в заявке, которую я создаю:

public class MyRunnable implements Runnable{ 
    String json; 
    public MyRunnable(String json){ 
     this.json = new String(json); 
    } 
    public void run() 
    { 
     DocumentClient documentClient = new DocumentClient (END_POINT, 
       MASTER_KEY, ConnectionPolicy.GetDefault(), 
       ConsistencyLevel.Session); 
     System.out.println("JSON : " + json); 
     Document myDocument = new Document(json); 
     System.out.println(new DateTime().toString(DateTimeFormat.forPattern("MM-dd-yyyy>HH:mm:ss.SSS"))+">"+"Created JSON Document Locally"); 
     // Create a new document 
     try { 
      //collectioncache is a variable in the parent restcontroller class that this class is declared inside of 
      System.out.println("CollectionExists:" + collectionCache != null); 
      System.out.println("CollectionLink:" + collectionCache.getSelfLink()); 
      System.out.println(new DateTime().toString(DateTimeFormat.forPattern("MM-dd-yyyy>HH:mm:ss.SSS"))+">"+"Creating Document on DocDB"); 
      documentClient.createDocument(collectionCache.getSelfLink(), myDocument, null, false); 
      System.out.println(new DateTime().toString(DateTimeFormat.forPattern("MM-dd-yyyy>HH:mm:ss.SSS"))+">"+"Document Creation Successful"); 
      System.out.flush(); 
      currentThreads.decrementAndGet(); 
     } catch (DocumentClientException e) { 
      System.out.println("Failed to Upload Document"); 
      e.printStackTrace(); 
     } 
    } 
} 

Любые идеи, где моя настоящая утечка? Где-то мне нужен строковый построитель? действительно ли строка просто делает память смешной, и мне нужно дать ей более высокий потолок, чтобы растянуться, тогда все будет хорошо?

Edit: я сделал некоторые бенчмаркинга, так что я мог бы на самом деле график поведения для того, чтобы лучше понять, что делает GC

00000 Threads - 457 MB 
01000 Threads - 535 MB 
02000 Threads - 545 MB 
03000 Threads - 549 MB 
04000 Threads - 551 MB 
05000 Threads - 555 MB 
2 hours later - 595 MB 
06000 Threads - 598 MB 
07000 Threads - 600 MB 
08000 Threads - 602 MB 

кажется асимптотическим, но то, что было самым интересным для меня является то, что в то время как я отсутствовал на собраниях и ест обед, он решил выращивать 40 МБ самостоятельно. я проверил с моей командой, и никто не использовал приложение за это время. Не уверен, что делать с этим

+0

Взгляните на это сообщение http://stackoverflow.com/questions/65668/why-to-use-stringbuffer-in-java-instead-of-the-string-concatenation-оператор, а также http: // stackoverflow.com/questions/18406703/when-will-a-string-be-garbage-collected-in-java – JavaHopper

+0

Очевидно, что проблема с строкой и сильным строителем не имеет никакого отношения к тому, вы получаете утечку памяти или нет. Откуда вы знаете, что у вас есть утечка? Если метод вызывается слишком часто до завершения предыдущих итераций, вы исчерпаете память. с другой стороны, если у вас все еще есть свободная память, нет никаких оснований для начала сбора GC, даже если некоторые объекты будут собираемыми. Это не похоже на утечку памяти в любом месте. – Voo

+0

@Voo Если я запустил приложение PCF сообщает о используемой памяти 400 МБ. если я скажу ему, чтобы развернуть пару тысяч потоков, использование памяти достигает 450 МБ. Если я проверил его через несколько часов, он все еще на 450mb –

ответ

0

Это потому, что вы продолжаете добавлять String. Java не GC Строка пула автоматически

Java String Pool

String spaceWaster = ""; 
      for (int i = 0; i < 10000; i ++) 
       spaceWaster += "W"; 

использования StringBuilder вместо

+0

после завершения цикла, хотя он должен завершить метод, выполнить с помощью spaceWaster и избавиться от поля, а это не так.StringBuilder не будет делать разницу, я мог бы заменить spaceWaster «int [] spacewaster2 = new int [1000000]», и утечка остается –

+0

. Только строки, интернированные на Java, являются литералами («W» и пустым в этом случае) или строки, которые вы явно вызываете intern on. Все остальное не по понятным причинам. – Voo

-1

использование stringbuilder было правильным

не думаю, что вам нужно 2000 нитей.

Лучшая конструкция может быть A Queue для задач (строка/документы) и thread pool для обработки строки/документов.

+0

Я согласен, что threadpooling будет лучше, чем использование AtomicInteger, чтобы отслеживать количество потоков, но у меня нет опыта с пулом в java, и в настоящее время я занимаюсь большей частью PoC, чем работаю на производственном коде. также программа фактически считывает из очереди и разворачивает потоки для обработки очереди. вся причина, по которой я вообще пронизываю, заключается в том, что Azure Document DB (предложение NoSQL) занимает недопустимое количество времени, чтобы добавить новую запись, но хорошо масштабируется для нескольких вызовов одновременно. –

+0

ОК, я вижу. зависит от вашей строки, она, вероятно, попала прямо в 'Permanent Generation', но не' eden space'. вам нужно настроить параметры jvm. – user3644708

+0

Никакие динамически выделенные строки не идут в постоянное поколение, нет абсолютно никаких оснований настраивать там какие-либо параметры. Черт, даже не интернированные струны, на какое-то время кончаются. – Voo

Смежные вопросы