JVM总结

类加载器与类的加载过程

ClassLoader只负责class文件的加载,至于它是否可以运行,则由Execution Engine决定。

加载阶段

通过一个类的全限定名获取定义此类的二进制字节流

将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构

在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口

链接阶段

  • 验证(Verify)

文件格式验证,元数据验证,字节码验证,符号引用验证。

  • 准备(Prepare)
    • 为类变量分配内存并且设置该类变量的默认初始值,即零值。
    • 这里不包含用final修饰的static,因为final在编译的时候就会分配了,准备阶段会显式初始化;
    • 这里不会为实例变量分配初始化 ,类变量会分配在方法区中,而实例变量是会随着对象一起分配到Java堆中。
    • 这里所设置的初始值,通常情况下是数据类型默认的 "零值",如 (0,0L,null,false),比如我们定义了 public static int value = 11,那么value 变量在准备阶段赋的值是0,而不是11,(初始化阶段才会赋值),特殊情况:比如给 value 变量加上了 final 关键字public static final int value=11 ,那么准备阶段 value 的值就被赋值为 11。
  • 解析(Resolve)

符号引用转换为直接引用 的过程。

初始化阶段

  • 初始化阶段就是执行类构造器方法<clinit>()的过程。
  • 此方法不需定义,是javac编译器自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并而来。
  • 构造器方法中指令按语句在源文件中出现的顺序执行。
  • <clinit>()不同于类的构造器。 (关联:构造器是虚拟机视角下的<init>())
  • 若该类具有父类,JVM会保证子类的<clinit>()执行前,父类的<clinit>()已经执行完毕。
  • 虚拟机必须保证一个类的<clinit>()方法在多线程下被同步加锁。
  • 就是相当于给public static int value = 11,赋值11

启动类加载器(引导类加载器,Bootstrap ClassLoader)

  • 这个类加载使用C/C++语言实现的,嵌套在JVM内部。
  • 它用来加载Java的核心库(JAVA_HOME/jre/lib/rt.jar、resources.jar或sun.boot.class.path路径下的内容),用于提供JVM自身需要的类
  • 并不继承自ava.lang.ClassLoader,没有父加载器。
  • 加载扩展类和应用程序类加载器,并指定为他们的父类加载器。
  • 出于安全考虑,Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类

扩展类加载器(Extension ClassLoader)

  • Java语言编写,由sun.misc.Launcher$ExtClassLoader实现。
  • 派生于ClassLoader类
  • 父类加载器为启动类加载器
  • 从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/1ib/ext子目录(扩展目录)下加载类库。如果用户创建的JAR放在此目录下,也会自动由扩展类加载器加载。

应用程序类加载器(系统类加载器,AppClassLoader)

  • java语言编写,由sun.misc.LaunchersAppClassLoader实现

  • 派生于ClassLoader类

  • 父类加载器为扩展类加载器

  • 它负责加载环境变量classpath或系统属性java.class.path指定路径下的类库

  • 该类加载是程序中默认的类加载器,一般来说,Java应用的类都是由它来完成加载

  • 通过ClassLoader#getSystemclassLoader() 方法可以获取到该类加载器

    复制代码
     protected Class<?> loadClass(String name, boolean resolve)   throws ClassNotFoundException
      {
          synchronized (getClassLoadingLock(name)) {
              // First, check if the class has already been loaded
              Class<?> c = findLoadedClass(name);
              if (c == null) {
                  long t0 = System.nanoTime();
                  try {
                      if (parent != null) {
                          c = parent.loadClass(name, false);
                      } else {
                          c = findBootstrapClassOrNull(name);
                      }
                  } catch (ClassNotFoundException e) {
                      // ClassNotFoundException thrown if class not found
                      // from the non-null parent class loader
                  }
    
                  if (c == null) {
                      // If still not found, then invoke findClass in order
                      // to find the class.
                      long t1 = System.nanoTime();
                      c = findClass(name);
                  }
              }
              if (resolve) {
                  resolveClass(c);
              }
              return c;
          }
      }

从源码上看就是先查看这个class是不是被加载过,如果加载过可以获得class,如果没有就看这个加载器有没有父加载器(parent),如果这个值为不为null就嵌套这个方法调用一遍,如果为null,调用findBootstrapClassOrNull去本地方法查看,大概就是查找启动类加载器能不能加载这个class,不能就交给他的下级加载器加载,,如果还是没有加载到,就报notfoundclass的错

优势

  • 避免类的重复加载
  • 保护程序安全,防止核心API被随意篡改
    • 自定义类:java.lang.String
    • 自定义类:java.lang.ShkStart(报错:阻止创建 java.lang开头的类)

沙箱安全机制

自定义String类,但是在加载自定义String类的时候会率先使用引导类加载器加载,而引导类加载器在加载的过程中会先加载jdk自带的文件(rt.jar包中java\lang\String.class),报错信息说没有main方法,就是因为加载的是rt.jar包中的string类。这样可以保证对java核心源代码的保护,这就是沙箱安全机制。

程序计数器

线程私有的和线程同生命周期,占内存小,运行速度快,不会oom,主要是记录用来存储指向下一条指令的地址,也即将要执行的指令代码。由执行引擎读取下一条指令

虚拟机栈

栈是运行时的单位,而堆是存储的单位

栈是线程私有的,会发生栈溢出和堆溢出,栈的基本单位是栈针,栈针的结构有局部变量,操作数栈,动态链接,方法返回地址,附件信息,局部变量是用于存储数据的(八个常量和引用类型),他的单位是槽,一个槽占32位,可以存int,如果是long和dubbo就需要两个槽,操作数栈是存储计算结果数据和一些临时数据,动态链接将这些符号引用转换为调用方法的直接引用,方法返回地址存的是下一条指令的地址,

新生区+养老区+元空间

  • "-Xms"用于表示堆区的起始内存,等价于-XX:InitialHeapSize
  • "-Xmx"则用于表示堆区的最大内存,等价于-XX:MaxHeapSize
  • 初始内存大小:物理电脑内存大小 / 64
  • 最大内存大小:物理电脑内存大小 / 4
  • 默认-XX:NewRatio=2,表示新生代占1,老年代占2,新生代占整个堆的1/3
  • 可以修改-XX:NewRatio=4,表示新生代占1,老年代占4,新生代占整个堆的1/5
  • Eden空间和另外两个survivor空间缺省所占的比例是8:1:1

当然开发人员可以通过选项"-xx:SurvivorRatio"调整这个空间比例。比如-xx:SurvivorRatio=8

为什么有TLAB(Thread Local Allocation Buffer)?

  • 堆区是线程共享区域,任何线程都可以访问到堆区中的共享数据
  • 由于对象实例的创建在JVM中非常频繁,因此在并发环境下从堆区中划分内存空间是线程不安全的
  • 为避免多个线程操作同一地址,需要使用加锁等机制,进而影响分配速度。

针对不同年龄段的对象分配原则如下所示:

  • 优先分配到Eden
  • 大对象直接分配到老年代(尽量避免程序中出现过多的大对象)
  • 长期存活的对象分配到老年代
  • 动态对象年龄判断:如果survivor区中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄。
  • 空间分配担保: -XX:HandlePromotionFailure

元空间

元空间不在虚拟机设置的内存中,而是使用本地内存

元数据区大小可以使用参数 -XX:MetaspaceSize

它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等。

运行时常量池(Runtime Constant Pool)是方法区的一部分。

常量池表(Constant Pool Table)是Class文件的一部分,用于存放编译期生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。

  1. 存储位置:
    • 常量池: 常量池存储在类文件中,即编译时的字节码文件。每个类文件都有一个常量池,其中包含了字面量常量(如字符串、数值等)和符号引用(如类名、方法名、字段名等)。
    • 运行时常量池: 运行时常量池是在类加载时从类文件的常量池中解析出来的,并存储在JVM的内存中,用于在运行时支持方法的执行。
  1. 内容解析:
    • 常量池: 常量池中存储了各种常量项,包括字面量、类引用、字段引用、方法引用等。这些常量项需要在运行时解析,才能被JVM使用。
    • 运行时常量池: 运行时常量池是在类加载时从常量池解析出来的数据结构,它会进行一些额外的处理,使得其中的符号引用能够直接指向内存中的方法、字段等。
  1. 用途:
    • 常量池: 常量池为编译时期提供了一种组织和管理常量和符号引用的机制,同时也在一定程度上支持了代码的优化。
    • 运行时常量池: 运行时常量池用于支持方法的执行和动态链接等。在运行时,通过运行时常量池中的符号引用,可以定位到具体的方法、字段等,实现方法调用和访问等操作。

总的来说,常量池是存储在类文件中的,包含了编译时期的常量和符号引用,而运行时常量池是从类文件的常量池中解析出来的数据结构,用于在运行时支持方法的执行和动态链接。

方法区的垃圾收集主要回收两部分内容:常量池中废弃的常量和不再使用的类型。

7.6.2. StringTable为什么要调整位置?

jdk7中将StringTable放到了堆空间中。因为永久代的回收效率很低,在full gc的时候才会触发。而full gc是老年代的空间不足、永久代不足时才会触发。

这就导致StringTable回收效率不高。而我们开发中会有大量的字符串被创建,回收效率低,导致永久代内存不足。放到堆里,能及时回收内存。

执行引擎

执行引擎属于JVM的下层,里面包括解释器、及时编译器、垃圾回收器

解释器:当当Java虚拟机启动时会根据预定义的规范对字节码采用逐行解释的方式执行,将每条字节码文件中的内容"翻译"为对应平台的本地机器指令执行。

解释器真正意义上所承担的角色就是一个运行时"翻译者",将字节码文件中的内容"翻译"为对应平台的本地机器指令执行。

JIT(Just In Time Compiler)编译器:就是虚拟机将源代码直接编译成和本地机器平台相关的机器语言。

  • 第一种是将源代码编译成字节码文件,然后在运行时通过解释器将字节码文件转为机器码执行
  • 第二种是编译执行(直接编译成机器码,但是要知道不同机器上编译的机器码是不一样,而字节码是可以跨平台的)。现代虚拟机为了提高执行效率,会使用即时编译技术(JIT,Just In Time)将方法编译成机器码后再执行

算法

标记阶段:引用计数算法

方式一:引用计数算法

引用计数算法(Reference Counting)比较简单,对每个对象保存一个整型的引用计数器属性。用于记录对象被引用的情况。

对于一个对象A,只要有任何一个对象引用了A,则A的引用计数器就加1;当引用失效时,引用计数器就减1。只要对象A的引用计数器的值为0,即表示对象A不可能再被使用,可进行回收。

优点:实现简单,垃圾对象便于辨识;判定效率高,回收没有延迟性。

缺点:

  • 它需要单独的字段存储计数器,这样的做法增加了存储空间的开销。
  • 每次赋值都需要更新计数器,伴随着加法和减法操作,这增加了时间开销。
  • 引用计数器有一个严重的问题,即无法处理循环引用的情况。这是一条致命缺陷,导致在Java的垃圾回收器中没有使用这类算法。

标记阶段:可达性分析算法

11.2.2. 标记阶段:可达性分析算法

可达性分析算法(根搜索算法、追踪性垃圾收集)

相对于引用计数算法而言,可达性分析算法不仅同样具备实现简单和执行高效等特点,更重要的是该算法可以有效地解决在引用计数算法中循环引用的问题,防止内存泄漏的发生。

相较于引用计数算法,这里的可达性分析就是Java、C#选择的。这种类型的垃圾收集通常也叫作追踪性垃圾收集(Tracing Garbage Collection)

所谓"GCRoots"根集合就是一组必须活跃的引用。

基本思路
  • 可达性分析算法是以根对象集合(GCRoots)为起始点,按照从上至下的方式搜索被根对象集合所连接的目标对象是否可达。
  • 使用可达性分析算法后,内存中的存活对象都会被根对象集合直接或间接连接着,搜索所走过的路径称为引用链(Reference Chain)
  • 如果目标对象没有任何引用链相连,则是不可达的,就意味着该对象己经死亡,可以标记为垃圾对象。
  • 在可达性分析算法中,只有能够被根对象集合直接或者间接连接的对象才是存活对象。

清除阶段:标记-清除算法

标记-清除算法(Mark-Sweep)

标记-清除算法(Mark-Sweep)是一种非常基础和常见的垃圾收集算法

  • 标记:Collector从引用根节点开始遍历,标记所有被引用的对象。一般是在对象的Header中记录为可达对象。
  • 清除:Collector对堆内存从头到尾进行线性的遍历,如果发现某个对象在其Header中没有标记为可达对象,则将其回收
缺点
  • 标记清除算法的效率不算高
  • 在进行GC的时候,需要停止整个应用程序,用户体验较差
  • 这种方式清理出来的空闲内存是不连续的,产生内碎片,需要维护一个空闲列表
何为清除?

这里所谓的清除并不是真的置空,而是把需要清除的对象地址保存在空闲的地址列表里。下次有新对象需要加载时,判断垃圾的位置空间是否够,如果够,就存放覆盖原有的地址。

清除阶段:复制算法

优点
  • 没有标记和清除过程,实现简单,运行高效
  • 复制过去以后保证空间的连续性,不会出现"碎片"问题。
缺点
  • 此算法的缺点也是很明显的,就是需要两倍的内存空间。
  • 对于G1这种分拆成为大量region的GC,复制而不是移动,意味着GC需要维护region之间对象引用关系,不管是内存占用或者时间开销也不小
标记-压缩(或标记-整理、Mark-Compact)算法
执行过程
  1. 第一阶段和标记清除算法一样,从根节点开始标记所有被引用对象
  2. 第二阶段将所有的存活对象压缩到内存的一端,按顺序排放。
  3. 之后,清理边界外所有的空间。
指针碰撞(Bump the Pointer)

JVM------对象的创建(指针碰撞和空闲对列)_指针碰撞 名字由来-CSDN博客

如果内存空间以规整和有序的方式分布,即已用和未用的内存都各自一边,彼此之间维系着一个记录下一次分配起始点的标记指针,当为新对象分配内存时,只需要通过修改指针的偏移量将新对象分配在第一个空闲内存位置上,这种分配方式就叫做指针碰撞(Bump tHe Pointer)。

优点
  • 消除了标记-清除算法当中,内存区域分散的缺点,我们需要给新对象分配内存时,JVM只需要持有一个内存的起始地址即可。
  • 消除了复制算法当中,内存减半的高额代价。
缺点
  • 从效率上来说,标记-整理算法要低于复制算法。
  • 移动对象的同时,如果对象被其他对象引用,则还需要调整引用的地址
  • 移动过程中,需要全程暂停用户应用程序。即:STW

垃圾回收器

Serial回收器:串行回收

Serial收集器采用复制算法、串行回收和"stop-the-World"机制的方式执行内存回收

SerialOld回收器:串行回收

Serial Old收集器同样也采用了串行回收和"Stop the World"机制,只不过内存回收算法使用的是标记-压缩算法。

ParNew回收器:并行回收

ParNew收集器在年轻代中同样也是采用复制算法、"Stop-the-World"机制。

Parallel回收器:吞吐量优先

Parallel Scavenge收集器同样也采用了复制算法、并行回收和"Stop the World"机制。

  • 和ParNew收集器不同,ParallelScavenge收集器的目标则是达到一个可控制的吞吐量(Throughput),它也被称为吞吐量优先的垃圾收集器。
  • 自适应调节策略也是Parallel Scavenge与ParNew一个重要区别。

Parallel Old收集器

并行,收集器采用了标记-压缩算法,但同样也是基于并行回收和"Stop-the-World"机制。

CMS回收器:低延迟

一种并发的收集器,实现了让垃圾收集线程与用户线程同时工作

CMS的垃圾收集算法采用标记-清除算法,并且也会"Stop-the-World"

  • 初始标记(Initial-Mark)阶段:在这个阶段中,程序中所有的工作线程都将会因为"Stop-the-World"机制而出现短暂的暂停,这个阶段的主要任务仅仅只是标记出GCRoots能直接关联到的对象。一旦标记完成之后就会恢复之前被暂停的所有应用线程。由于直接关联对象比较小,所以这里的速度非常快。
  • 并发标记(Concurrent-Mark)阶段:从GC Roots的直接关联对象开始遍历整个对象图的过程,这个过程耗时较长但是不需要停顿用户线程,可以与垃圾收集线程一起并发运行。
  • 重新标记(Remark)阶段:由于在并发标记阶段中,程序的工作线程会和垃圾收集线程同时运行或者交叉运行,因此为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间通常会比初始标记阶段稍长一些,但也远比并发标记阶段的时间短。
  • 并发清除(Concurrent-Sweep)阶段:此阶段清理删除掉标记阶段判断的已经死亡的对象,释放内存空间。由于不需要移动存活对象,所以这个阶段也是可以与用户线程同时并发的

CMS 收集器无法在标记阶段停止应用程序的运行,这可能导致在标记过程中应用程序继续产生新的垃圾对象。这些新产生的垃圾对象就被称为"浮动垃圾"。

13.6.1. CMS的优点

  • 并发收集
  • 低延迟

13.6.2. CMS的弊端

  • 会产生内存碎片,导致并发清除后,用户线程可用的空间不足。在无法分配大对象的情况下,不得不提前触发FullGC。
  • CMS收集器对CPU资源非常敏感。在并发阶段,它虽然不会导致用户停顿,但是会因为占用了一部分线程而导致应用程序变慢,总吞吐量会降低。
  • CMS收集器无法处理浮动垃圾。可能出现"Concurrent Mode Failure"失败而导致另一次Full GC的产生。在并发标记阶段由于程序的工作线程和垃圾收集线程是同时运行或者交叉运行的,那么在并发标记阶段如果产生新的垃圾对象,CMS将无法对这些垃圾对象进行标记,最终会导致这些新产生的垃圾对象没有被及时回收,从而只能在下一次执行GC时释放这些之前未被回收的内存空间。

在G1垃圾回收器中,不是等所有的Eden区域都满了才进行年轻代垃圾回收(Young Generation Garbage Collection)。相反,G1会根据一定的条件和算法来触发垃圾回收。

G1将整个堆内存划分为多个区域,每个区域称为一个"region"。这些region包括Eden区域、幸存者区域以及老年代区域。年轻代部分主要由Eden区域和幸存者区域组成。

G1会根据堆内存的占用情况和垃圾回收的目标来决定何时触发垃圾回收。当一个或多个Eden区域中的内存占用达到一定的阈值,即"Eden占用率"达到一定百分比时,G1会启动年轻代垃圾回收。这时,G1会选取一些Eden区域以及相应的幸存者区域来进行垃圾回收操作。

所以,每次年轻代垃圾回收并不是等待所有Eden区域都满了,而是根据实际情况选择合适的区域来进行回收。这种方式有助于更好地平衡垃圾回收的开销和应用程序的吞吐量,同时也有利于减少长时间的停顿。

优化

优化Java虚拟机(JVM)在日常项目中是确保应用程序性能和稳定性的重要步骤。以下是一些常见的JVM调优手段,可以帮助你在日常项目中提升应用性能:

调整堆内存

通过调整JVM堆内存大小来平衡内存使用和垃圾回收性能。增加堆内存可以减少垃圾回收的频率和时间,但要注意不要过分分配,以免导致内存泄漏或过多的垃圾回收。

可以通过jconsole和java visualvm查看内存(堆,Eden,老年区),cpu运行情况

  1. -Xmx :设置最大堆内存大小。例如,-Xmx2g表示将最大堆内存设置为2GB。
  2. -Xms :设置初始堆内存大小。例如,-Xms512m表示将初始堆内存设置为512MB。
  3. -Xmn :设置新生代堆内存大小。例如,-Xmn256m表示将新生代堆内存设置为256MB。
  4. -XX:NewRatio :设置新生代与老年代的堆内存比例。例如,-XX:NewRatio=2表示新生代堆内存是老年代堆内存的1/2。
  5. -XX:SurvivorRatio :设置Eden区与Survivor区的堆内存比例。例如,-XX:SurvivorRatio=8表示Eden区与单个Survivor区的大小比为8:1。
  6. -XX:MaxTenuringThreshold :设置对象经过多少次Minor GC后进入老年代。例如,-XX:MaxTenuringThreshold=15表示对象经过15次Minor GC后进入老年代。
  7. -XX:InitialTenuringThreshold :设置对象最初进入老年代的阈值。例如,-XX:InitialTenuringThreshold=7表示对象最初在经过7次Minor GC后进入老年代。
  8. -XX:MaxPermSize (在Java 8之前) / -XX:MaxMetaspaceSize (在Java 8及以后,用于替代永久代):设置非堆内存(永久代或元空间)的最大大小。例如,-XX:MaxMetaspaceSize=256m表示将元空间的最大大小设置为256MB。
  9. -XX:ReservedCodeCacheSize :设置代码缓存大小,用于存储JIT编译后的代码。例如,-XX:ReservedCodeCacheSize=128m表示将代码缓存大小设置为128MB。
  10. -XX:MaxGCPauseMillis :设置期望的最大垃圾回收停顿时间。例如,-XX:MaxGCPauseMillis=50表示尝试将垃圾回收停顿时间限制在50毫秒以内。

选择合适的垃圾回收器

  1. Serial GC
    • 单线程收集器,适用于小型应用或者单核处理器。
    • 适合应用负载不高的情况。
    • 通过 " -XX:+UseSerialGC " 开启。
  1. Parallel GC
    • 多线程收集器,适用于多核处理器。
    • 适合具有较高吞吐量要求的应用,但可能会有较长的暂停时间。
    • 通过 " -XX:+UseParallelGC " 开启。
  1. Concurrent Mark-Sweep (CMS) GC
    • 适用于低延迟要求的应用,致力于减少停顿时间。
    • 在大型堆上可能导致碎片问题。
    • 通过 " -XX:+UseConcMarkSweepGC " 开启。
  1. G1 (Garbage-First) GC
    • 面向具有大堆和低延迟要求的应用。
    • 分阶段回收,尽量保持较稳定的延迟。
    • 通过 " -XX:+UseG1GC " 开启。
  1. Z Garbage Collector (ZGC)
    • 适用于大堆和低延迟要求,能够在限制时间内执行回收。
    • 通过 " -XX:+UseZGC " 开启。
  1. Shenandoah GC
    • 适用于需要极低停顿时间的应用,特别是大堆情况。
    • 通过 " -XX:+UseShenandoahGC " 开启。

JVM提供了不同类型的垃圾回收器,如Serial、Parallel、CMS、G1等。根据应用程序的特点选择适合的垃圾回收器,以获得最佳的垃圾回收性能。

低延迟: 低延迟是指系统在响应请求时的时间非常短,用户或应用程序能够在很短的时间内获得反馈。在低延迟的情况下,系统的响应时间几乎是实时的。这对于需要即时交互和高响应速度的应用非常重要。

适用场景:

  • 实时系统:需要在短时间内做出响应,例如金融交易系统、即时通讯应用等。
  • 游戏应用:需要快速响应玩家的输入,以保持流畅的游戏体验。
  • 视频流媒体:需要确保视频流的快速传输和播放,以避免缓冲和卡顿。

高吞吐量: 高吞吐量是指系统能够处理大量请求或任务,每单位时间内能够处理的工作量很大。虽然响应时间可能不如低延迟系统那样迅速,但系统能够以高效率处理大量工作。

适用场景:

  • 批量处理:需要处理大量数据或任务,例如数据分析、报表生成等。
  • 后台任务:执行定期的后台任务,如数据清理、备份等。
  • Web 服务器:需要处理大量并发请求,如网站、应用程序后端等。

调整垃圾回收器参数

根据应用程序的工作负载和需求,调整垃圾回收器的参数,如堆大小、新生代和老年代的比例、垃圾回收阈值等。

避免过多的对象创建

频繁创建对象会增加垃圾回收的压力。尽量重用对象、使用对象池,以减少对象的创建和销毁。

减少内存泄漏

确保对象在不再使用时能够被正确释放,防止因为引用未被清理而导致的内存泄漏。

使用合适的数据结构和算法

选择合适的数据结构和算法可以减少内存消耗和CPU利用率,从而提升应用性能。

使用并发工具

合理使用Java的并发工具,如多线程、线程池等,可以充分利用多核CPU,提高应用的吞吐量和响应能力。

监控和分析工具

使用JVM监控工具(如JVisualVM、VisualVM、JConsole等)进行实时性能监测,以及使用分析工具(如Heap Dump分析、线程Dump分析等)来识别性能问题和瓶颈。

代码优化

优化代码逻辑,避免不必要的计算和循环。使用合适的数据结构和算法可以减少CPU和内存的使用。

合理设置GC日志

jstat -gc 8764 1000 10 每秒刷新一次,一共刷10条

通过GC日志可以了解垃圾回收的情况,有助于识别垃圾回收问题,及时采取措施。

性能测试和负载测试

定期进行性能测试和负载测试,模拟实际生产环境,发现潜在的性能问题并及时解决。

升级JVM版本

定期考虑升级JVM版本,新版本通常会带来性能和稳定性的改进。

请注意,每个项目和应用的情况都是独特的,因此需要根据实际情况来选择和应用这些调优手段。最好的做法是结合实际性能监测和分析,以及持续的测试和调整,来逐步优化JVM性能。

jinfo 是Java虚拟机(JVM)自带的一个命令行工具,用于查看和调整正在运行的Java进程的一些配置信息。它可以帮助你获取关于Java进程的各种运行时信息,如系统属性、堆内存配置、垃圾回收器设置等。以下是使用jinfo命令的一些常见用法:

  1. 查看Java进程的系统属性:

    phpCopy code
    jinfo <pid>

用实际的进程ID(pid)替换**<pid>**,这将显示与Java进程相关的系统属性信息。

  1. 查看Java进程的JVM参数和标志:

    phpCopy code
    jinfo -flags <pid>

这将显示Java进程使用的JVM参数和标志。

  1. 查看Java进程的堆内存使用情况:

    phpCopy code
    jinfo -heap <pid>

这将显示有关Java进程堆内存的详细信息,如最大堆大小、初始堆大小等。

  1. 查看Java进程的非堆内存使用情况:

    phpCopy code
    jinfo -noname <pid>

这将显示有关Java进程非堆内存的信息,如Metaspace大小、Compressed Class Space大小等。

  1. 查看Java进程的垃圾回收器信息:

    phpCopy code
    jinfo -gc <pid>

这将显示有关Java进程垃圾回收器的详细信息,如使用的垃圾回收器类型、各代的内存配置等。

  1. 更改Java进程的某个系统属性:

    phpCopy code
    jinfo -flag <name>=<value> <pid>

使用这个命令可以在不重启Java进程的情况下更改某个系统属性的值。

请注意,jinfo 命令的可用性可能会因JVM版本和操作系统而有所不同,某些选项可能只适用于特定版本的JVM。在使用jinfo 之前,建议查阅相关文档以确保正确使用该命令。此外,为了使用jinfo ,你需要知道要检查的Java进程的进程ID(pid)。可以通过命令如jps(Java进程状态工具)或操作系统的进程管理工具来获取进程ID。

jmap 是 Java 虚拟机(JVM)自带的一个命令行工具,用于获取 Java 进程的堆内存快照、内存映射和类统计信息。这些信息可以帮助你分析内存使用情况、检查内存泄漏等问题。以下是使用 jmap 命令的一些常见用法:

  1. 生成堆内存快照:

    luaCopy code
    jmap -dump:format=b,file=<filename> <pid>

这将生成一个堆内存快照,并将其保存到指定的文件中。你需要将 <filename> 替换为你希望保存快照的文件名,<pid> 替换为目标 Java 进程的进程 ID。

  1. 查看堆内存使用情况:

    phpCopy code
    jmap -heap <pid>

这将显示有关 Java 进程堆内存的详细信息,如最大堆大小、初始堆大小、已使用的堆内存等。

  1. 查看内存映射信息:

    phpCopy code
    jmap -histo <pid>

这将显示堆中各个类的实例数量和占用的内存量,帮助你了解各个类的内存使用情况。

  1. 查看类加载统计信息:

    phpCopy code
    jmap -clstats <pid>

这将显示有关 Java 类加载器的统计信息,包括加载的类数量、卸载的类数量等。

  1. 查看共享对象的内存映射:

    phpCopy code
    jmap -finalizerinfo <pid>

这将显示等待终结的对象以及它们的终结方法情况,帮助你检查是否有未被正确终结的对象。

请注意,jmap 命令生成的堆内存快照文件可能会比较大,因此在使用之前要确保有足够的磁盘空间。另外,与其他 JVM 相关的工具一样,jmap 的可用性可能会因 JVM 版本和操作系统而有所不同。在使用 jmap 之前,建议查阅相关文档以确保正确使用该命令。同样,你需要知道要检查的 Java 进程的进程 ID(pid),可以通过命令如 jps 或操作系统的进程管理工具来获取。

jstack pid查看线程,死锁

可以通过jvisualvm查看

jstat 是一个用于监控和收集 Java 虚拟机(JVM)性能统计信息的命令行工具。它可以用来查看有关内存、垃圾回收、类加载、线程和编译等方面的信息,帮助开发人员了解应用程序在运行时的性能情况,以便于诊断问题、优化代码和调整虚拟机参数。

jstat 命令的基本用法如下:

复制代码
shCopy code
jstat [ options ] <vmid> [ interval [ count ] ]

其中:

  • options:用于指定要监控的数据类型和输出格式的选项。
  • vmid :目标 JVM 的进程 ID 或主机名。你可以使用 jps 命令来获取 JVM 进程的 ID。
  • interval:指定采样间隔的时间(以毫秒为单位)。
  • count:指定采样的次数。

以下是一些常用的 jstat 选项:

  • -gc:用于监控垃圾回收相关的统计信息,如堆内存使用、垃圾回收时间等。
  • -class:用于监控类加载和卸载相关的统计信息。
  • -compiler:用于监控编译器的统计信息,如编译任务数、时间等。
  • -gcutil:以百分比形式显示垃圾回收统计信息。
  • -gccapacity:以容量形式显示垃圾回收统计信息。
  • -gcnew-gcold-gcpermcapacity:分别用于监控新生代、老年代和永久代的垃圾回收统计信息。

示例用法:

复制代码
shCopy code
# 监控某个进程的堆内存使用情况,每秒采样一次,共采样5次
jstat -gc <vmid> 1000 5

# 以百分比形式监控垃圾回收情况
jstat -gcutil <vmid> 1000 5

# 监控类加载情况
jstat -class <vmid> 1000 5

总之,jstat 是一个强大的工具,可以帮助开发人员实时了解 Java 应用程序在运行时的性能状况,从而进行性能分析和优化。

相关推荐
程序猿20233 小时前
MAT(memory analyzer tool)主要功能
jvm
期待のcode5 小时前
Java虚拟机的非堆内存
java·开发语言·jvm
jmxwzy9 小时前
JVM(java虚拟机)
jvm
Maỿbe10 小时前
JVM中的类加载&&Minor GC与Full GC
jvm
人道领域11 小时前
【零基础学java】(等待唤醒机制,线程池补充)
java·开发语言·jvm
小突突突11 小时前
浅谈JVM
jvm
饺子大魔王的男人12 小时前
远程调试总碰壁?局域网成 “绊脚石”?Remote JVM Debug与cpolar的合作让效率飙升
网络·jvm
天“码”行空1 天前
java面向对象的三大特性之一多态
java·开发语言·jvm
独自破碎E1 天前
JVM的内存区域是怎么划分的?
jvm
期待のcode1 天前
认识Java虚拟机
java·开发语言·jvm