Java 工具(jmap,jstack)在linux上的源码分析(三)执行的线程vm thread

在前面的博客中(http://blog.youkuaiyun.com/raintungli/article/details/7034005)所提到的信号转发线程,Attach Listener 线程都只是操作socket文件,并没有去执行比如stack 分析,或者heap的分析,真正的工作线程其实是vm thread.

(一)启动vm thread

jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) {
...
  // Create the VMThread
  { TraceTime timer("Start VMThread", TraceStartupTime);
    VMThread::create();
    Thread* vmthread = VMThread::vm_thread();

    if (!os::create_thread(vmthread, os::vm_thread))
      vm_exit_during_initialization("Cannot create VM thread. Out of system resources.");

    // Wait for the VM thread to become ready, and VMThread::run to initialize
    // Monitors can have spurious returns, must always check another state flag
    {
      MutexLocker ml(Notify_lock);
      os::start_thread(vmthread);
      while (vmthread->active_handles() == NULL) {
        Notify_lock->wait();
      }
    }
  }
...


}

我们可以看到,在thread.cpp里启动了线程vm thread,在这里我们同时也稍微的略带的讲一下jvm在linux里如何启动线程的。

通常在linux中启动线程,是调用

int pthread_create((pthread_t *__thread, __const pthread_attr_t *__attr,void *(*__start_routine) (void *), void *__arg));


而在java里却增加了os:create_thread --初始化线程 和os:start_thread--启动线程

我们去看一下jvm里面是如何在linux里做到的

在os_linux.cpp中来看create_thread的方法


bool os::create_thread(Thread* thread, ThreadType thr_type, size_t stack_size) {
....
int ret = pthread_create(&tid, &attr, (void* (*)(void*)) java_start, thread);
....
}

继续看java_start方法

static void *java_start(Thread *thread) {
....
  // handshaking with parent thread
  {
    MutexLockerEx ml(sync, Mutex::_no_safepoint_check_flag);

    // notify parent thread
    osthread->set_state(INITIALIZED);
    sync->notify_all();

    // wait until os::start_thread()
    while (osthread->get_state() == INITIALIZED) {
      sync->wait(Mutex::_no_safepoint_check_flag);
    }
  }

  // call one more level start routine
  thread->run();

  return 0;
}

首先jvm先设置了当前线程的状态是Initialized, 然后notify所有的线程,

 while (osthread->get_state() == INITIALIZED) {
      sync->wait(Mutex::_no_safepoint_check_flag);
    }

不停的查看线程的当前状态是不是Initialized, 如果是的话,调用了sync->wait()的方法等待。

来看os:start_thread的方法 os.cpp

void os::start_thread(Thread* thread) {
  // guard suspend/resume
  MutexLockerEx ml(thread->SR_lock(), Mutex::_no_safepoint_check_flag);
  OSThread* osthread = thread->osthread();
  osthread->set_state(RUNNABLE);
  pd_start_thread(thread);
}


这时候设置了线程的状态为runnable,但没有notify线程

在 pd_start_thread(thread)中, os_linux.cpp中

void os::pd_start_thread(Thread* thread) {
  OSThread * osthread = thread->osthread();
  assert(osthread->get_state() != INITIALIZED, "just checking");
  Monitor* sync_with_child = osthread->startThread_lock();
  MutexLockerEx ml(sync_with_child, Mutex::_no_safepoint_check_flag);
  sync_with_child->notify();
}

这时候我们看到了notify 线程的操作

也就是这时候notify了线程,因为这时候的线程的状态是RUNNABLE, 方法java_start继续往下执行,于是调用了thread->run()的方法

对于线程vm Thread 也就是调用了vmthread::run方法

vmThread.cpp

void VMThread::run() {
...
this->loop();
...
}

调用了loop函数,处理了VM_Operation 的queue 关于queue的级别和优先级处理算法:可以参考 另一篇博客:http://blog.youkuaiyun.com/raintungli/article/details/6553337

 

(二)Jstack 运行在vm thread里的VM_Operation

jstack 处理也就是在前面博客所提到的attach Listener 线程所做的 operation

static jint thread_dump(AttachOperation* op, outputStream* out) {
  bool print_concurrent_locks = false;
  if (op->arg(0) != NULL && strcmp(op->arg(0), "-l") == 0) {
    print_concurrent_locks = true;
  }

  // thread stacks
  VM_PrintThreads op1(out, print_concurrent_locks);
  VMThread::execute(&op1);

  // JNI global handles
  VM_PrintJNI op2(out);
  VMThread::execute(&op2);

  // Deadlock detection
  VM_FindDeadlocks op3(out);
  VMThread::execute(&op3);

  return JNI_OK;
}
简单看一下类VM_PrintThreads 它 继承了VM_Operation
class VM_PrintThreads: public VM_Operation {
 private:
  outputStream* _out;
  bool _print_concurrent_locks;
 public:
  VM_PrintThreads()                                                { _out = tty; _print_concurrent_locks = PrintConcurrentLocks; }
  VM_PrintThreads(outputStream* out, bool print_concurrent_locks)  { _out = out; _print_concurrent_locks = print_concurrent_locks; }
  VMOp_Type type() const                                           {  return VMOp_PrintThreads; }
  void doit();
  bool doit_prologue();
  void doit_epilogue();
};

当调用VMThread::execute()也就是将VM_PrintThreads 放入了_vm_queue中,交给vm thread 处理,对vm thread来说取出queue里的VM_Operation,并且调用doit方法。

在jstack里,attach listener 的线程产生了VM_PrintThreads,VM_PrintJNI,VM_FindDeadlocks 3个operations,交给了vm thread  的线程处理。







<think>嗯,用户问的是Java序部署在服务器上,虚拟内存持续增长的问题。我需要先理清楚可能的原因,然后逐步分析。首先,虚拟内存的概念是什么?Java序在运行时,JVM管理内存,包括堆内存、非堆内存,还有本地内存。虚拟内存持续增长,可能和JVM的内存管理有关,或者操作系统的机制有关。 首先想到的是JVM的堆内存设置。如果堆内存设置得过大,或者有内存泄漏,导致频繁GC,但堆内存可能不会直接导致虚拟内存的持续增长,因为JVM会预先申请堆内存。不过,如果存在内存泄漏,比如堆外内存泄漏,比如使用了NIO的DirectByteBuffer,或者JNI调用本地库导致的内存泄漏,那么这部分属于堆外内存,不会被GC管理,可能导致虚拟内存增长。 其次,JVM的元空间(Metaspace)如果配置不当,比如加载了大量类,可能导致元空间不断增长,从而增加虚拟内存的使用。此外,线程栈的数量也可能影响,每个线程会分配一定的栈空间,如果线程数无限增长,也会导致虚拟内存增加。 另外,操作系统的虚拟内存机制。比如,即使物理内存足够,Linux会倾向于分配虚拟内存,而JVM可能因为使用mmap等方式申请内存,导致虚拟内存统计值上升,但实际可能并未全部使用。这种情况下,虚拟内存高可能不是问题,而是正常现象。 还需要考虑是否有第方库或框架存在内存泄漏。比如,使用了一些本地库,或者网络库,数据库连接池未正确关闭等,导致资源未释放。 用户可能需要检查JVM参数,比如-Xmx, -Xms, -XX:MaxMetaspaceSize等设置是否合理。使用工具监控内存情况,比如jstat查看堆和元空间使用情况,或者使用pmap查看进的内存映射,找出哪部分内存区域在增长。此外,使用Native Memory Tracking(NMT)跟踪JVM的本地内存分配情况,可以更详细地了解内存使用。 可能的解决步骤包括:调整JVM参数限制内存,检查代码是否有堆外内存泄漏,监控线程数量,检查是否有类加载器泄漏导致元空间增长,以及确认操作系统的内存管理机制是否导致虚高。 需要注意的是,有时候虚拟内存的持续增长并不一定意味着问题,特别是当物理内存使用稳定时,可能只是操作系统分配策略的结果。这时候需要结合实际的性能指标,比如应用是否有OOM、响应时间是否变长等来判断是否需要处理。</think>### Java序虚拟内存持续增长的可能原因及解决方案 --- #### 一、问题本质:虚拟内存(Virtual Memory)是什么? 虚拟内存是操作系统为进分配的“逻辑内存”,包含 **物理内存(RAM)** + **交换区(Swap)**。Java序通过JVM管理内存,但JVM自身也会依赖操作系统分配虚拟内存。 --- #### 二、核心原因分析 1. **JVM内存区域泄漏** - **堆内存泄漏** - 现象:`Old Gen`内存持续增长,`Full GC`无法回收。 - 常见原因:静态集合缓存未清理、长生命周期对象持有短生命周期对象。 - 检查工具:`jmap -histo`、`VisualVM`、`Eclipse MAT`。 - **非堆内存泄漏** - **Metaspace(元空间)**:加载过多类(如动态代理、反射生成类未卸载)。 - 检查参数:`-XX:MaxMetaspaceSize`是否限制。 - **Native Memory(本地内存)**:JNI调用、`DirectByteBuffer`未释放。 - 检查工具:`Native Memory Tracking (NMT)`。 2. **线程泄漏** - 现象:线程数无限增长,每个线程占用约`1MB`栈内存(默认`-Xss1m`)。 - 检查工具:`jstack`、`jcmd <pid> Thread.print`。 3. **操作系统行为** - **内存映射文件(mmap)**:如使用`MappedByteBuffer`操作大文件。 - **Glibc内存分配策略**:`glibc`的`ptmalloc`可能缓存内存不释放,导致虚拟内存虚高(但物理内存可能稳定)。 - 检查命令:`pmap -x <pid>`、`cat /proc/<pid>/smaps`。 4. **第方库或框架问题** - 例如:Netty的`PooledByteBufAllocator`未释放、数据库连接池未关闭。 --- #### 、排查步骤 1. **监控基础指标** ```bash # 查看进内存概况 top -p <pid> # 关注RES(物理内存)和VIRT(虚拟内存) # 检查JVM内存分布 jstat -gcutil <pid> 1000 # 每1秒输出堆内存使用率 ``` 2. **检查堆内存** ```bash # 生成堆转储文件 jmap -dump:format=b,file=heap.hprof <pid> # 分析工具:Eclipse MAT、JProfiler ``` 3. **检查Native Memory** ```bash # 启用NMT(需重启JVM) -XX:NativeMemoryTracking=detail # 查看NMT报告 jcmd <pid> VM.native_memory summary ``` 4. **分析线状态** ```bash jstack <pid> > thread_dump.log # 统计线程数 grep 'java.lang.Thread.State' thread_dump.log | wc -l ``` 5. **操作系统级分析** ```bash # 查看进内存映射详情 pmap -x <pid> | sort -nk2 # 检查glibc内存分配 export MALLOC_ARENA_MAX=2 # 限制内存池数量(临时测试) ``` --- #### 四、解决方案 1. **限制内存分配** ```bash # 示例JVM参数 -Xmx4g -Xms4g # 固定堆大小 -XX:MaxMetaspaceSize=256m # 限制元空间 -XX:ReservedCodeCacheSize=128m # 限制代码缓存 ``` 2. **修复代码问题** - 关闭资源:确保`Closeable`对象(如流、连接)在`finally`块中释放。 - 清理缓存:使用`WeakHashMap`或`Caffeine`自动回收缓存。 - 检查JNI代码:确保本地代码释放内存。 3. **调整系统参数** ```bash # 限制线程栈大小 -Xss256k # 限制Glibc内存池(需权衡性能) export MALLOC_ARENA_MAX=2 ``` 4. **升级依赖** - 更新JDK版本(如JDK 11+的ZGC优化内存回收)。 - 检查第方库版本(如Netty 4.1+修复内存泄漏问题)。 --- #### 五、虚拟内存高≠实际故障! - **正常现象**:JVM可能预分配大块虚拟内存(如G1的`-XX:+UnlockExperimentalVMOptions -XX:G1HeapRegionSize=32M`)。 - **关键指标**:若物理内存(RES)稳定且无OOM,虚拟内存增长可能无需干预。 --- 通过上述步骤定位具体原因后,可针对性地优化代码或调整配置。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值