线程占用的内存

16

我需要监控应用程序所创建的线程消耗的内存量。如果某个贪婪的线程消耗了太多的内存,那么就需要采取纠正措施。我参考了How much memory does my java thread take?中的一些建议,其中一个建议是在ThreadMXBean中使用getThreadAllocatedBytes。我使用下面的作业来尝试使用getThreadAllocatedBytes

List<Long> primes = new ArrayList<Long>();
long i = 0;
while (true) {
            primes.add(++i);
            if ((i % 10) == 0) {
                primes.clear();
                System.runFinalization();
                System.gc();
            }
        }

我在四个线程上运行这个任务相当长的时间。尽管该任务不会持续累积内存,但getThreadAllocatedBytes返回的值却不断增加,甚至从未下降。这意味着getThreadAllocatedBytes并不返回线程在堆上实际使用的内存量,而是返回自线程启动以来为其分配的堆上总内存量。我的平台细节如下:

Linux PG85213.egi.ericsson.com 3.5.0-030500-generic #201207211835 SMP Sat Jul 21 22:35:55 UTC 2012 x86_64 x86_64 x86_64 GNU/Linux java version "1.7.0_45"
Java(TM) SE Runtime Environment (build 1.7.0_45-b18) Java HotSpot(TM) 64-Bit Server VM (build 24.45-b08, mixed mode)

上述行为是否是getThreadAllocatedBytes的期望行为? 如果是,那么是否没有办法找到线程有效使用的堆上内存。

参考完整程序如下:

package workbench;

import java.lang.management.ManagementFactory;
import com.sun.management.ThreadMXBean;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;

public class AnotherWorkBench {

private static final CountDownLatch latch = new CountDownLatch(4);
static final List<Long> threadIds = Collections.synchronizedList(new ArrayList<Long>());

private void dummyJob() {
    List<Long> primes = new ArrayList<Long>();
    long i = 0;
    while (true) {
        primes.add(++i);
        if ((i % 10) == 0) {
            primes.clear();
            //introduce sleep to prevent process hogging 
            try {
                Thread.currentThread().sleep(2000);
            } catch (InterruptedException ex) {
                Logger.getLogger(AnotherWorkBench.class.getName()).log(Level.SEVERE, null, ex);
            }
            System.runFinalization();
            System.gc();
        }
    }
}

private void runDummyJobs() {

    Runnable dummyJob = new Runnable() {
        @Override
        public void run() {
            threadIds.add(Thread.currentThread().getId());
            latch.countDown();
            dummyJob();
        }
    };

    Runnable memoryMonitorJob = new Runnable() {
        @Override
        public void run() {

            System.out.println(Thread.currentThread().getName() + " : Monitor thread started");
            ThreadMXBean threadMxBean = (ThreadMXBean) ManagementFactory.getThreadMXBean();
            threadMxBean.setThreadAllocatedMemoryEnabled(true);

            while (true) {
                for (Long threadId : threadIds) {
                    System.out.println(Thread.currentThread().getName() + " : Thread ID : " + threadId + " : memory = " + threadMxBean.getThreadAllocatedBytes(threadId) + " bytes");
                }

                //wait between subsequent scans
                try {
                    System.out.println(Thread.currentThread().getName() + " : secondary sleep");
                    Thread.currentThread().sleep(5000);
                    System.out.println(Thread.currentThread().getName() + " : out of secondary sleep");
                } catch (InterruptedException ex) {
                    Logger.getLogger(WorkBench.class.getName()).log(Level.SEVERE, null, ex);
                }
            }


        }
    };

    Executors.newSingleThreadExecutor().submit(dummyJob);
    Executors.newSingleThreadExecutor().submit(dummyJob);
    Executors.newSingleThreadExecutor().submit(dummyJob);
    Executors.newSingleThreadExecutor().submit(dummyJob);

    try {
        latch.await();
    } catch (InterruptedException ex) {
        Logger.getLogger(AnotherWorkBench.class.getName()).log(Level.SEVERE, null, ex);
    }
    Executors.newSingleThreadExecutor().submit(memoryMonitorJob);
}

/**
 * @param args the command line arguments
 */
public static void main(String[] args) {
    new AnotherWorkBench().runDummyJobs();
}
}

2
请注意,System.gc()并不保证GC一定会运行。特别是在您的情况下,System.gc()可能会以亚毫秒间隔调用,虚拟机可能会决定将GC运行推迟到某个任意时间;通常是当可用内存按某种度量变得较低时。 - JimmyB
请问能否提供一个更完整的示例呢?我想在本地重复您的实验。 - Chris K
1
SAP JVM(https://tools.hana.ondemand.com/#cloud)似乎正好支持此功能。我从未使用过这个虚拟机,只是读到它被支持了。 - C-Otto
2个回答

14
据我所知,没有可靠的方法在运行时完成此操作。正如源问题所指出的那样,堆是一种共享资源,因此单个线程的堆大小没有意义,因为它会与其他线程的对象引用重叠。

话虽如此,当我想要知道单个线程的“保留”大小时,是一个不同但类似于您所询问的度量时,我会通过进行堆转储,然后使用MAT(http://www.eclipse.org/mat/)来完成。

我知道有人使用Java代理来监视对象分配,然后使用弱引用来监视它何时被GC回收。但是这样做的性能影响非常大。非常高。

您最好在运行时使用启发式算法和单元测试以确保内存保持在范围内。例如,您可以使用JMX监视堆大小,当看到老年代增长时,您可以发出警报。使用getThreadAllocatedBytes来计算分配速率也可能很有用。

好的运行时监控工具:appdynamicsnewrelicvisualvmyourkit

对于离线内存分析,matjclarity非常好。

一个非常有用的工具可以帮助我们发现是否存在泄漏,或者至少运行是否符合预期,就是打印每个类当前在堆上的实例数量计数:jcmd <pid> GC.class_histogram


我主要关注应用程序上下文中的运行时监控。因此,在我们的情况下,从外部对应用程序进行分析将没有太大帮助。但是,我想检查一下分析器是否提供每个线程的堆内存消耗量。除了VisualVM以外,所有分析器都是商业许可的。因此,我只检查了VisualVM。它不会给出每个线程使用的堆内存量。我不确定是否有其他分析器提供此功能。根据迄今为止的回复和所有搜索结果,似乎没有办法获取每个线程消耗的堆内存量。 - Sarvesh
@SarveswaranMeenakshiSundaram,没错,没有确定的方法来做这件事。正如一些回复所指出的那样,“每个线程使用的堆内存”实际上是什么意思呢?堆是一个共享资源,任何单个对象都可以被多个线程访问。这就是为什么我提到了“保留”大小,这是一种可以在离线或运行时计算的度量标准。然而,在运行时进行计算会产生成本,并且根据您的操作方式可能不准确。离线是通常的做法,像MAT这样的免费工具支持这种方式。以上是两种方法的详细信息。 - Chris K

4

Java VisualVM 可以用于“监视本地应用程序并查看 Java 虚拟机 (JVM) 中的内存堆、线程活动和加载的类的实时高级数据。监视应用程序对性能影响小,可长时间使用。”

另请参阅如何监视 Java 内存使用情况?以获取其他可能性。


我尝试了VisualVM,其中有一列显示每个线程分配的内存量。这与ThreadMXBean中的getThreadAllocatedBytes返回的值相匹配。即使是VisualVM也无法提供每个线程实际使用的堆上内存!这意味着在JVM层面上没有支持或黑科技可以实时监视每个线程使用的内存量。 - Sarvesh
@gumuruh 没有想法。 这个问题与Netbeans无关。 - DavidPostill

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接