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)秘籍 文章被收录于专栏

主要归纳JVM知识点

全部评论

相关推荐

点赞 评论 收藏
分享
3 4 评论
分享
牛客网
牛客企业服务