Java单元测试:如何测量方法调用的内存占用
假设我有一个类,它执行一些繁重的处理,使用多个集合进行操作。我想做的是确保这样的操作不会导致内存不足,甚至更好,我想设置一个它可以使用多少内存的阈值Java单元测试:如何测量方法调用的内存占用,java,unit-testing,junit,out-of-memory,testng,Java,Unit Testing,Junit,Out Of Memory,Testng,假设我有一个类,它执行一些繁重的处理,使用多个集合进行操作。我想做的是确保这样的操作不会导致内存不足,甚至更好,我想设置一个它可以使用多少内存的阈值 class MyClass() { public void myMethod() { for(int i=0; i<10000000; i++) { // Allocate some memory, may be several collections } } } cl
class MyClass()
{
public void myMethod()
{
for(int i=0; i<10000000; i++)
{
// Allocate some memory, may be several collections
}
}
}
class MyClassTest
{
@Test
public void myMethod_makeSureMemoryFootprintIsNotBiggerThanMax()
{
new MyClass().myMethod();
// How do I measure amount of memory it may try to allocate?
}
}
class MyClass()
{
公共方法()
{
对于(int i=0;i来测量当前内存使用情况:
Runtime.getRuntime().freemory()
,
Runtime.getRuntime().totalMemory()
下面是一个很好的例子:
但是这个测量并不精确,但它可以给你很多信息。
另一个问题是不可预测的GC
。这里有一个来自Netty的例子,它做了类似的事情:。Guava's也有一个基于大小的逐出。你可以查看这些源并复制它们正在做的事情。特别是,下面是Netty的情况。本质上,你要估计你在方法中生成的对象的大小d和保持计数
获取总体内存信息(如可用/使用的堆数量)将帮助您决定分配给方法的内存使用量,而不是跟踪单个方法调用使用的内存数量
话虽如此,您合法地需要它是非常罕见的。在大多数情况下,通过限制给定点上可以存在的对象数量(例如,通过使用有界队列)来限制内存使用量已经足够好了,而且实现起来要简单得多。我可以想出几个选项:
- 通过微基准(即)了解您的方法需要多少内存
- 基于启发式估计构建分配策略。有几种开源解决方案实现类大小估计,也就是说,一种更简单的方法是利用缓存释放很少使用的对象(即Guava的缓存)。正如@EnnoShioji所提到的,Guava的缓存具有基于内存的逐出策略
你也可以编写自己的基准测试来计算内存
运行一个线程
创建一个新数组来存储要分配的对象。这样在GC运行期间不会收集这些对象
System.gc()
,memoryBefore=runtime.totalMemory()-runtime.freemory()
分配对象。将它们放入数组中
System.gc()
,memoryAfter=runtime.totalMemory()-runtime.freemory()
这是我在my中使用的一种技术,能够以字节精度测量内存分配。您可以使用探查器(例如JProfiler)按类查看内存使用情况。或者,如Areo所述,只需打印内存使用情况:
Runtime runtime = Runtime.getRuntime();
long usedMemoryBefore = runtime.totalMemory() - runtime.freeMemory();
System.out.println("Used Memory before" + usedMemoryBefore);
// working code here
long usedMemoryAfter = runtime.totalMemory() - runtime.freeMemory();
System.out.println("Memory increased:" + (usedMemoryAfter-usedMemoryBefore));
这个问题有点棘手,因为Java可以在处理过程中分配大量短期对象,这些对象随后将在垃圾收集过程中被收集。在公认的答案中,我们不能确定垃圾收集是否在任何给定时间运行。即使我们引入循环结构,使用multipleSystem.gc()
调用时,垃圾收集可能会在方法调用之间运行
更好的方法是使用中建议的一些变体,其中会触发System.gc()
,但我们也会等待报告的gc计数增加:
long getGcCount() {
long sum = 0;
for (GarbageCollectorMXBean b : ManagementFactory.getGarbageCollectorMXBeans()) {
long count = b.getCollectionCount();
if (count != -1) { sum += count; }
}
return sum;
}
long getReallyUsedMemory() {
long before = getGcCount();
System.gc();
while (getGcCount() == before);
return getCurrentlyAllocatedMemory();
}
long getCurrentlyAllocatedMemory() {
final Runtime runtime = Runtime.getRuntime();
return (runtime.totalMemory() - runtime.freeMemory()) / (1024 * 1024);
}
这仍然只给出了代码在给定时间实际分配的内存的近似值,但该值通常更接近人们通常感兴趣的值。下面是一个在单独线程中运行内存使用情况的示例代码。由于进程运行时可以随时触发GC,因此这将记录内存每秒使用一次,并报告已使用的最大内存
runnable
是需要测量的实际进程,而runTimeSecs
是进程运行的预期时间。这是为了确保线程计算内存不会在实际进程之前终止
public void recordMemoryUsage(Runnable runnable, int runTimeSecs) {
try {
CompletableFuture<Void> mainProcessFuture = CompletableFuture.runAsync(runnable);
CompletableFuture<Void> memUsageFuture = CompletableFuture.runAsync(() -> {
long mem = 0;
for (int cnt = 0; cnt < runTimeSecs; cnt++) {
long memUsed = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
mem = memUsed > mem ? memUsed : mem;
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
;
System.out.println("Max memory used (gb): " + mem/1000000000D);
});
CompletableFuture<Void> allOf = CompletableFuture.allOf(mainProcessFuture, memUsageFuture);
allOf.get();
} catch (Exception e) {
e.printStackTrace();
}
}
public void recordMemoryUsage(Runnable Runnable,int runTimeSecs){
试一试{
CompletableFuture mainProcessFuture=CompletableFuture.runAsync(可运行);
CompletableFuture memUsageFuture=CompletableFuture.runAsync(()->{
长记忆=0;
对于(int cnt=0;cntmem?memUsed:mem;
试一试{
时间单位。秒。睡眠(1);
}捕捉(中断异常e){
e、 printStackTrace();
}
}
;
System.out.println(“使用的最大内存(gb):”+mem/1000000000D);
});
CompletableFuture allOf=CompletableFuture.allOf(mainProcessFuture,memUsageFuture);
allOf.get();
}捕获(例外e){
e、 printStackTrace();
}
}
估计内存使用量的最简单方法是使用运行时中的方法。
我建议不要依赖它,而只用于近似估计。理想情况下,您应该只记录这些信息并自己分析,而不使用它来自动化测试或代码。
它可能不太可靠,但在单元测试这样的封闭环境中,它可能会让您的估计更接近现实。
特别是,不能保证在调用System.gc()
垃圾收集器之后,它会在我们期望的时候运行(这只是对gc的一个建议),这里描述的freemory
方法存在精度限制:可能还有更多的警告
解决方案:
private static final long BYTE_TO_MB_CONVERSION_VALUE=1024*1024;
@试验
公共无效内存测试(){
long MemorySageBeforLoadingData=getCurrentlyUsedMemory();
log.debug(“加载某些数据之前使用的内存:“+memoryUsageBeforeLoadingData+”MB”);
列出一些
private static final long BYTE_TO_MB_CONVERSION_VALUE = 1024 * 1024;
@Test
public void memoryUsageTest() {
long memoryUsageBeforeLoadingData = getCurrentlyUsedMemory();
log.debug("Used memory before loading some data: " + memoryUsageBeforeLoadingData + " MB");
List<SomeObject> somethingBigLoadedFromDatabase = loadSomethingBigFromDatabase();
long memoryUsageAfterLoadingData = getCurrentlyUsedMemory();
log.debug("Used memory after loading some data: " + memoryUsageAfterLoadingData + " MB");
log.debug("Difference: " + (memoryUsageAfterLoadingData - memoryUsageBeforeLoadingData) + " MB");
someOperations(somethingBigLoadedFromDatabase);
}
private long getCurrentlyUsedMemory() {
System.gc();
return (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / BYTE_TO_MB_CONVERSION_VALUE;
}