Я пытаюсь реализовать многопоточность для некоторого параллелизма задач в программе, которую я пишу. Программа использует 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 МБ самостоятельно. я проверил с моей командой, и никто не использовал приложение за это время. Не уверен, что делать с этим
Взгляните на это сообщение 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
Очевидно, что проблема с строкой и сильным строителем не имеет никакого отношения к тому, вы получаете утечку памяти или нет. Откуда вы знаете, что у вас есть утечка? Если метод вызывается слишком часто до завершения предыдущих итераций, вы исчерпаете память. с другой стороны, если у вас все еще есть свободная память, нет никаких оснований для начала сбора GC, даже если некоторые объекты будут собираемыми. Это не похоже на утечку памяти в любом месте. – Voo
@Voo Если я запустил приложение PCF сообщает о используемой памяти 400 МБ. если я скажу ему, чтобы развернуть пару тысяч потоков, использование памяти достигает 450 МБ. Если я проверил его через несколько часов, он все еще на 450mb –