java虚拟机秘籍(JVM_2)
接上一篇 JVM_1 :https://www.nowcoder.com/discuss/1151960
三、 深入学习
3.1 JVM参数
3.1.1 标准参数
-version -help -server -cp
3.1.2 -X参数
非标准参数,各个jdk版本中可能会变化
-Xint 解释执行模式
-Xcomp 编译模式
-Xmixed 混合模式 由JVM自己决定
3.1.3 -XX参数
使用最多的参数,非标准化参数,主要用于JVM调优和Debug
a.Boolean类型:-XX:[+-] + -符号表示启用/禁用
例如:-XX:+UseG1GC
b.非Boolean类型 -XX:= 表示属性的值
例如:-XX:MaxHeapSize=1024M
3.1.4 其他参数
-Xms1024M ==> -XX:InitialHeapSize=1024M
-Xmx1024M ==> -XX:MaxHeapSize=1024M
-Xss512M ==> -XX:ThreadStackSize=512M
3.1.5 查看参数
java -XX:+PrintFlagsFinal -version > flags.txt
其中“=”表示默认值 “:=”表示被程序员或者JVM修改过的值
3.1.6 常用参数含义
参数 | 含义 | 说明 |
-XX:CICompilerCount=3 | 最大并行编译数 | 如果设置大于1,虽然编译速度 会提高,但是同样影响系统稳定 性,会增加JVM崩溃的可能 |
-XX:InitialHeapSize=100M | 初始化堆大小 | 简写-Xms100M |
-XX:MaxHeapSize=100M | 最大堆大小 | 简写-Xmx100M |
-XX:NewSize=20M | 设置年轻代的大小 | |
-XX:MaxNewSize=50M | 年轻代最大大小 | |
-XX:OldSize=50M | 设置老年代大小 | |
XX:MetaspaceSize=50M | 设置方法区大小 | |
-XX:MaxMetaspaceSize=50M | 方法区最大大小 | |
-XX:+UseParallelGC | 使用ParallelGC | 新生代,吞吐量优先 |
-XX:+UseParallelOldGC | 使用ParallelOldGC | 老年代,吞吐量优先 |
-XX:+UseConcMarkSweepGC | 使用CMS | 老年代,停顿时间优先 |
-XX:+UseG1GC | 使用G1 GC | 新生代、老年代停顿时间优先 |
-XX:NewRatio | 新生代和老年代的比值 | 比如-XX:Ratio=4,则表示新生代:老年代=1:4,也就是新生代占整个堆内存的1/5 |
-XX:SurvivorRatio | 两个S区和Eden区的比值 | 比如-XX:SurvivorRatio=8,也就是(S0+S1):Eden=2:8,也就是一个S占整个新生代的1/10 |
-XX:+HeapDumpOnOutOfMemoryError | 启动堆内存溢出打印 | 当JVM堆内存发生溢出时,也就是OOM,自动生成dump文件 |
-XX:HeapDumpPath=heap.hprof | 指定堆内存溢出打印目录 | 表示在当前目录生成一个 heap.hprof文件 |
-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps | 打印GC日志 | |
-XX:ThreadStackSize=512k | 设置每个线程的堆栈大小 | -Xss128k |
-XX:MaxTenuringThreshold=6 | 提升年老代的最大临界值 默认值为 15 | 在对象头中,只占4bit,该值最大值为15 |
-XX:InitiatingHeapOccupancyPercent | 启动并发GC周期时堆内存使用占比 | G1之类的垃圾收集器用它来触发并发GC周期,基于整个堆的使用率,而不只是某一代内存的使用比.值为 0 则表示”一直执行GC循环”.默认值为 45. |
-XX:G1HeapWastePercent | 允许的浪费堆空间的占比 | 默认是10%,如果并发标记可回收的空间小于10%,则不会触发MixedGC。 |
-XX:MaxGCPauseMillis=200ms | G1最大停顿时间 | 暂停时间不能太小,太小的话就会导致出现G1跟不上垃圾产生的速度。最终退化成Full GC。所以对这个参数的调优是一个持续的过程,逐步调整到最佳状态。 |
-XX:ConcGCThreads=n | 并发垃圾收集器使用的线程数量 | 默认值随JVM运行的平台不同而不同 |
-XX:G1MixedGCLiveThresholdPercent=65 | 混合垃圾回收周期中要包括的旧区域设置占用率阈值 | 默认占用率为 65% |
-XX:G1MixedGCCountTarget=8 | 设置标记周期完成后,对存活数据上限为G1MixedGCLIveThresholdPercent的旧区域执行混合垃圾回收的目标次数 | 默认8次混合垃圾回收,混合回收的目标是要控制在此目标次数以内 |
-XX:G1OldCSetRegionThresholdPercent=1 | 描述Mixed GC时,Old Region被加入到CSet中 | 默认情况下,G1只把10%的OldRegion加入到CSet中 |
3.2 常用命令
3.2.1 jps
查看java进程
3.2.2 jinfo
实时查看和调整JVM配置参数
jinfo -flag 参数名 pid 查看参数的值
例如; jinfo -flag MaxHeapSize 2169928
jinfo -flag InitialHeapSize 2169928
修改:参数只有被标记为manageable 的flags可以被实时修改
jinfo -flag [+|-] PID
jinfo -flag = PID
查看曾经赋过值的一些参数
jinfo -flags PID
3.2.3 jstat
查看虚拟机性能统计信息;
查看类装载信息
jstat -class PID 1000 10 查看该java进程的类装载信息,每1000毫秒输出一次,共10次
查看垃圾收集信息
jstat -gc PID 1000 10 查看该java进程的gc信息,每1000毫秒输出一次,共10次
3.2.4 jstack
查看线程堆栈信息
用法:jstack PID
死锁案例:
public class DeadLockDemo { public static void main(String[] args) { DeadLock deadLock1 = new DeadLock(true); DeadLock deadLock2 = new DeadLock(false); Thread thread1 = new Thread(deadLock1); Thread thread2 = new Thread(deadLock2); thread1.start(); thread2.start(); } static class DeadLock implements Runnable { private boolean flag; DeadLock(boolean flag) { this.flag = flag; } @Override public void run() { if (flag) { while (true) { synchronized (MyLock.obj1) { System.out.println(Thread.currentThread().getName() + "--if中获得obj1锁"); synchronized (MyLock.obj2) { System.out.println(Thread.currentThread().getName() + "--if中获得obj2锁"); } } } } else { while (true) { synchronized (MyLock.obj2) { System.out.println(Thread.currentThread().getName() + "--else中获得obj2锁"); synchronized (MyLock.obj1) { System.out.println(Thread.currentThread().getName() + "--else中获得obj1锁"); } } } } } } static class MyLock { public static final Object obj1 = new Object(); public static final Object obj2 = new Object(); } }
运行结果:
jstack分析,拉到最后:
3.2.5 jmap
生成堆转储快照:
打印堆内存相关信息: jmap heap PID
dump出堆内存相关信息: jmap -dump:format=b,file=heap.hprof PID
设置JVM参数,当堆内存溢出时,自动dump出文件:
XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=heap.hprof
四、 性能优化
JVM性能优化可以分为代码层面和非代码层面。代码层面一般结合字节码进行优化,非代码层面一般从内存,gc,CPU占用率等方面进行优化。JVM一般不需要优化。
4.1 内存
4.1.1 内存分配
正常情况下不需要设置,但在秒杀和促销的场景下,需要调整。默认情况下young:old=1:2,秒杀的情况下,就可能出现young区内存不够
4.1.2 内存溢出(OOM)
一般有两种原因:1.大并发场景 2:内存泄漏导致OOM
4.1.2.1 大并发(秒杀)优化
a.浏览器缓存、本地缓存、验证码
b.CDN静态资源服务器
c.集群+负载均衡
d.动静态资源分离、限流(基于令牌桶、漏桶算法)
e.应用级别缓存、接口防刷限流、队列、tomcat性能优化
f.异步消息中间件
g.Redis热点数据对象缓存(数据预热)
h.分布式锁、数据库锁
i.5分钟内没有支付订单,取消订单,恢复库存
4.1.2.2 内存泄漏导致内存溢出
ThreadLocal 使用完未删除,会导致内存泄漏
4.2 GC
4.2.1 是否选用G1
官网:https://docs.oracle.com/javase/8/docs/technotes/guides/vm/G1.html#use_cases
a.50%以上的堆被存活对象占用
b.对象分配和晋升速度变化比较大
c.垃圾回收时间比较长
4.2.2 G1调优
a.使用G1垃圾收集器:-XX:+UseG1GC
获取GC日志,使用GCViewer分析吞吐量和响应时间
b.调整内存大小之后,再获取gc日志分析
可以调整内存的大小 -Xms300M -Xmx300M
最大停顿时间:-XX:MaxGCPauseNillis=200
启动并发GC时堆内存占用百分比:-XX:InitiatingHeapOccupancyPercent=45
4.3 JVM性能优化指南
4.4. 常见问题
4.4.1 内存泄漏与内存溢出的区别
内存泄漏是指不再使用的对象无法得到及时的回收,持续占用内存空间,从而造成内存空间的浪费。 内存泄漏很容易导致内存溢出,但内存溢出不一定是内存泄漏导致的。
4.4.2 young GC会有stw嘛?
无论什么 GC,都会发送 stop-the-world,区别是发生的时间长短。而这个时间跟垃圾收集器又有关 系,Serial、PartNew、Parallel Scavenge 收集器无论是串行还是并行,都会挂起用户线程,而 CMS
和 G1 在并发标记时,是不会挂起用户线程的,但其它时候一样会挂起用户线程,stop the world 的时 间相对来说就小很多了
4.4.3 major gc 和full gc的区别
Major GC在很多参考资料中是等价于 Full GC 的,我们也可以发现很多性能监测工具中只有 Minor GC 和 Full GC。一般情况下,一次 Full GC 将会对年轻代、老年代、元空间以及堆外内存进行垃圾回收。触 发 Full GC 的原因有很多:当年轻代晋升到老年代的对象大小,并比目前老年代剩余的空间大小还要大 时,会触发 Full GC;当老年代的空间使用率超过某阈值时,会触发 Full GC;当元空间不足时(JDK1.7 永久代不足),也会触发 Full GC;System.gc() 也会安排一次 Full GC。
4.4.4 什么是直接内存
Java的NIO库允许Java程序使用直接内存。直接内存是在java堆外的、直接向系统申请的内存空间。通 常访问直接内存的速度会优于Java堆。因此出于性能的考虑,读写频繁的场合可能会考虑使用直接内 存。由于直接内存在java堆外,因此它的大小不会直接受限于Xmx指定的最大堆大小,但是系统内存是 有限的,Java堆和直接内存的总和依然受限于操作系统能给出的最大内存
4.4.5 垃圾判断方式
引用计数法:指的是如果某个地方引用了这个对象就+1,如果失效了就-1,当为0就会回收但是JVM没 有用这种方式,因为无法判定相互循环引用(A引用B,B引用A)的情况。
根可达: 通过一种GC ROOT的对象(方法区中静态变量引用的对象等-static变量)来判断,如果有 一条链能够到达GC ROOT就说明,不能到达GC ROOT就说明可以回收。
4.4.6 不可达对象一定要被回收嘛
即使在可达性分析法中不可达的对象,也并非是“非死不可”的,这时候它们暂时处于“缓刑阶段”,要真 正宣告一个对象死亡,至少要经历两次标记过程;可达性分析法中不可达的对象被第一次标记并且进行 一次筛选,筛选的条件是此对象是否有必要执行 finalize 方法。当对象没有覆盖 finalize 方法,或 finalize 方法已经被虚拟机调用过时,虚拟机将这两种情况视为没有必要执行。 被判定为需要执行的对象将会被放在一个队列中进行第二次标记,除非这个对象与引用链上的任何一个 对象建立关联,否则就会被真的回收。
4.4.7 为什么要区分新生代和老年代
虚拟机的垃圾收集都采用分代收集算法,这种算法没有什么新的思想,只是根据对象存活周期的不 同将内存分为几块。一般将 java 堆分为新生代和老年代,这样我们就可以根据各个年代的特点选择合 适的垃圾收集算法。 比如在新生代中,每次收集都会有大量对象死去,所以可以选择复制算法,只需要付出少量对象的复制 成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分 配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集。
4.4.8 G1和CMS的区别是什么?
CMS 主要集中在老年代的回收,而 G1 集中在分代回收,包括了年轻代的 Young GC 以及老年代的 Mix GC;G1 使用了 Region 方式对堆内存进行了划分,且基于标记整理算法实现,整体减少了垃圾碎片的 产生;在初始化标记阶段,搜索可达对象使用到的 Card Table,其实现方式不一样。
4.4.9 方法区中的无用类回收
方法区主要回收的是无用的类,那么如何判断一个类是无用的类的呢? 判定一个常量是否是“废弃常量”比较简单,而要判定一个类是否是“无用的类”的条件则相对苛刻许多。 类需要同时满足下面 3 个条件才能算是 “无用的类” :
a-该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。
b-加载该类的 ClassLoader 已经被回收。
c-该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
#面试##JVM#主要归纳JVM知识点