美文网首页面试精选面试java学习之路
java大厂面试题整理(六)JVM常用命令和参数

java大厂面试题整理(六)JVM常用命令和参数

作者: 唯有努力不欺人丶 | 来源:发表于2021-04-27 19:44 被阅读0次

    关于JVM的面试题由死锁引出。

    死锁及定位

    从宏观上死锁产生的原因:死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象。若无外力干涉他们都将无法推进下去。通俗点说:线程A持有锁1想要获取锁2.线程B持有锁2想要获取锁1.如上代码A,B会僵持下去,如无外力干涉则会无法继续往下。当然这个是一个很简单的语言表述。简单说下产生死锁的主要原因:

    1. 系统资源不足。
    2. 进程运行推进的顺序不合适
    3. 资源分配不当

    下面让我们代码演示一下死锁:

    public class Test16 {
        
        public static void main(String[] args) {
            String a = "aaa";
            String b = "bbb";
            new TestLock(a, b).start();
            new TestLock(b, a).start();
        }
    
    }
    class TestLock extends Thread{
    
        private String lockA;
        private String lockB;
        public TestLock(String a,String b) {
            this.lockA = a;
            this.lockB = b;
        }
        
        @Override
        public void run() {
            synchronized(lockA){
                try {
                    System.out.println(Thread.currentThread().getName()+"\t 持有锁A,试图获取锁B");
                    TimeUnit.SECONDS.sleep(2l);
                    
                } catch (Exception e) {
                    e.printStackTrace();
                }
                synchronized (lockB) {
                    System.out.println(Thread.currentThread().getName()+"获取锁B");
                }
            }
        }
    }
    

    其实这个代码比较容易实现。但是要如何证明死锁呢(找到死锁在哪里)?这个点是下面的关系:出现死锁一定程序卡住不往下走了。但是程序卡住不往下走了不一定是死锁。所以我们如何在程序不走了的时候确定是因为出现了死锁而不是while死循环呢?
    下面两个至关重要的命令(windows)上的:
    jps -l (获取java程序的进程)
    jstack xxx(进程号) 找到死锁查看
    如下demo(我刚刚死锁的demo还在跑):

    控制台查看
    注意这个死锁的查看我们可以直接看原因,在下面位置:
    死锁位置和原因

    JVM内存结构

    jvm体系结构概括

    而GC的作用域在于方法区和堆。也就是线程共享部分。
    常见的垃圾回收算法有四种:

    • 引用计数(每次有引用+1.但是缺点是较难处理循环引用。所以JVM的实现一般不采用这种方式。)
    • 复制 (年轻代使用。复制->清空->互换。缺点是浪费空间,有些大对象会耗时。)
    • 标记清除(先标记要回收的对象,统一回收。优点:没有大面积复制,节约空间。缺点是会产生大量空间碎片。)
    • 标记整理(标记回收的对象,回收后统一滑动到一端,也就是整理。优点是没有看空间碎片。缺点是整理的时候移动耗时)

    java中什么是垃圾?
    简单来说就是内存中已经不再被使用到的空间就是垃圾。
    而如何确定一个对象是不是可以被回收?
    两种方式:引用计数法.枚举根节点做可达性分析。
    引用计数法比较号理解,上面也说了,就是引用一次+1.引用计数法为0就是没有引用。但是反过来:引用计数不为0不代表就有用,这个时候就要做可达性分析。
    可达性分析:基本思路是通过一系列名为GC Roots的对象作为起始点。从这个对象开始向下搜索,如果一个对象到GC Roots没有任何引用链相连,说明此对象不可用。
    哪些对象可以作为GC Roots对象呢?

    1. 虚拟机栈(栈帧中的局部变量区,也叫做局部变量表)中引用的对象。
    2. 方法区中的类静态属性引用的对象。
    3. 方法区中常量引用的对象。
    4. 本地方法栈中JNI(native方法)引用的对象。

    如何对JVM调优和参数配置?如何盘点查看JVM系统默认值?
    说这道题之前起码要对JVM的参数有一个基本的了解。JVM的参数类型有三种:

    • 标配参数(这个就是从最开始就一直在的,比较简单。比如-version -help -showversion等)
    • X参数(了解)(-Xint 解释执行 -Xcomp 第一次使用就编译成本地代码。 -Xmixed 混合模式)
    • XX参数(重点!)
      这个类型的参数还可以细分为多种类型:
      • 布尔类型 -XX: +/-某个属性值(+表示开启,-表示关闭)
        我们可以在控制台查看某个java线程是不是开启了某个值。例如我们判断下某线程是否打印GC收集细节。demo如下:


        是不是开启了PrintGCDetails属性

        结果很明显,没开启、因为是减号(注意这里单词不要拼写错了)
        然后我在启动的时候设置参数是开启:


        开启这个参数(eclipse用法)
        然后再次打印:
        这次打印出来的信息是开启GC收集细节了
      • KV设值类型 -XX:属性key=属性值value
        说白了就是键值对的形式设置。比如设置堆内存。这种不是开启或关闭,而是需要具体的值。依然用一个demo来展示:


        查看线程元空间大小

        注意这个第一次参数名称我打错了,然后这个提示其实挺人性化的,就是没这个参数,并且这个参数是严格区分大小写的。注意点吧。然后这个是默认的,我们如果在启动的时候配置一下试试:


        设置元空间大小
        设置完成后获取参数值
        我们可以很明显看到启动的时候设置属性值以后,查看出来的是之前设置的。
        其实别的也是类型的。再拿个参数试一下(因为上面元空间设置和实际优点出入)我们可以用年轻的要经过多少次晋级到老年区这个参数来举例子:
        默认是15次
        设置成10次以后重新启动
        结果从十五次变成了十次

        这里再说一个小技巧:如何获取某线程的全部参数:

    jinfo -flags xxx
    

    如下demo:


    一个线程的全部参数

    这个第一个参数是vm默认的,第二个是特别设置过的。

    这里有个坑:-Xms和-Xmx两个参数算是什么格式呢?其实这种写法是缩写。因为太常用了所以有了简写。这两个参数原型如下:

    -Xms == -XX:InitialHeapSize
    -Xmx == -XX:MaxHeapSize

    这种有缩写的参数还有一些,下面简单罗列一下:

    -Xss == -XX:ThreadStackSize 栈空间的大小

    而且这个参数有个很特殊的地方:如果采用默认的参数来说,根据系统不同会有区别(采用默认的话会显示为0)


    参数默认值
    -Xmn == -XX:年轻代的大小(一般都直接使用默认的)

    下面这个参数是需要额外配置的:那就是元空间的大小
    正常java8中元空间的大小理论上的物理内存的大小,但是!!其实这个在配置的时候jvm是有个默认值的,而这个值还很小。下面是默认的大小:

    元空间默认大小
    注意了,这个单位是字节,也就是换算一下,其实这个元空间的默认大小只有20多M。所以虽然理论上物理内存多大就能存多大,但是实际上不更改这个配置还是该溢出就溢出的!所以说一般元空间都要往大点配置。
    元空间换算

    在JVM初始化会有一些默认的参数,我们可以用一个命令来查询JVM初始的参数:

    java -XX:+PrintFlagsInitial
    

    还有一个命令是查看JVM最终的参数:

    java -XX:+PrintFlagsFinal
    

    而且我们在查看的时候会发现参数表现形式有两种。一种是直接等于,还有一种是:=。如下截图:


    :=的现象

    其实很简单,普通的等于是初始值,而:=是代表这个值是人为改过或者jvm根据加载而不一样的参数。
    最后还有一个很重要的命令,查看JVM比较重要的几个参数值:

    java -XX:+PrintCommandLineFlags -version
    
    几个重要且常用的参数

    这几个参数中最重要的是最后一个参数:

    -XX:+UseParallelGC  //并行垃圾回收器
    

    这个参数是当前JVM使用的垃圾回收器(垃圾回收算法是算法,而垃圾回收器是算法的落地实现)。这个垃圾回收器是可改的。比如改成串行垃圾回收器 -XX:+UseSerialGC

    打印GC详细信息

    -XX:+PrintGCDetails
    

    这个打印出来的结果如下:


    打印GC详情

    这个参数看似莫名其妙,其实是有规律的。简而言之公式:
    [哪个区(年轻代/老年代/元空间):GC前大小->GC后大小(空间占用大小)]xxxxx(如果后面还有参数是堆的。)。


    GC参数解读
    设置JVM中年轻代的空间分配(默认的是8。也就是eden:from:to = 8:1:1)。而这个x设置的参数值是eden的比例,后面from和to都是1:1.
    -XX:SurvivorRatio=x
    

    如下图:


    正常启动默认x的值是8

    启动的时候修改为4.


    启动线程时指定参数
    启动后查询结果
    下面再说一个内存分配的参数,新生代和老年代内存大小的配置(默认x是2):
    -XX:NewRatio=x
    

    这个x也是我们配置的。默认是老年代所占份数。新生代默认占一份。
    这个反正我个人觉得挺抽象的。虽然参数名是NewRatio。新的比率,但是这个值确实控制老年代份数的。
    打个比方:如果x是2,则表示新生代一份,老年代二份。也就是新生代是三分之一。
    如果x是6,说明新生代一份,老年代6份,新生代是七分之一大小。
    一般我们实际中不太用调。用默认的三分之一就可以。
    设置垃圾的最大年龄(新生代经历多少次垃圾回收能进入老年代):

    -XX:MaxTenuringThreshold=xx
    

    这个默认的是15次。而且这个值我们只能在0-15中间设置,不能超过15,也不能小于0。如果是0则说明创建就进入老年代。
    至此一些常用的需要调优的参数就说到这了,剩下的我们可以在官网上查看。常用的设置,查看等命令也要记住。下面说点相关的知识点。

    本篇笔记就记到这里,如果稍微帮到你了记得点个喜欢点个关注。也祝大家工作顺顺利利,所有努力都有所收获!

    相关文章

      网友评论

        本文标题:java大厂面试题整理(六)JVM常用命令和参数

        本文链接:https://www.haomeiwen.com/subject/ctjwlltx.html