常见的JVM调优工具有哪些

典型回答 JVM工具主要用来监控JVM的,这类工具主要分为两大类,第一类是JVM自带的,比如jstat、jmap等,还有依赖是第三方的,如VisualVM等。 这些工具的用途都不太一样,监控的方向也不一样。下面是简单的介绍: jps:JDK 1.5提供的一个显示当前所有java进程pid的命令,简单实用,非常适合在linux/unix平台上简单察看当前java进程的一些简单情况。 ✅jps命令的作用是什么? jstack:Java虚拟机自带的命令行工具,主要用于生成线程的堆栈信息,用于诊断死锁及线程阻塞等问题。 ✅jstack命令的作用是什么? jmap:Java虚拟机自带的命令行工具,可以生成JVM中堆内存的Dump文件,用于分析堆内存的使用情况。排查内存泄漏等问题。 ✅jmap命令的作用是什么? jstat:Java虚拟机自带的命令行工具,主要用来监控JVM中的类加载、GC、线程等信息。 ✅jstat命令的作用是什么? jhat:使用jmap可以生成Java堆的Dump文件,生成dump文件之后就可以用jhat命令,将dump文件转成html的形式,然后通过http访问可以查看堆情况。 ✅jhat有什么用,如何用他分析堆dump JConsole:一个基于JMX(Java Management Extensions)的监控工具,可以用来监视JVM中的内存、线程、GC等信息,并可以执行一些诊断和故障排除任务。 VisualVM:一个基于NetBeans平台的可视化工具,可以监视本地和远程JVM进程的性能和资源使用情况,包括CPU、内存、线程、GC等信息,并可以进行故障排除和性能分析。 YourKit:一个商业的JVM分析工具,可以进行内存、CPU、线程等方面的分析,提供了一些高级功能如内存泄漏检测、代码热替换等。 JProfiler:一个商业的JVM分析工具,可以监视JVM中的内存、线程、GC等信息,并提供一些高级功能如代码分析、内存泄漏检测等。 Arthas:Arthas 是Alibaba开源的Java诊断工具,非常强大,非常推荐,

March 22, 2026 · 1 min · santu

虚拟机中的堆一定是线程共享的吗?

并不一定哦! 为了保证对象的内存分配过程中的线程安全性,HotSpot虚拟机提供了一种叫做TLAB(Thread Local Allocation Buffer)的技术。 在线程初始化时,虚拟机会为每个线程分配一块TLAB空间,只给当前线程使用,当需要分配内存时,就在自己的空间上分配,这样就不存在竞争的情况,可以大大提升分配效率。 所以,“堆是线程共享的内存区域”这句话并不完全正确,因为TLAB是堆内存的一部分,他在读取上确实是线程共享的,但是在内存分分配上,是线程独享的。 TLAB的空间其实并不大,所以大对象还是可能需要在堆内存中直接分配。那么,对象的内存分配步骤就是先尝试TLAB分配,空间不足之后,再判断是否应该直接进入老年代,然后再确定是再eden分配还是在老年代分配。

March 22, 2026 · 1 min · santu

哪些语言有GC机制

很多编程语言都有垃圾回收(GC)机制,其中包括: Java C# Python Ruby JavaScript Kotlin Swift Go R Lua 这些语言的 GC 机制都是在运行时自动管理内存,以防止内存泄漏和数据丢失。

March 22, 2026 · 1 min · santu

字符串常量池是如何实现的?

典型回答 字符串常量池(String Constant Pool)是Java中一块特殊的内存区域,用于存储字符串常量。 当程序中出现字符串常量时,Java编译器会将其放入字符串常量池中。字符串常量是不可变的,因此可以共享。如果字符串常量池中已存在相同内容的字符串,编译器会直接引用已存在的字符串常量,而不会创建新的对象。 在HotSpot虚拟机中: 在JDK 1.6及之前的版本,字符串常量池通常被实现为方法区的一部分,即永久代(Permanent Generation),用于存储类信息、常量池、静态变量、即时编译器编译后的代码等数据。 从JDK 1.7开始,字符串常量池的实现方式发生了重大改变。字符串常量池不再位于永久代,而是直接存放在堆(Heap)中,与其他对象共享堆内存。 之所以要挪到堆内存中,主要原因是因为永久代的 GC 回收效率太低,只有在FullGC的时候才会被执行回收。但是Java中往往会有很多字符串也是朝生夕死的,将字符串常量池放到堆中,能够更高效及时地回收字符串内存 扩展知识 字符串常量从哪来的? 字符串常量池中的常量有以下几个来源: 1、字面量常量。 在代码中直接使用双引号括起来的字符串字面值(如String s = "Hollis")会被认为是常量,并且会在编译后进入class文件的常量池,并且在运行阶段,进入字符串常量池。这是最常见的字符串常量来源。 2、intern()方法 String类提供了一个intern()方法,用于将字符串对象手动添加到字符串常量池中。调用intern()方法时,如果字符串常量池中已经存在相同内容的字符串,将会返回常量池中的引用;如果不存在,则会在常量池中创建新的字符串

March 22, 2026 · 1 min · santu

JVM为什么要把堆和栈区分出来呢?

典型回答 堆和栈是JVM中的两个区域,想要知道为什么要搞两个区域,其实只需要搞清楚他们的特点和用途之间区别是什么就行了。 堆是存储对象的区域,堆的大小可以根据需要随时调整,堆的管理有垃圾回收器进行,堆内存是多个线程之间共享的。 栈是每个线程独享的一块区域,用于方法调用、局部变量等的存储。 把这两者区分开的好处有以下几个: 首先因为他们的存储内容不同,可以分开管理。堆内存可以用垃圾回收器管理,栈内存可以靠编译器和虚拟机执行完成。 其次,可以做到不互相影响。独立开两个不同的区域,可以做到不互相影响。堆内存溢出不会影响到栈。栈溢出也不会影响到堆。 还有就是可以做到数据隔离,因为有了栈,就可以把一些线程独享的局部变量等内容放到栈上,可以做到更好的隔离。而共享的一些数据就可以放到堆上做统一管理。 提升各自性能。栈上分配的效率很高,可以适合分配局部变量等,可以非常的高效。而堆上的内存分配及回收都会相对复杂。这样区分开可以做各自的优化,提升整体效率。

March 22, 2026 · 1 min · santu

FullGC多久一次算正常?

典型回答 很多人会在面试的时候,提到频繁FullGC,那有的面试官就会问,你认为多久一次FullGC算是频繁呢?多久一次算是正常呢? 其实,Full GC的频率取决于多个因素,包括应用程序的性质、堆的大小、内存分配和释放的模式等。正常情况下,Full GC应该是相对较少发生的,因为频繁的Full GC会导致应用程序的性能下降和响应时间延长。 如果一定要给一个指标,那么我可以给一个经验值,拿我们这面一个非常核心的应用来说: 这个应用日常的QPS在5000以上,线上一共有100台左右的机器。 整个集群,也就是100多台4C8G的机器总体的数据是: 平常情况,FullGC次数,一周不超过一次。 业务高峰期,FullGC次数,2小时一次。 FullGC耗时,400-700ms,不超过1秒钟。 YoungGC次数,100+/分钟,YoungGC耗时,20ms左右 堆内存利用率维持在50%以下。 以上,供大家参考,一般来说,日常情况,FullGC不应该超过一周一次的这个频率。 扩展知识 ✅4C8G的机器,各项系统指标,什么范围算是正常?

March 22, 2026 · 1 min · santu

运行时常量池和字符串常量池的关系是什么?

典型回答 运行时常量池,是runtime constant pool,是Java虚拟机规范中定义的一块逻辑区域,它是方法区的一部分,规范中说明了,它是用于存储常量、符号引用和一些编译期已知的常量数据。 因为Java虚拟机规范并没有规定要如何实现方法区,所以在不同的HotSpot的JDK版本中,方法区所处的位置是不同的,所以运行时常量池所处的位置也是不一样的。 ✅什么是方法区?是如何实现的? Java虚拟机规范中还说,字符串字面量不应该重复的存储在运行时常量池中,应该做到可以复用。 但是,以上都是规范,并不是具体实现,而字符串常量池这个东西,就是HotSpot的一种具体实现。 HotSpot为了复用字符串对象,定义了一个字符串常量池,它是作为字符串对象的缓存池,用于存储所有字面量形式创建的字符串。 很多人认为字符串常量池和运行时常量池没啥关系,因为他们所处的位置不一样,尤其是在JDK 1.7之后,字符串常量池在堆上,而运行时常量池随着方法区而处于永久代或者元空间。 但是,根据虚拟机规范,字符串常量,需要放在运行时常量池中。所以,我认为字符串池就是运行时常量池的一个逻辑子区域。即字符串池是运行时常量池的分池!

March 22, 2026 · 1 min · santu

内存泄漏和内存溢出的区别是什么?

典型回答 **内存泄漏指的是程序中分配的内存在不再需要时没有被正确释放或回收的情况。**这会导致程序持续占用内存,随着时间的推移,可用内存逐渐减少,最终可能导致程序性能下降或崩溃。 内存泄漏通常发生在程序中的对象或数据结构被创建后,但没有适时地释放对它们的引用,从而阻止垃圾回收器将它们清理出内存。 常见的内存泄漏情况包括未关闭的文件或数据库连接、未释放的资源对象(如打开的文件句柄或网络连接)、长时间被引用的集合类(List、Map)等。 内存溢出指的是程序试图分配超过其可用内存的内存空间的情况。这通常会直接导致Java程序崩溃。 常见的内存溢出情况包括栈溢出和堆溢出。我们常说的内存溢出如果没有特别说明都是指堆溢出,即OutOfMemory 在Java中,当程序动态分配内存(例如使用new操作符在堆中创建对象)时,没有足够的可用内存时,就会发生OOM,即OutOfMemoryError。 一般来说,内存泄漏是会导致内存溢出的,因为内存泄漏会导致部分内存一直无法被回收,久而久之就会没有内存可以分配,就会导致内存溢出。 扩展知识 ✅OOM问题排查过程

March 22, 2026 · 1 min · santu

破坏双亲委派之后,能重写String类吗?

典型回答 Java通过双亲委派模型保证了java核心包中的类不会被破坏,但破坏双亲委派能够脱离加载范围的限制,增强第三方组件的能力。 ✅什么是双亲委派?如何破坏? 但是我们虽然可以通过破坏双亲委派屏蔽Bootstrap ClassLoader,但无法重写java.包下的类,如java.lang.String。 我们知道,要破坏双亲委派模型是需要extends ClassLoader并重写其中的loadClass()和findClass()方法。 之所以无法替换java.包的类,主要原因是即使我们破坏双亲委派模型,依然需要调用父类中(java.lang.ClassLoader.java)的defineClass()方法来把字节流转换为一个JVM识别的class。而defineClass()方法中通过preDefineClass()方法限制了类全限定名不能以java.开头。 如下代码所示: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 //将字节流转换成jvm可识别的java类 protected final Class<?> defineClass(String name, byte[] b, int off, int len, ProtectionDomain protectionDomain) throws ClassFormatError { protectionDomain = preDefineClass(name, protectionDomain);//检查类全限定名是否有效 String source = defineClassSourceLocation(protectionDomain); Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);//调用本地方法,执行字节流转JVM类的逻辑。 postDefineClass(c, protectionDomain); return c; } //检查类名的有效性 private ProtectionDomain preDefineClass(String name, ProtectionDomain pd) { if (!checkName(name)) throw new NoClassDefFoundError("IllegalName: " + name); if ((name != null) && name.startsWith("java.")) { //禁止替换以java.开头的类文件 throw new SecurityException ("Prohibited package name: " + name.substring(0, name.lastIndexOf('.'))); } if (pd == null) { pd = defaultDomain; } if (name != null) checkCerts(name, pd.getCodeSource()); return pd; } 注意,defineClassX三兄弟是三个本地方法,用于不同参数长度的方法调用。 ...

March 22, 2026 · 2 min · santu

Java发生了OOM一定会导致JVM 退出吗?

典型回答 我们在Java中遇到的比较严重的问题应该就是OutOfMemoryError,StackOverflowError等这些问题了,那么,如果遇到这些问题,JVM一定会退出吗? 我们知道,JVM是一个操作系统的进程,而在Linux和其他类Unix操作系统中,当一个进程在执行非法内存访问时,如访问未分配给它的内存或者访问超出其允许范围的内存时,操作系统会向该程序发送SIGSEGV信号(“段错误”(Segmentation Fault)),若进程没有注册信号处理函数会直接退出,并产生Segment Fault错误提示。 而我们熟知的OutOfMemoryError,StackOverflowError就是Segment Fault的具体情况,不过,JVM被设计成能够容忍和隔离单个线程出现问题,当一个线程崩溃时,JVM会尝试将问题限定在该线程内,而不会影响其他线程或整个应用程序。 也就是说,即使我们的线程执行过程中,发生了OutOfMemoryError,StackOverflowError等这些问题了,也并不代表JVM就一定要立即退出或者崩溃。 ✅什么情况会导致JVM退出? 主要是因为,OutOfMemoryError,StackOverflowError等这些我们看到的ERROR,已经是JVM在注册了SIGSEGV信号处理函数之后,经过自己的处理之后抛给我们的错误了。(这部分源码在文末) 而OutOfMemoryError,StackOverflowError等这些错误抛给我们之后,其实都是可以被catch的,如果被catch掉之后,程序还是可以正常执行,而不会崩溃退出的。 所以说,Java中的所有线程的崩溃,包括主线程、子线程,并不是说一定就会导致JVM直接崩溃的。 不过,有些JVM参数配置可能会在遇到OOM时导致JVM终止。例如,-XX:OnOutOfMemoryError="; " 参数允许用户指定在遇到OOM错误时要执行的命令,这些命令可以包括终止JVM的命令。 如:<u>XX:OnOutOfMemoryError="kill -9 %p"</u> 扩展知识 Linux为何会对访问内存错误发出异常信号? Linux通过MMU(内存管理单元)将物理内存转换为虚拟内存,确保每个进程能看到的地址空间都是相同的,并且是自己进程独享的。 32位下每个进程可访问从0到4G内存(用户空间3G,内核1G)。但实际上物理内存是所有进程共享的,所以内核需要有能力防止进程随意破坏不属于自己的内存空间。 使用C语言执行如下代码: 1 2 3 4 5 int main(int argc,char **argv){ int *p =(int *)0xC0000FEE; *p = 1024; return 0; } 以上代码由于非法向进程的内核地址空间0xC0000FEE处写入数据1024,操作系统会为该进程发出SIGSEGV的信号,由于我们没有实现信号处理函数,所以进程会默认被系统杀死,并向控制台输出Segment Fault错误。 进程、主线程、子线程的关系 在Linux下通过fork函数创建进程,fork创建进程时会将调用者(父进程)的页表、文件系统、打开文件句柄等信息复制一份,这是一个成本较大的操作,所以引入线程。 Linux的线程也是通过fork函数实现,其入参与进程略有不同(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND),这样使得所有子线程与进程共享页表、文件系统、打开句柄等信息,降低fork系统调用的成本。 进程和线程在Linux内核中都被抽象为一个task_struct的结构体,其中包括了页表、栈信息、文件描述符等很多信息,其中tgid保存了线程组id,如果tgid为pid则是所谓的主线程,而所有子线程tgid就是子线程的pid。本质上主线程和子线程没有任何区别(都是进程fork出来的,共享的信息都一样)。 我们看到线程的fork参数中有一个CLONE_SIGHAND,代表线程与进程共享信号处理函数。所以JVM内部在启动时注册的信号处理函数,会被所有线程复用。JVM注册的信号处理函数会抛出各种Error和Exception,但不会结束JVM进程。这样子线程崩溃了,也不会影响JVM中的其他线程。 JVM都处理了哪些异常信号? 除了上面提到的SIGSEGV信号,JVM还注册了如下信号处理函数: 信号值 信号名称 作用 11 SIGSEGV 试图访问未分配给自己的内存,或试图往没有写权限的内存地址写数据 13 SIGPIPE 管道破裂。这个信号通常在进程间通信产生,比如采用 FIFO (管道) 通信的两个进程,读管道没打开或者意外终止就往管道写,写进程会收到 SIGPIPE 信号。此外用 Socket 通信的两个进程,写进程在写 Socket 的时候,读进程已经终止。 7 SIGBUS 非法地址,包括内存地址对齐 (alignment) 出错。比如访问一个四个字长的整数,但其地址不是 4 的倍数。它与 SIGSEGV 的区别在于后者是由于对合法存储地址的非法访问触发的 (如访问不属于自己存储空间或只读存储空间)。 4 SIGILL 执行了非法指令。通常是因为可执行文件本身出现错误,或者试图执行数据段. 堆栈溢出时也有可能产生这个信号。 8 SIGFPE 在发生致命的算术运算错误时发出。不仅包括浮点运算错误,还包括溢出及除数为 0 等其它所有的算术的错误。 25 SIGXFSZ 当进程企图扩大文件以至于超过文件大小资源限制。 JVM拦截这些信号的目的有三个: ...

March 22, 2026 · 3 min · santu

留言给博主