JVM

Java Virtual Machine(Java虚拟机),是C语言开发的虚拟出来的一个"操作系统",在这个"操作系统"里面运行java程序的class字节码文件;不同的电脑操作系统有不同的JVM版本。

JVM启动之后,在我们的电脑上或者服务器上表现出来的就是一个java进程。

能在JVM上运行的编程语言都是把编写好的源代码编译成JVM能识别的.class字节码文件,然后再JVM上运行;(javac编译器将.java文件编译为.class文件)
JVM参数配置参考:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html


JDK、JRE、JVM

类加载器

通过一个类的全限定名来获取描述此类的二进制字节流;

工作过程:如果一个类加载器收到一个类加载的请求,它首先不会自己加载,而是把这个请求委派给父类加载器,只有父类无法完成加载时子类加载器才会尝试加载;
双亲委派模型(jdk1.2+)

  • Bootstrap ClassLoader启动类加载器(C++ 实现,是虚拟机的一部分);
  • Extension ClassLoader 扩展类加载器(Java 实现,独立于虚拟机外部且全继承自 java.lang.ClassLoader)
  • Application ClassLoader 应用程序加载器(Java实现)
  • Custom ClassLoader 自定义类加载器(java实现,继承 jdk 的 ClassLoader)

除了启动类加载器之外,其他类加载器都有自己的父类加载器;

java 复制代码
// null(bootstrapClassLoader c++语言实现,没有名称)
String.class.getClassLoader();
//sun.misc.Launcher$ExtClassLoader@4f7c7a2d
MyTest.class.getClassLoader().getParent();
// sun.misc.Launcher$AppClassLoader@18ab4ac2
MyTest.class.getClassLoader();

JVM内存部分(运行时数据区)

程序计数器

  1. 记录程序执行位置、行号;
  2. 一块很小的区域;
  3. 线程私有;
  4. 不存在OutOfMemoryError;
  5. 无GC回收;

虚拟机栈

虚拟机栈是采用了一种栈的数据结构,入口和出口只有一个,分为入栈和出栈,先进后出;虚拟机栈主要是执行方法;

A方法调用B方法,B方法调用C方法,(A--> B--> C),方法执行就是压栈,方法执行结束就出栈。

  • 局部变量表:是一组变量值存储空间,用于存放方法参数和方法内定义的局部变量;
  • 操作数栈:也叫操作栈,它是一个先进后出的栈 (FILO),当一个方法刚刚开始执行时,其操作数栈是空的,随着方法执行,会从局部变量表或对象实例的字段中复制常量或变量写入到操作数栈,再随着计算的进行将栈中元素出栈到局部变量表或者返回给方法调用者,也就是入栈和出栈操作,一个完整的方法执行期间往往包含多个这样入栈/出栈的过程;
  • 动态链接:一个方法要调用其他方法,需要将这些方法的符号引用转化为其在内存地址中的直接引用,而符号引用存在于方法区中的运行时常量池,所以需要在运行时动态将这些符号引用转化为直接引用;
  • 返回地址:方法不管是正常执行结束还是异常退出,需要返回方法被调用的位置;

虚拟机栈的特点

  1. 线程私有;
  2. 方法执行会创建栈帧,存储局部变量表等信息;
  3. 方法执行入虚拟机栈,方法执行完出虚拟机栈;
  4. 栈深度大于虚拟机所允许StackOverflowError;
  5. 栈需扩展而无法申请空间OutOfMemoryError;
  6. 栈里面运行方法,存放方法的局部变量名,变量名所指向的值(常量值、对象值等)都存放到堆上的;
  7. 栈一般都不设置大小,栈所占的空间其实很小,可以通过-Xss1M进行设置,如果不设置默认为1M;
  8. 该区域不会有GC回收;

本地方法栈

  1. 与虚拟机栈基本类似;
  2. 区别在于本地方法栈为Native方法服务;
  3. Sun HotSpot将虚拟机栈和本地方法栈合并;
  4. 有StackOverflowError和OutOfMemoryError;
  5. GC不会回收该区域;

栈的OutOfMemoryError溢出一般是在多线程 条件下可能会产生,建立过多的线程,每个线程的运行时间又比较长,可能产生栈的OutOfMemoryError溢出;

单线程 下,无论是由于栈帧太大还是虚拟机容量太小,当内存无法分配时,虚拟机抛出StackOverFlowError的错误异常;
线程私有部分的整体特征:

是线程私有的,随着线程执行结束而结束(JVM就销毁了虚拟机栈里面的栈帧),是比较有规律的,问题会少一些,出问题比较多的是线程共享的部分,也就是堆和方法区(元空间);

方法区(元空间)

方法区(jdk 1.7后合并到了堆)

方法区在JDK1.8称为元空间(Metaspace),元空间与堆不相连,但与堆共享物理内存;

方法区(元空间)的特点

  1. 线程共享;
  2. 存储 类信息、常量、运行时常量池、静态变量、即时编译器编译后的代码等数据;
  3. HotSpot虚拟机上将方法区叫永久代;(1.7及之前的版本)
  4. 垃圾收集很少光顾该区域(无GC回收);
  5. 方法区通过-XX:MaxPermSize设置最大值;(1.7及之前的版本)
    (元空间:1.8是-XX:MaxMetaspaceSize=48m)
  6. 空间不够分配时OutOfMemoryError;

  • -XX:+PrintGCDetails用于打印GC日志;
  • -XX:+PrintGCDateStamps用于打印对应的时间戳;
  • -XX:-UseCompressedClassPointer表示在Metaspace中不要开辟出一块新的空间(Compressed Class Space),如果开辟这块空间的话,默认大小是1G,所以我们关闭该功能;
  • -XX:MetaspaceSize=20M
  • -XX:MaxMetaspaceSize=20m

方法区/元空间溢出(OutOfMemoryError:Metaspace)

  1. 方法区也称永久代(1.7及之前的版本);

  2. 方法区存放class的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等 ,比如通过反射大量生成动态类(Class<?>)填充该区域即会发生内存溢出

JDK1.6及之前:有永久代,常量池在方法区;

JDK1.7:从某个版本开始已去除永久代,常量池1.7放入堆中;

JDK1.8及之后:无永久代,常量池1.8在元空间;

在jdk1.7及jdk1.8中不会报OutOfMemoryError:PermGen space;

jdk1.8元空间设置大小:-XX:MetaspaceSize=10M -XX:MaxMetaspaceSize=10M

  1. 线程共享;
  2. 内存中最大的区域;
  3. 虚拟机启动时创建;
  4. 存放所有实例对象或数组
  5. GC垃圾收集器的主要管理区域;
  6. 可分为新生代(1/3)、老年代(2/3);
  7. 新生代更细化可分为Eden(8/10)、From Survivor(1/10)、To Survivor(1/10);
  8. 可通过-Xmx、-Xms调节堆大小;
  9. 无法再扩展java.lang.OutOfMemoryError: Java heap space

堆溢出

不断创建对象又不释放,当对象到达一定数量,无堆空间将产生堆内存溢出;

被持有引用无法释放就溢出,未被持有引用,GC满了就回收

运行jdk的bin目录下jvisualvm.exe图形工具,可以查看当前系统中java进程的运行情况(方法、GC、堆、元空间等)

  • 内存泄漏:GC Roots到对象之间有可达路径而无法收集;
  • 内存溢出:GC Roots到对象之间无可达路径,可以被收集,但对象还需要存活着,此时可根据物理机内存适当调大虚拟机堆参数-Xms、-Xmx,分析代码是否对象生命周期是否过长、对象是否持有状态时间过长;

堆中对象的创建过程

  • 当通过 new 创建对象时,首先检查这个new指令的参数是否能在元空间中定位到一个类的符号引用(Class信息),并且检查这个符号引用代表的类是否已经被加载、解析和初始化过,如果没有,执行相应的类加载;
  • 类加载检查通过之后,为新对象分配内存(内存大小在类加载完成后便可确定),在堆的空闲内存中划分一块区域 ('指针碰撞《内存规整》'(内存顺序排列,指针后移)或'空闲列表《内存交错》'(内存随机排列,记录空闲地址)的分配方式);
  • 由于堆中分配内存非常频繁,为了避免多个线程同时分配堆内存时的冲突,虚拟机采用CAS和失败重试方式保证操作的线程安全 ,同时虚拟机还有另一套设计就是把每个线程分配堆内存的动作隔离开,即++每个线程预先在堆中分配一块内存,称为线程分配缓冲++(TLAB->Thread Local Allocation Buffer),线程先在自己的TLAB上分配,分配完了再CAS同步,这样可以很大程度避免在并发情况下频繁创建对象造成的线程不安全;
  • 内存空间分配完成后会初始化为 0(不包括对象头),接下来就是填充对象头,把对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息存入对象头。
  • 执行 new 指令后执行 init 方法后才算一份真正可用的对象创建完成;

对象的内存布局

在 HotSpot 虚拟机中,分为 3 块区域:

  • 对象头(Header) :包含两部分
    • 第一部分用于存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等,32 位虚拟机占 32 bit,64 位虚拟机占 64 bit。官方称为 'Mark Word';
    • 第二部分是类型指针,即对象指向它的类的元数据指针,虚拟机通过这个指针确定这个对象是哪个类的实例,另外,如果是 Java 数组,对象头中还必须有一块用于记录数组长度的数据,因为普通对象可以通过 Java 对象元数据确定大小,而数组对象不可以;
存储内容 标志位 状态
对象哈希码、对象分代年龄 01 未锁定
指向锁记录的指针 00 轻量级锁定
指向重量级的指针 10 膨胀(重量级锁定)
空,不需要记录信息 11 GC标记
偏向线程ID、偏向时间戳,对象分代年龄 01 可偏向
  • 实例数据(Instance Data):程序代码中所定义的各种成员变量类型的字段内容(包含父类继承下来的和子类中定义的);
  • 对齐填充(Padding):不是必然需要,主要是占位,保证对象大小是某个字节的整数倍;

对象的访问

使用对象时,我们是通过栈上的 reference 引用来操作堆上的具体对象;

Sun Hotspot虚拟机使用直接指针访问具体对象;

JVM垃圾回收

垃圾收集Garbage Collection 简称GC;

线程私有部分:程序计数器、虚拟机栈、本地方法栈;随着线程创建和结束,不需要回收。

线程共享部分:堆、方法区;需要考虑垃圾回收问题。

可达性分析算法

JVM是通过可达性分析算法来判断一个对象是否需要被回收 ,其实现思想是通过一个GC Root对象为起始根节点,然后逐个往下去搜索各个对象,看GC Root到对象之间有没有可达路径,如果没有可达路径则表示该对象是不可用的,可以被回收;

JVM中如下几种对象可以作为GC Root

  • 虚拟机栈本地变量表中引用类型所引用的对象;
  • 方法区/元空间中类的静态变量所引用的对象;
  • 方法区/元空间中类的常量所引用的对象;
  • 本地方法栈中Native方法里所引用的对象;

判断是否有可达路径主要是通过 引用 去分析,Java中的引用分为4种:(强度依次减弱)

  1. 强引用(Strong Reference):是最普遍的引用,比如 User user = new User();垃圾收集器不会回收强引用;
  2. 软引用(Soft Reference):表示一些对象还有用,但是也不是必须要用的,在系统内存不足时,可以被垃圾回收器回收;
  3. 弱引用(Weak Reference):表示一些不是必须的对象,当开始垃圾收集时,无论内存是否足够,都会回收弱引用对象;
  4. 虚引用(Phantom Reference):是一种特殊的引用,也是最弱的引用关系,用来实现Object.finalize功能,在开发中很少使用;

方法区/元空间的回收

在JVM的垃圾回收中,堆内存是回收最频繁也是最多的,方法区/元空间的垃圾收集效率非常低,所以JVM规范中并没有要求一定要回收方法区/元空间 ,如果方法区/元空间有无用的类信息、常量池,JVM不是必须要回收的;

Hotspot 虚拟机默认会进行类的卸载,如果不想卸载无用的类,可以加上参数-Xnoclassgc;

查看类的加载和卸载参数:-XX:+PrintGCDetails -XX:+PrintGCDateStamps -verbose:class -XX:+TraceClassLoading -XX:+TraceClassUnloading

方法区/元空间垃圾回收主要两部分内容:

  • 废弃常量:一般是判断没有任何对象引用该常量;
  • 无用的类:要满足以下三个条件
    1. 该类所有的实例都已经回收;
    2. 加载该类的 ClassLoader 已经被回收;
    3. 该类对应的 Class 对象没有任何地方被引用;

JVM回收对象的两次标记过程

  • 第一次标记
    如果对象进行可达性分析算法之后没有发现与GC Roots相连的引用链,那它将会第一次标记并且进行一次筛选;
    • 筛选条件:判断此对象是否有必要执行finalize()方法;
    • 筛选结果:当对象没有覆盖finalize()方法或者finalize()方法已经被JVM执行过,则判定为可回收对象,如果对象有必要执行finalize()方法,则被放入F-Queue队列中,稍后在JVM自动建立低优先级的Finalizer线程(可能多个线程)中触发这个方法;
  • 第二次标记
    GC对F-Queue队列中的对象进行二次标记;
    如果对象在finalize()方法中重新与引用链上的任何一个对象建立了关联,那么第二次标记时则会将它移出"即将回收"集合,如果此时对象还没成功逃脱,那么只能被回收了;

finalize() 是Object类的一个空方法、该方法是被垃圾收集器所调用,一个对象的finalize()方法只会被垃圾收集器自动调用一次,经过finalize()方法逃脱死亡的对象,第二次不会再调用;

不提倡在程序中调用finalize()来进行对象的自救,因为该方法执行的时间不确定,甚至是否被执行也不确定(Java程序的不正常退出),无法保证各个对象的调用顺序(甚至有不同线程中调用);

JVM垃圾回收器回收算法

标记-清除算法

最基础的收集算法;

标记:标记出所有需要回收的对象,经过两次标记的对象就可以判定可以回收了;

清除:两次标记后,对还在" 即将回收 "集合的对象进行回收;

优点:基于最基础的可达性分析算法,实现简单,后续的收集算法都是基于这种思想实现的;

缺点:标记和清除效率不高,产生大量不连续的内存碎片,导致创建大对象时找不到连续的空间,不得不提前触发另一次的垃圾回收;

复制算法

将可用内存按容量分为大小相等的两块,每次只使用其中一块,当这一块的内存用完了,就将还存活的对象复制到另外一块内存上,然后再把已使用过的内存空间一次清理掉;

优点:实现简单,效率高,解决了标记-清除算法导致的内存碎片问题;

缺点:代价太大,将可分配内存缩小了一半,效率随对象的存活率升高而降低,一般虚拟机都会采用该算法来回收新生代;

标记-整理算法

标记-整理算法是根据老年代的特点而产生的;

标记:标记过程与上面的标记-清理算法一致,也是基于可达性分析算法,也是两次标记;

整理:和标记-清理不同的是,该算法不是针对可回收对象进行清理,而是根据存活对象进行整理。让存活对象都向一端移动,然后直接清理掉边界以外的内存;

优点:不会像复制算法那样划分两个区域,提高了空间利用率,不会产生不连续的内存碎片;

缺点:效率问题,除了像标记-清除算法的标记过程外,还多了一步整理过程,效率变低;

分代收集算法

现在一般虚拟机的垃圾收集都是采用" 分代收集 "算法;

根据对象存活周期的不同将内存划分为几块,一般把java堆分为新生代和老年代,JVM根据各个年代的特点采用不同的收集算法;
++新生代++ 中,每次进行垃圾回收都会发现大量对象死去,只有少量存活,因此采用复制算法,只需要付出少量存活对象的复制成本就可以完成收集;
++老年代++中,因为对象存活率较高,采用标记-清理、标记-整理算法来进行回收;

JVM垃圾收集器

一共有7种作用于不同分代的垃圾收集器,如果两个收集器之间存在连线,则说明它们可以搭配使用,垃圾收集器所处区域表示它是属于新生代收集器还是老年代收集器;

新生代收集器:Serial、ParNew、Parallel Scavenge

老年代收集器:CMS、Serial Old、Parallel Old

整堆收集器: G1

Serial收集器

-XX:+UseSerialGC

新生代收集器,最早的收集器,单线程的,收集时需暂停用户线程的工作。所以有卡顿现象,效率不高。但Serial收集器简单,不会有线程交互的开销,是client模式下默认的垃圾收集器。

java -version查看默认模式: -client, -server(jdk1.8);

ParNew收集器

-XX:+UseParNewGC

它是新生代收集器,就是Serial收集器的多线程版本,大部分基本一样,配置参数也一致,单CPU下,ParNew还需要切换线程,可能还不如Serial。

Serial和ParNew收集器可以配合CMS收集器,前者收集新生代,后者CMS收集老年代。

"-XX:+UseConcMarkSweepGC":指定使用CMS后,会默认使用ParNew作为新生代收集器;

"-XX:ParallelGCThreads=2":指定垃圾收集的线程数量,ParNew默认开启的收集线程与CPU的数量相同;

Parallel Scavenge收集器

-XX:+UseParallelGC

它是新生代收集器,基于复制算法,并行的多线程收集器(与ParNew收集器类似),侧重于达到一个可控的吞吐量,虚拟机运行100分钟,垃圾收集花1分钟,则吞吐量为99%。

它提供两个参数设置吞吐量:

-XX:MaxGCPauseMillis 设置大于0的毫秒数,每次GC的时间将保持不超过设置的值。

--XX:GCTimeRatio 垃圾收集时间占整个虚拟机时间的比率

Serial Old收集器

它是Serial收集器的老年代版本,同Serial一样,单线程,可在Client模式下使用,也可在Server模式下使用,采用标记-整理算法;

Parallel Old收集器

-XX:+UseParallelOldGC

是Parallel Scavenge的老年代版本,多线程,标记整理算法,它是在1.6开始才提供。

在注重吞吐量和CPU资源的情况, Parallel Scavenge新生代+ Parallel Old老年代是一个很好的搭配。

CMS收集器

-XX:+UseConcMarkSweepGC

全称Concurrent Mark Sweep,是追求最短回收停顿时间为目标的收集器,互联网B/S结构的服务器端特别适合此收集器。

基于标记清除算法,它的运作过程分为4个阶段,初始标记、并发标记、重新标记、并发清除,其中初始标记和重新标记需要暂停用户线程,其他是并发执行,所以总体上暂停时间更短。

CMS收集器的缺点:

  • 并发收集会占用CPU资源,特别是cpu数量小的服务器下,会占用用户线程,导致性能下降;
  • 会产生浮动垃圾,因为你并发清除的时候用户线程可能还在产生垃圾,这些垃圾没有清除,而且你不能让老年代填满了再清除,你要给用户线程留一定空间,所以jdk1.5默认是老年代65%了就触发回收,jdk1.6则提升到92%,如果预留老年代不够用户线程使用,则启用Serial Old收集,这就会暂停用户线程,导致性能下降。
  • 这个标记整理算法清理后会产生碎片空间,如果此时要分配一个大对象,则无连续空间,需要一次full gc,性能下降;

## G1收集器 -XX:+UseG1GC 目前最前沿最先进的垃圾收集器,从JDK1.7u4开始可以使用; G1收集器的特点: - 并发和并行,充分利用多核cpu,让用户线程不暂停; - 保留了分代收集的特性,不需要其他收集器配合完成各代的回收; - 空间整合,G1不会产生碎片空间,不会触发full gc去回收碎片; - 可预测的停顿,G1追求了停顿预测模型,在一段时间内停顿不超过多少毫秒等;

GC日志

  • -XX:+PrintGC
    允许在每个GC上打印消息,默认禁用。
  • -XX:+PrintGCApplicationConcurrentTime
    启用打印自上次暂停(例如GC暂停)以来经过的时间,默认禁用。
  • -XX:+PrintGCApplicationStoppedTime
    允许打印暂停(例如GC暂停)持续的时间,默认禁用。
  • -XX:+PrintGCDateStamps
    启用在每个GC上打印日期戳,默认禁用。
  • -XX:+PrintGCDetails
    允许在每个GC上打印详细消息,默认禁用。
  • -XX:+PrintGCTaskTimeStamps
    启用为每个GC工作线程任务打印时间戳,默认禁用。
  • -XX:+PrintGCTimeStamps
    启用在每个GC上打印时间戳,默认禁用。
  • -Xloggc:filename
    设置要将详细GC事件信息重定向到其中进行日志记录的文件。写入此文件的信息类似于-verbose:gc的输出,其中包含自每个记录的事件之前的第一个gc事件以来经过的时间。-Xloggc选项重写-verbose:gc,如果这两个选项都是用同一个java命令给出的。
  • -XX:+HeapDumpOnOutOfMemoryError
    启用在引发Java.lang.OutOfMemoryError异常时使用堆探查器(HPROF)将Java堆转储到当前目录中的文件。可以使用-XX:heap dump path选项显式设置堆转储文件的路径和名称,默认禁用。
  • -XX:HeapDumpPath=path
    设置在设置-XX:+HeapDumpOnOutOfMemoryError选项时用于写入堆分析器(HPROF)提供的堆转储的路径和文件名。默认是在当前工作目录中创建的,名为java_pidpid.hprof,其中pid是导致错误的进程的标识符。
    显式设置默认文件(%p表示当前进程标识符):-XX:HeapDumpPath=./java_pid%p.hprof

分析阅读GC日志

JDK命令行监控工具

官方文档:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/index.html

  • jinfo
    查看正在运行的Java程序的扩展参数信息 jinfo pid
    查看JVM的参数 jinfo -flags pid
    查看java系统属性 jinfo -sysprops pid
  • jps
    显示所有的HotSpot虚拟机进程,可选参数:
    -q 只输出LVMID,LVMID与linux的PID一致
    -m 输出虚拟机进程启动时传给主类main()的参数
    -l 输出主类的全名或jar包的路径
    -v 输出虚拟机进程启动时JVM参数
  • jstat
    虚拟机监控命令,收集虚拟机运行时的各方面数据,可选参数:
    -class 监视类装载、卸载及耗时,
    -gc 监视java堆各个区的情况
    -gccapacity 与gc基本相同,但更关注java堆个区域使用到的最大、最小空间
    -gcutil 与gc基本相同,但更关注已使用空间占总空间的百分比
    -gccause 与gcutil功能一样,但会额外输出导致上一次GC产生的原因
    -gcnew 监视新生代GC状况
    -gcnewcapacity 与gcnew一致,但更关注使用到的最大、最小空间
    -gcold 监视老年代GC状况
    -gcoldcapacity 与gcold一致,但更关注使用到的最大、最小空间
    -gcpermcapacity 输出永久代使用到的最大、最小空间
    -gcmetacapacity 元空间GC情况
    -compiler 输出JIT编译器编译过的方法、耗时等信息
    -printcompilation 输出已经被JIT编译的方法
    其他
    S0C:年轻代中第一个survivor(幸存区)的容量 (字节)
    S1C:年轻代中第二个survivor(幸存区)的容量 (字节)
    S0U:年轻代中第一个survivor(幸存区)目前已使用空间 (字节)
    S1U:年轻代中第二个survivor(幸存区)目前已使用空间 (字节)
    EC:年轻代中Eden(伊甸园)的容量 (字节)
    EU:年轻代中Eden(伊甸园)目前已使用空间 (字节)
    OC:Old代的容量 (字节)
    OU:Old代目前已使用空间 (字节)
    PC:Perm(持久代)的容量 (字节)
    PU:Perm(持久代)目前已使用空间 (字节)
    YGC:从应用程序启动到采样时年轻代中gc次数
    YGCT:从应用程序启动到采样时年轻代中gc所用时间(s)
    FGC:从应用程序启动到采样时old代(全gc)gc次数
    FGCT:从应用程序启动到采样时old代(全gc)gc所用时间(s)
    GCT:从应用程序启动到采样时gc用的总时间(s)
    NGCMN:年轻代(young)中初始化(最小)的大小 (字节)
    NGCMX:年轻代(young)的最大容量 (字节)
    NGC:年轻代(young)中当前的容量 (字节)
    OGCMN:old代中初始化(最小)的大小 (字节)
    OGCMX:old代的最大容量 (字节)
    OGC:old代当前新生成的容量 (字节)
    PGCMN:perm代中初始化(最小)的大小 (字节)
    PGCMX:perm代的最大容量 (字节)
    PGC:perm代当前新生成的容量 (字节)
    S0:年轻代中第一个survivor(幸存区)已使用的占当前容量百分比
    S1:年轻代中第二个survivor(幸存区)已使用的占当前容量百分比
    E:年轻代中Eden(伊甸园)已使用的占当前容量百分比
    O:old代已使用的占当前容量百分比
    P:perm代已使用的占当前容量百分比
    S0CMX:年轻代中第一个survivor(幸存区)的最大容量 (字节)
    S1CMX :年轻代中第二个survivor(幸存区)的最大容量 (字节)
    ECMX:年轻代中Eden(伊甸园)的最大容量 (字节)
    DSS:当前需要survivor(幸存区)的容量 (字节)(Eden区已满)
    TT: 持有次数限制
    MTT:最大持有次数限制
  • jmap
    可用于生成java堆转储快照文件
    可选参数:(有些参数在window下无效)
    -dump,如:jmap -dump:format=b,file=/opt/dump.bin pid
    -finalizerinfo 等待执行finalizer的对象
    -heap 显示java堆栈信息,如jmap -heap pid
    -histo 显示堆中对象统计信息,如jmap -histo pid
    -permstat 显示永久代的内存情况
    -F 当-dump生成堆栈无响应,可以使用-F强制生成堆栈信息
  • jhat
    用于分析虚拟机生成的堆转储快照文件
  • jstack
    生成虚拟机中当前执行的线程快照信息,目的是查出当前线程有没有卡顿、长时间没有响应的情况,卡顿等待的原因可能为等待外部资源、数据库连接、网络资源、设备资源、死循环、锁等,可选参数:
    -F 当正常输出无响应时强制输出线程堆栈;
    -l 除显示堆栈外,显示锁的附加信息;
    -m 如果调用到本地方法,显示C/C++的堆栈信息;

JVM可视化监视

JCnsole(jdk1.5+)

Java监控与管理控制台

jdk安装目录bin下双击jconsole.exe启动;

概要是总体信息

内存标签页相当于jstat命令

线程标签页相当于jstack命令

VisualVM(jdk1.6 u7+)

java虚拟机监控工具

在jdk安装目标bin下双击jvisualvm.exe启动;

注意:使用图形化工具,要开启JMX;JDK1.5及之前的版本要手动开启JMX功能,需在程序启动前加上参数-Dcom.sun.management.jmxremote。

JVM内存溢出分析

MAT(Memory Analysis Tool):分析dump文件(堆转储快照文件)

官网下载:https://www.eclipse.org/mat/

JVM参数类型

官方文档:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html

  • 标准参数
    Standard Options
    在各个版本中都有,是比较稳定的,以-开头;
    -help、-server、-client、-version、-classpath...
  • 非标准化X参数
    Non-Standard Options
    是不稳定的,在各个jvm版本中不一样,以-X开头,可以使用 java -X 查看可选参数;
  • 非标准化XX参数
    Advanced Runtime Options
    Advanced JIT Compiler Options
    Advanced Serviceability Options
    Advanced Garbage Collection Options
    也是不稳定的,在各个jvm版本中不一样,主要用于jvm调优和debug,以-XX开头;
    非标准化XX参数分两类
    • boolean类型
      -XX:[+-]<name>,+表示启用,-表示禁用
      比如:-XX:+UseG1GC
    • 非boolean类型
      -XX:<key>=<value>
      -XX:MaxGCPauseMillis=200

内存溢出问题分析

  • 自动导出内存溢出堆栈转存文件
    添加JVM参数启动java程序
    -Xmx10m
    -XX:+PrintGCDetails
    -XX:+PrintGCDateStamps
    -Xloggc:d:/logs/gc.log
    -XX:+HeapDumpOnOutOfMemoryError
    -XX:HeapDumpPath=d:/logs/heapdump.hprof
  • 手动导出内存溢出堆栈转存文件
    jmap -dump:format=b,file=d:/logs/heapdump.hprof pid

使用 MAT 工具打开 heapdump.hprof 查看溢出怀疑

线程问题监控

官方文档:https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/tooldescr034.html#sthref31
线程状态

线程状态 描述
NEW 线程尚未启动
RUNNABLE 线程在JVM中执行
BLOCKED 线程在等待监视器锁定时被阻塞
WAITING 线程正在无限期地等待另一个线程来执行特定的操作
TIMED WAITING 线程等待另一个线程执行一个操作,等待时间最长为指定的等待时间
TERMINATED 线程已退出
  • jstack pid > xxx.txt
    生成进程(pid)的线程运行信息到指定文件
    • 可以定位死锁
  • top
    查看 cpu 进程使用情况;
  • top -p pid -H
    查看进程中线程的使用情况

监控本地及远程进程

监控本地tomcat

监控远程tomcat

监控普通java进程

visualvm

文档:https://visualvm.github.io/index.html

连接远程tomcat

(1)开启远程访问:

JAVA_OPTS="$JAVA_OPTS -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=8898 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -Djava.rmi.server.hostname=192.168.10.128"

(2)开启远程访问并开启认证:

JAVA_OPTS="$JAVA_OPTS -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=8899 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=true -Dcom.sun.management.jmxremote.password.file=.../conf/jmxremote.password -Dcom.sun.management.jmxremote.access.file=.../conf/jmxremote.access"

复制并修改授权文件

JAVA_HOME/jre/lib/management下

jmxremote.access和jmxremote.password.template的模板文件,将两个文件复制到CATALINA_BASE/conf目录下

重命名jmxremote.password.template文件为jmxremote.password

修改CATALINA_BASE/conf/jmxremote.access 添加内容:

monitorRole readonly

controlRole readwrite

修改CATALINA_BASE/conf/jmxremote.password 添加内容:

monitorRole 123456

controlRole 123456

赋权限

chmod 600 jmxremote.password

(3)也可以再可在/root/.bash_profile最后追加如下配置:

export JAVA_OPTS="-Dcom.sun.management.jmxremote

-Dcom.sun.management.jmxremote.port=8899

-Dcom.sun.management.jmxremote.ssl=false

-Dcom.sun.management.jmxremote.authenticate=false

-Djava.rmi.server.hostname=192.168.10.128"

使上述配置生效:

source ~/.bash_profile

启动想要监控的java程序;

或者在 /etc/profile 文件中配置;

jstatd连接配置

需要jdk支持,jre不支持;

新建文件jstatd.all.policy(可自定义文件名),内容如下:

grant codebase "file:/usr/local/jdk1.8.0_191/lib/tools.jar" {

permission java.security.AllPermission;

};

执行命令(文件名与上一步相同):

jstatd -J-Djava.security.policy=jstatd.all.policy &

&后台启动,-J后面没有空格,查看1099端口是否正常开启;

相关推荐
yuanbenshidiaos6 小时前
c++---------数据类型
java·jvm·c++
java1234_小锋9 小时前
JVM对象分配内存如何保证线程安全?
jvm
40岁的系统架构师12 小时前
1 JVM JDK JRE之间的区别以及使用字节码的好处
java·jvm·python
寻找沙漠的人13 小时前
理解JVM
java·jvm·java-ee
我叫啥都行13 小时前
计算机基础复习12.22
java·jvm·redis·后端·mysql
bufanjun00115 小时前
JUC并发工具---ThreadLocal
java·jvm·面试·并发·并发基础
东阳马生架构1 天前
JVM简介—1.Java内存区域
jvm
工程师老罗1 天前
Android笔试面试题AI答之SQLite(2)
android·jvm·sqlite
Qzer_4071 天前
jvm字节码中方法的结构
jvm
奇偶变不变2 天前
RTOS之事件集
java·linux·jvm·单片机·算法