传送门
【JVM】一、JVM体系结构
【JVM】二、JVM垃圾收集器
【JVM】三、JVM内存溢出问题分析查看
【JVM】四、JVM优化-GC调优
上一篇:【JVM】一、JVM体系结构
JVM垃圾收集器
垃圾收集Garbage Collection 简称GC;
哪些内存需要回收?
什么时候回收?
如何回收?
线程私有部分:
- 1、程序计数器;(随线程而生、随线程而灭)
- 2、虚拟机栈;(随线程而生、随线程而灭)
- 3、本地方法栈;(随线程而生、随线程而灭)
线程共享部分 (需要回收):
- 1、堆;(需要考虑垃圾回收问题)
- 2、方法区/元空间;(需要考虑垃圾回收问题)
一、如何判断对象可以回收?
1.1、 可达性分析算法
JVM是通过可达性分析算法来判断一个对象是否需要被回收,其实现思想是通过一个GC Root对象为起始根节点,然后逐个往下去搜索各个对象,看GC Root到对象之间有没有可达路径,如果没有可达路径则表示该对象是不可用的,可以被回收;
JVM中如下几种对象可以作为GC Root:
- 虚拟机栈本地变量表中引用类型所引用的对象;
- 方法区/元空间中类的静态变量所引用的对象;
- 方法区/元空间中类的常量所引用的对象;
- 本地方法栈中Native方法里所引用的对象;
通过上面的分析,我们知道这里面主要是通过 引用 去分析是否有可达路径;
Java中的引用分为4种:(强度依次减弱) - 1、强引用(Strong Reference);
- 2、软引用(Soft Reference);
- 3、弱引用(Weak Reference);
- 4、虚引用(Phantom Reference)
-
- 1、强引用是最普遍的引用,比如 User user = new User(); 这是强引用,垃圾收集器不会回收强引用;
-
- 2、软引用是表示一些对象还有用,但是也不是必须要用的,比如像缓存对象就可以采用软引用,在系统内存不足时,这些软引用是可以被垃圾回收器回收的,如果我们要使用软引用的话,编码的时候,把对象采用jdk中提供的SoftReference类型来包装;
-
- 3、弱引用也是表示一些不是必须的对象,但它比软引用更弱,被弱引用所引用的对象只能生存到下一次垃圾收集之前,当开始垃圾收集时,无论内存是否足够,都会回收弱引用对象;
-
- 4、虚引用PhantomReference是一种特殊的引用,也是最弱的引用关系,用来实现Object.finalize功能,在开发中很少使用;
1.2、 方法区/元空间的回收
在JVM的垃圾回收中,堆内存是回收最频繁也是最多的,方法区/元空间的垃圾收集效率非常低,所以JVM规范中并没有要求一定要回收方法区/元空间,如果方法区/元空间有无用的类信息、常量池,JVM不是必须要回收的;
Hotspot 虚拟机默认会进行类的卸载,如果不想要对无用的类进行卸载,可以加上参数-Xnoclassgc(不卸载),默认情况下Hotspot 虚拟机会卸载类;
查看类的加载和卸载参数:
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -verbose:class -XX:+TraceClassLoading -XX:+TraceClassUnloading
方法区/元空间垃圾回收主要两部分内容:废弃的常量和无用的类;
判断废弃常量:一般是判断没有任何对象引用该常量;
判断无用的类:要满足以下三个条件
- (1)该类所有的实例都已经回收,也就是 Java 堆中不存在该类的任何实例;
- (2)加载该类的 ClassLoader 已经被回收;
- (3)该类对应的 java.lang.Class 对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法;
大量使用动态代理、字节码生成类的应用中,一定要注意方法区/元空间的溢出;
1.3、JVM回收对象的两次标记过程
1、第一次标记
如果对象进行可达性分析算法之后没有发现与GC Roots相连的引用链,那它将会第一次标记并且进行一次筛选;
筛选条件:判断此对象是否有必要执行finalize()方法;
筛选结果:当对象没有覆盖finalize()方法或者finalize()方法已经被JVM执行过,则判定为可回收对象,如果对象有必要执行finalize()方法,则被放入F-Queue队列中,稍后在JVM自动建立低优先级的Finalizer线程(可能多个线程)中触发这个方法;
2、第二次标记
GC对F-Queue队列中的对象进行二次标记;
如果对象在finalize()方法中重新与引用链上的任何一个对象建立了关联,那么第二次标记时则会将它移出“即将回收”集合,如果此时对象还没成功逃脱,那么只能被回收了;
注:finalize() 方法
finalize()是Object类的一个空方法、该方法是被垃圾收集器所调用,一个对象的finalize()方法只会被垃圾收集器自动调用一次,经过finalize()方法逃脱死亡的对象,第二次不会再调用;
不提倡在程序中调用finalize()来进行对象的自救,因为该方法执行的时间不确定,甚至是否被执行也不确定(Java程序的不正常退出),无法保证各个对象的调用顺序(甚至有不同线程中调用);
JVM垃圾回收器回收算法
二、JVM垃圾回收器回收算法
常见的垃圾回收算法:标记-清除算法、复制算法、标记-整理算法、分代收集算法;
1、 标记-清除算法
最基础的收集算法,分为‘ 标记 ’和‘ 清除 ’两个阶段;
- 1.标记
标记出所有需要回收的对象,这里面会经历两次标记,一次标记和二次标记,经过两次标记的对象就可以判定可以回收了; - 2.清除
两次标记后,对还在“?即将回收?”集合的对象进行回收;
优点:基于最基础的可达性分析算法,实现简单,后续的收集算法都是基于这种思想实现的;
缺点:标记和清除效率不高,产生大量不连续的内存碎片,导致创建大对象时找不到连续的空间,不得不提前触发另一次的垃圾回收;
2、复制算法
将可用内存按容量分为大小相等的两块,每次只使用其中一块,当这一块的内存用完了,就将还存活的对象复制到另外一块内存上,然后再把已使用过的内存空间一次清理掉;
优点:实现简单,效率高,解决了标记-清除算法导致的内存碎片问题;
缺点:代价太大,将可分配内存缩小了一半,效率随对象的存活率升高而降低,
一般虚拟机都会采用该算法来回收新生代;
3、标记-整理算法
标记-整理算法是根据老年代的特点而产生的;
- 1 标记
标记过程与上面的标记-清理算法一致,也是基于可达性分析算法,也是两次标记; - 2 整理
和标记-清理不同的是,该算法不是针对可回收对象进行清理,而是根据存活对象进行整理。让存活对象都向一端移动,然后直接清理掉边界以外的内存;
优点:不会像复制算法那样划分两个区域,提高了空间利用率,不会产生不连续的内存碎片;
缺点:效率问题,除了像标记-清除算法的标记过程外,还多了一步整理过程,效率变低;
4、分代收集算法
现在一般虚拟机的垃圾收集都是采用“ 分代收集 ”算法;
根据对象存活周期的不同将内存划分为几块,一般把java堆分为新生代和老年代,JVM根据各个年代的特点采用不同的收集算法;
新生代中,每次进行垃圾回收都会发现大量对象死去,只有少量存活,因此采用复制算法,只需要付出少量存活对象的复制成本就可以完成收集;
老年代中,因为对象存活率较高,采用标记-清理、标记-整理算法来进行回收;
三、JVM垃圾收集器
如上图,一共有7种作用于不同分代的垃圾收集器,如果两个收集器之间存在连线,则说明它们可以搭配使用,垃圾收集器所处区域表示它是属于新生代收集器还是老年代收集器;
新生代收集器:Serial、ParNew、Parallel Scavenge
老年代收集器:CMS、Serial Old、Parallel Old
整堆收集器: G1
1、Serial收集器
新生代收集器,最早的收集器,单线程的,收集时需暂停用户线程的工作。所以有卡顿现象,效率不高,致使java语言的开发团队一直在改进垃圾收集器的算法和实现。但Serial收集器简单,不会有线程交互的开销,是client模式下默认的垃圾收集器。 -client, -server;
参数: -XX:+UseSerialGC
2、ParNew收集器
它是新生代收集器,就是Serial收集器的多线程版本,大部分基本一样,配置参数也一致,单CPU下,ParNew还需要切换线程,可能还不如Serial。
Serial和ParNew收集器可以配合CMS收集器,前者收集新生代,后者CMS收集老年代。
“-XX:+UseConcMarkSweepGC”:指定使用CMS后,会默认使用ParNew作为新生代收集器;
“-XX:+UseParNewGC”:强制指定使用ParNew;
“-XX:ParallelGCThreads=2”:指定垃圾收集的线程数量,ParNew默认开启的收集线程与CPU的数量相同;
3、Parallel Scavenge收集器
它是新生代收集器,基于复制算法,并行的多线程收集器(与ParNew收集器类似),侧重于达到一个可控的吞吐量,虚拟机运行100分钟,垃圾收集花1分钟,则吞吐量为99%。
它提供两个参数设置吞吐量:
-XX:MaxGCPauseMillis 设置大于0的毫秒数,每次GC的时间将保持不超过设置的值。
–XX:GCTimeRatio 垃圾收集时间占整个虚拟机时间的比率,取值0-100
参数:-XX:+UseParallelGC
4、Serial Old收集器
它是Serial收集器的老年代版本,同Serial一样,单线程,可在Client模式下使用,也可在Server模式下使用,采用标记-整理算法;
5、Parallel Old收集器
是Parallel Scavenge的老年代版本,多线程,标记整理算法,它是在1.6开始才提供。
在注重吞吐量和CPU资源的情况, Parallel Scavenge新生代+ Parallel Old老年代是一个很好的搭配。
参数:-XX:+UseParallelOldGC
6、CMS收集器
全称Concurrent Mark Sweep,是追求最短回收停顿时间为目标的收集器,互联网B/S结构的服务器端特别适合此收集器。
基于标记清除算法,它的运作过程分为4个阶段,初始标记、并发标记、重新标记、并发清除,其中初始标记和重新标记需要暂停用户线程,其他是并发执行,所以总体上暂停时间更短。
CMS收集器的缺点:
1并发收集会占用CPU资源,特别是cpu数量小的服务器下,会占用用户线程,导致性能下降;
2会产生浮动垃圾,因为你并发清除的时候用户线程可能还在产生垃圾,这些垃圾没有清除,而且你不能让老年代填满了再清除,你要给用户线程留一定空间,所以jdk1.5默认是老年代65%了就触发回收,jdk1.6则提升到92%,如果预留老年代不够用户线程使用,则启用Serial Old收集,这就会暂停用户线程,导致性能下降。
3这个标记整理算法清理后会产生碎片空间,如果此时要分配一个大对象,则无连续空间,需要一次full gc,性能下降;
参数:-XX:+UseConcMarkSweepGC
7、G1收集器
目前最前沿最先进的垃圾收集器,从JDK1.7u4开始可以使用;
G1收集器的特别:
1并发和并行,充分利用多核cpu,让用户线程不暂停;
2保留了分代收集的特性,不需要其他收集器配合完成各代的回收;
3空间整合,G1不会产生碎片空间,不会触发full gc去回收碎片;
4可预测的停顿,G1追求了停顿预测模型,在一段时间内停顿不超过多少毫秒等;
-XX:+UseG1GC
四、GC日志
1、gc相关参数
- -XX:+PrintGC
Enables printing of messages at every GC. By default, this option is disabled.
允许在每个GC上打印消息。默认情况下,此选项处于禁用状态。 - -XX:+PrintGCApplicationConcurrentTime
Enables printing of how much time elapsed since the last pause (for example, a GC pause). By default, this option is disabled.
启用打印自上次暂停(例如GC暂停)以来经过的时间。默认情况下,此选项处于禁用状态。 - -XX:+PrintGCApplicationStoppedTime
Enables printing of how much time the pause (for example, a GC pause) lasted. By default, this option is disabled.
允许打印暂停(例如GC暂停)持续的时间。默认情况下,此选项处于禁用状态。 - -XX:+PrintGCDateStamps
Enables printing of a date stamp at every GC. By default, this option is disabled.
启用在每个GC上打印日期戳。默认情况下,此选项处于禁用状态。 - -XX:+PrintGCDetails
Enables printing of detailed messages at every GC. By default, this option is disabled.
允许在每个GC上打印详细消息。默认情况下,此选项处于禁用状态。 - -XX:+PrintGCTaskTimeStamps
Enables printing of time stamps for every individual GC worker thread task. By default, this option is disabled.
启用为每个GC工作线程任务打印时间戳。默认情况下,此选项处于禁用状态。 - -XX:+PrintGCTimeStamps
Enables printing of time stamps at every GC. By default, this option is disabled.
启用在每个GC上打印时间戳。默认情况下,此选项处于禁用状态。 - -Xloggc:filename
Sets the file to which verbose GC events information should be redirected for logging. The information written to this file is similar to the output of?-verbose:gc?with the time elapsed since the first GC event preceding each logged event. The?-Xloggc?option overrides?-verbose:gc?if both are given with the same?java?command.
设置要将详细GC事件信息重定向到其中进行日志记录的文件。写入此文件的信息类似于-verbose:gc的输出,其中包含自每个记录的事件之前的第一个gc事件以来经过的时间。-Xloggc选项重写-verbose:gc,如果这两个选项都是用同一个java命令给出的。 - -XX:+HeapDumpOnOutOfMemoryError
Enables the dumping of the Java heap to a file in the current directory by using the heap profiler (HPROF) when a?java.lang.OutOfMemoryError?exception is thrown. You can explicitly set the heap dump file path and name using the?-XX:HeapDumpPath?option. By default, this option is disabled and the heap is not dumped when an?OutOfMemoryError?exception is thrown.
启用在引发Java.lang.OutOfMemoryError异常时使用堆探查器(HPROF)将Java堆转储到当前目录中的文件。可以使用-XX:heap dump path选项显式设置堆转储文件的路径和名称。默认情况下,此选项被禁用,并且在引发OutOfMemoryError异常时不会转储堆。 - -XX:HeapDumpPath=path
Sets the path and file name for writing the heap dump provided by the heap profiler (HPROF) when the?-XX:+HeapDumpOnOutOfMemoryError?option is set. By default, the file is created in the current working directory, and it is named?java_pidpid.hprof?where?pid?is the identifier of the process that caused the error. The following example shows how to set the default file explicitly (%p?represents the current process identificator):
设置在设置-XX:+HeapDumpOnOutOfMemoryError选项时用于写入堆分析器(HPROF)提供的堆转储的路径和文件名。默认情况下,文件是在当前工作目录中创建的,名为java_pidpid.hprof,其中pid是导致错误的进程的标识符。
2、gc相关参数下面的示例演示如何显式设置默认文件(p表示当前进程标识符)
-XX:HeapDumpPath=./java_pid%p.hprof
-XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:d:/jvm/jvmgc.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=d:/jvm/heapdump.hprof
- GC日志
- Full GC日志
3、JDK命令行监控工具
文档:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/index.html
3.1、JDK的主要命令行监控工具:
jinfo
Jinfo pid , 比如 jinfo 3337
查看正在运行的Java程序的扩展参数信息
查看JVM的参数 jinfo -flags 3337
查看java系统属性 jinfo -sysprops 3337
jps
jps:显示所有的HotSpot虚拟机进程,可选参数:
-q 只输出LVMID,LVMID与linux的PID一致
-m 输出虚拟机进程启动时传给主类main()的参数
-l 输出主类的全名或jar包的路径
-v 输出虚拟机进程启动时JVM参数
使用举例:
jps -v
jps -l
jstat
3.2、jstat:虚拟机监控命令,收集虚拟机运行时的各方面数据,可选参数:
-class 监视类装载、卸载及耗时,比如:jstat –class 4120(4120是进程号)
-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:最大持有次数限制
3.3、jmap
jmap:可用于生成java堆转储快照文件
如果不用jmap,还可以采用一些“暴力”手段获得java堆转储快照文件,如:
-XX:+HeapDumpOnOutOfMemoryError
可选参数:(有些参数在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强制生成堆栈信息
3.4、jhat
jhat:用于分析虚拟机生成的堆转储快照文件,比如jmap生成的堆转储快照文件,不过这个工具的功能较弱,实践中更多使用VisualVM和Eclipse Memory Analyzer分析;
3.5、jstack
jstack:生成虚拟机中当前执行的线程快照信息,目的是查出当前线程有没有卡顿、长时间没有响应的情况,卡顿等待的原因可能为等待外部资源、数据库连接、网络资源、设备资源、死循环、锁等,可选参数:
-F 当正常输出无响应时强制输出线程堆栈;
-l 除显示堆栈外,显示锁的附加信息;
-m 如果调用到本地方法,显示C/C++的堆栈信息;
举例:jstack -l pid
4、JVM可视化监视
JDK给我们提供了两个功能强大的JVM可视化工具:
4.1、JConsole --jdk1.5即提供
4.2、VisualVM --jdk1.6 u7才提供,但也可以监控jdk1.4.2这样老版本的java虚拟机;
4.3、JCnsole
Java监控与管理控制台,windows下双击jdk安装目录bin下的jconsole.exe启动,可以监控本地java虚拟机,也可监控远程java虚拟机;
概要是总体信息
内存标签页相当于jstat命令
线程标签页相当于jstack命令
注:使用图形化工具,要开启JMX
4.4、VisualVM
可能是最好的java虚拟机监控工具,其功能比收费工具JProfiler也不逊色多少;
在jdk安装目标bin下双击jvisualvm.exe启动VisualVM;
VisualVM基于NetBeans开发,可以支持安装多种插件,在菜单的工具–>插件一栏可以选择安装自己需要的插件;
注意:
JDK1.5及之前的版本要开启JMX功能,需在程序启动前加上参数-Dcom.sun.management.jmxremote,JDK的一些可视化工具需要开启JMX功能才能使用,JDK1.5之后的版本默认是开启了JMX的,无需加该参数;
五、JVM内存溢出分析
Memory Analysis (dump文件) mat
下载:https://www.eclipse.org/mat/
直接解压即可使用,和eclipse一样;