面试精选面试java学习之路

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

2021-04-27  本文已影响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的作用域在于方法区和堆。也就是线程共享部分。
常见的垃圾回收算法有四种:

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

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

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

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则说明创建就进入老年代。
至此一些常用的需要调优的参数就说到这了,剩下的我们可以在官网上查看。常用的设置,查看等命令也要记住。下面说点相关的知识点。

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

上一篇下一篇

猜你喜欢

热点阅读