java大厂面试题整理(六)JVM常用命令和参数
关于JVM的面试题由死锁引出。
死锁及定位
从宏观上死锁产生的原因:死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象。若无外力干涉他们都将无法推进下去。通俗点说:线程A持有锁1想要获取锁2.线程B持有锁2想要获取锁1.如上代码A,B会僵持下去,如无外力干涉则会无法继续往下。当然这个是一个很简单的语言表述。简单说下产生死锁的主要原因:
- 系统资源不足。
- 进程运行推进的顺序不合适
- 资源分配不当
下面让我们代码演示一下死锁:
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对象呢?
- 虚拟机栈(栈帧中的局部变量区,也叫做局部变量表)中引用的对象。
- 方法区中的类静态属性引用的对象。
- 方法区中常量引用的对象。
- 本地方法栈中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则说明创建就进入老年代。
至此一些常用的需要调优的参数就说到这了,剩下的我们可以在官网上查看。常用的设置,查看等命令也要记住。下面说点相关的知识点。
本篇笔记就记到这里,如果稍微帮到你了记得点个喜欢点个关注。也祝大家工作顺顺利利,所有努力都有所收获!