Skip to content

Latest commit

 

History

History
734 lines (383 loc) · 30.5 KB

File metadata and controls

734 lines (383 loc) · 30.5 KB

请谈谈你对 OOM 的认识?

GC 垃圾回收算法和垃圾收集器的关系?分别是什么请你谈谈?

怎么查看服务器默认的垃圾收集器是哪个?生产上如何配置垃圾收集器的?谈谈你对垃圾收集器的理解?

G1 垃圾收集器?

生产环境服务器变慢,诊断思路和性能评估谈谈?

假如生产环境出现 CPU 占用过高,请谈谈你的分析思路和定位

类加载子系统

类加载机制?类加载过程

Java 虚拟机把描述类的数据从 Class 文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的 Java 类型,这就是虚拟机的加载机制

类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载、验证、准备、解析、初始化、使用和卸载七个阶段。(验证、准备和解析又统称为连接,为了支持Java语言的运行时绑定,所以解析阶段也可以是在初始化之后进行的。以上顺序都只是说开始的顺序,实际过程中是交叉的混合式进行的,加载过程中可能就已经开始验证了)

什么是类加载器,类加载器有哪些?这些类加载器都加载哪些文件?

启动类加载器(引导类加载器,Bootstrap ClassLoader)

  • 这个类加载使用 C/C++ 语言实现,嵌套在 JVM 内部
  • 它用来加载 Java 的核心库(JAVA_HOME/jre/lib/rt.jarresource.jarsun.boot.class.path路径下的内容),用于提供 JVM 自身需要的类
  • 并不继承自 java.lang.ClassLoader,没有父加载器
  • 加载扩展类和应用程序类加载器,并指定为他们的父类加载器
  • 出于安全考虑,Bootstrap 启动类加载器只加载名为java、Javax、sun等开头的类

扩展类加载器(Extension ClassLoader)

  • Java 语言编写,由sun.misc.Launcher$ExtClassLoader实现
  • 派生于 ClassLoader
  • 父类加载器为启动类加载器
  • java.ext.dirs 系统属性所指定的目录中加载类库,或从 JDK 的安装目录的jre/lib/ext 子目录(扩展目录)下加载类库。如果用户创建的 JAR 放在此目录下,也会自动由扩展类加载器加载

应用程序类加载器(也叫系统类加载器,AppClassLoader)

  • Java 语言编写,由 sun.misc.Lanucher$AppClassLoader 实现
  • 派生于 ClassLoader
  • 父类加载器为扩展类加载器
  • 它负责加载环境变量classpath或系统属性 java.class.path 指定路径下的类库
  • 该类加载是程序中默认的类加载器,一般来说,Java 应用的类都是由它来完成加载的
  • 通过 ClassLoader#getSystemClassLoader() 方法可以获取到该类加载器

用户自定义类加载器

在 Java 的日常应用程序开发中,类的加载几乎是由 3 种类加载器相互配合执行的,在必要时,我们还可以自定义类加载器,来定制类的加载方式

为什么要自定义类加载器?
  • 隔离加载类
  • 修改类加载的方式
  • 扩展加载源(可以从数据库、云端等指定来源加载类)
  • 防止源码泄露(Java 代码容易被反编译,如果加密后,自定义加载器加载类的时候就可以先解密,再加载)

多线程的情况下,类的加载为什么不会出现重复加载的情况?

双亲委派

什么是双亲委派机制?它有啥优势?可以打破这种机制吗?

Java 虚拟机对 class 文件采用的是按需加载的方式,也就是说当需要使用该类的时候才会将它的 class 文件加载到内存生成 class 对象。而且加载某个类的 class 文件时,Java 虚拟机采用的是双亲委派模式,即把请求交给父类处理,它是一种任务委派模式。

工作过程

  • 如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行;
  • 如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器;
  • 如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式

优势

  • 避免类的重复加载,JVM 中区分不同类,不仅仅是根据类名,相同的 class 文件被不同的 ClassLoader 加载就属于两个不同的类(比如,Java中的Object类,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,如果不采用双亲委派模型,由各个类加载器自己去加载的话,系统中会存在多种不同的 Object 类)
  • 保护程序安全,防止核心 API 被随意篡改,避免用户自己编写的类动态替换 Java 的一些核心类,比如我们自定义类:java.lang.String

自定义了一个String,那么会加载哪个String?

针对java.*开头的类,jvm的实现中已经保证了必须由bootstrp来加载

简单说说你了解的类加载器,可以打破双亲委派么,怎么打破。

思路: 先说明一下什么是类加载器,可以给面试官画个图,再说一下类加载器存在的意义,说一下双亲委派模型,最后阐述怎么打破双亲委派模型。

我的答案:

1) 什么是类加载器?

类加载器 就是根据指定全限定名称将class文件加载到JVM内存,转为Class对象。

  • 启动类加载器(Bootstrap ClassLoader):由C++语言实现(针对HotSpot),负责将存放在<JAVA_HOME>\lib目录或-Xbootclasspath参数指定的路径中的类库加载到内存中。
  • 其他类加载器:由Java语言实现,继承自抽象类ClassLoader。如:
  • 扩展类加载器(Extension ClassLoader):负责加载<JAVA_HOME>\lib\ext目录或java.ext.dirs系统变量指定的路径中的所有类库。
  • 应用程序类加载器(Application ClassLoader)。负责加载用户类路径(classpath)上的指定类库,我们可以直接使用这个类加载器。一般情况,如果我们没有自定义类加载器默认就是用这个加载器。

2)双亲委派模型

双亲委派模型工作过程是:

如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成。每个类加载器都是如此,只有当父加载器在自己的搜索范围内找不到指定的类时(即ClassNotFoundException),子加载器才会尝试自己去加载。

双亲委派模型图:

img

3)为什么需要双亲委派模型?

在这里,先想一下,如果没有双亲委派,那么用户是不是可以自己定义一个java.lang.Object的同名类java.lang.String的同名类,并把它放到ClassPath中,那么类之间的比较结果及类的唯一性将无法保证,因此,为什么需要双亲委派模型?防止内存中出现多份同样的字节码

4)怎么打破双亲委派模型?

打破双亲委派机制则不仅要继承ClassLoader类,还要重写loadClass和findClass方法。



内存管理

Java内存结构?

方法区和堆是所有线程共享的内存区域;而Java栈、本地方法栈和程序计数器是线程私有的内存区域。

  • Java堆是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。
  • 方法区用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
  • 程序计数器(Program Counter Register)是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。
  • JVM栈(JVM Stacks),与程序计数器一样,也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
  • 本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。

内存泄露和内存溢出的区别?

内存泄露 memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。

内存溢出 out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出。

memory leak会最终会导致out of memory!

内存泄漏时,如何定位问题代码

Java 的内存泄漏问题比较难以定位,下面针对一些常见的内存泄漏场景做介绍:

  1. 持续在堆上创建对象而不释放。例如,持续不断的往一个列表中添加对象,而不对列表清空。这种问题,通常可以给程序运行时添加 JVM 参数-Xmx 指定一个较小的运行堆大小,这样可以比较容易的发现这类问题。
  2. 不正确的使用静态对象。因为 static 关键字修饰的对象的生命周期与 Java 程序的运行周期是一致的,所以垃圾回收机制无法回收静态变量引用的对象。所以,发生内存泄漏问题时,我们要着重分析所有的静态变量。
  3. 对大 String 对象调用 String.intern()方法,该方法会从字符串常量池中查询当前字符串是否存在,若不存在就会将当前字符串放入常量池中。而在 jdk6 之前,字符串常量存储在 PermGen 区的,但是默认情况下 PermGen 区比较小,所以较大的字符串调用此方法,很容易会触发内存溢出问题。
  4. 打开的输入流、连接没有争取关闭。由于这些资源需要对应的内存维护状态,因此不关闭会导致这些内存无法释放。

Java的内存泄漏定位一般是比较困难的,需要使用到很多的实践经验和调试技巧。下面是一些比较通用的方法:

  • 可以添加-verbose:gc启动参数来输出Java程序的GC日志。通过分析这些日志,可以知道每次GC后内存是否有增加,如果在缓慢的增加的那,那就有可能是内存泄漏了(当然也需要结合当前的负载)。如果无法添加这个启动参数,也可以使用jstat来查看实时的gc日志。如果条件运行的化可以考虑使用jvisualvm图形化的观察,不过要是线上的化一般没这个条件。
  • 当通过dump出堆内存,然后使用jvisualvm查看分析,一般能够分析出内存中大量存在的对象以及它的类型等。我们可以通过添加-XX:+HeapDumpOnOutOfMemoryError启动参数来自动保存发生OOM时的内存dump。
  • 当确定出大对象,或者大量存在的实例类型以后,我们就需要去review代码,从实际的代码入手来定位到真正发生泄漏的代码。

什么情况下会发生栈内存溢出?

  • 栈是线程私有的,他的生命周期与线程相同,每个方法在执行的时候都会创建一个栈帧,用来存储局部变量表,操作数栈,动态链接,方法出口等信息。局部变量表又包含基本数据类型,对象引用类型
  • 如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常,方法递归调用产生这种结果。
  • 如果Java虚拟机栈可以动态扩展,并且扩展的动作已经尝试过,但是无法申请到足够的内存去完成扩展,或者在新建立线程的时候没有足够的内存去创建对应的虚拟机栈,那么Java虚拟机将抛出一个OutOfMemory 异常。(线程启动过多)
  • 参数 -Xss 去调整JVM栈的大小

JVM内存为什么要分成新生代,老年代,持久代。新生代中为什么要分为Eden和Survivor。

1)共享内存区划分

  • 共享内存区 = 持久带 + 堆
  • 持久带 = 方法区 + 其他
  • Java堆 = 老年代 + 新生代
  • 新生代 = Eden + S0 + S1

2)一些参数的配置

  • 默认的,新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2 ,可以通过参数 –XX:NewRatio 配置。
  • 默认的,Edem : from : to = 8 : 1 : 1 ( 可以通过参数 –XX:SurvivorRatio 来设定)
  • Survivor区中的对象被复制次数为15(对应虚拟机参数 -XX:+MaxTenuringThreshold)

3)为什么要分为Eden和Survivor?为什么要设置两个Survivor区?

  • 如果没有Survivor,Eden区每进行一次Minor GC,存活的对象就会被送到老年代。老年代很快被填满,触发Major GC.老年代的内存空间远大于新生代,进行一次Full GC消耗的时间比Minor GC长得多,所以需要分为Eden和Survivor。
  • Survivor的存在意义,就是减少被送到老年代的对象,进而减少Full GC的发生,Survivor的预筛选保证,只有经历16次Minor GC还能在新生代中存活的对象,才会被送到老年代。
  • 设置两个Survivor区最大的好处就是解决了碎片化,刚刚新建的对象在Eden中,经历一次Minor GC,Eden中的存活对象就会被移动到第一块survivor space S0,Eden被清空;等Eden区再满了,就再触发一次Minor GC,Eden和S0中的存活对象又会被复制送入第二块survivor space S1(这个过程非常重要,因为这种复制算法保证了S1中来自S0和Eden两部分的存活对象占用连续的内存空间,避免了碎片化的发生)

GC

1. JVM 垃圾回收的时候如何确定垃圾? 你知道什么是 GC Roots 吗?GC Roots 如何确定,那些对象可以作为 GC Roots?

内存中不再使用的空间就是垃圾

引用计数法和可达性分析

哪些对象可以作为 GC Root 呢,有以下几类

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中 JNI(即一般说的 Native 方法)引用的对象

JVM中一次完整的GC流程是怎样的,对象如何晋升到老年代

思路: 先描述一下Java堆内存划分,再解释Minor GC,Major GC,full GC,描述它们之间转化流程。

我的答案:

  • Java堆 = 老年代 + 新生代
  • 新生代 = Eden + S0 + S1
  • 当 Eden 区的空间满了, Java虚拟机会触发一次 Minor GC,以收集新生代的垃圾,存活下来的对象,则会转移到 Survivor区。
  • 大对象(需要大量连续内存空间的Java对象,如那种很长的字符串)直接进入老年态
  • 如果对象在Eden出生,并经过第一次Minor GC后仍然存活,并且被Survivor容纳的话,年龄设为1,每熬过一次Minor GC,年龄+1,若年龄超过一定限制(15),则被晋升到老年态。即长期存活的对象进入老年态
  • 老年代满了而无法容纳更多的对象,Minor GC 之后通常就会进行Full GC,Full GC 清理整个内存堆 – 包括年轻代和年老代
  • Major GC 发生在老年代的GC清理老年区,经常会伴随至少一次Minor GC,比Minor GC慢10倍以上

你知道哪几种垃圾收集器,各自的优缺点,重点讲下cms和G1,包括原理,流程,优缺点。

思路: 一定要记住典型的垃圾收集器,尤其cms和G1,它们的原理与区别,涉及的垃圾回收算法。

我的答案:

1)几种垃圾收集器:

  • Serial收集器: 单线程的收集器,收集垃圾时,必须stop the world,使用复制算法。
  • ParNew收集器: Serial收集器的多线程版本,也需要stop the world,复制算法。
  • Parallel Scavenge收集器: 新生代收集器,复制算法的收集器,并发的多线程收集器,目标是达到一个可控的吞吐量。如果虚拟机总共运行100分钟,其中垃圾花掉1分钟,吞吐量就是99%。
  • Serial Old收集器: 是Serial收集器的老年代版本,单线程收集器,使用标记整理算法。
  • Parallel Old收集器: 是Parallel Scavenge收集器的老年代版本,使用多线程,标记-整理算法。
  • CMS(Concurrent Mark Sweep) 收集器: 是一种以获得最短回收停顿时间为目标的收集器,标记清除算法,运作过程:初始标记,并发标记,重新标记,并发清除,收集结束会产生大量空间碎片。
  • G1收集器: 标记整理算法实现,运作流程主要包括以下:初始标记,并发标记,最终标记,筛选标记。不会产生空间碎片,可以精确地控制停顿。

2)CMS收集器和G1收集器的区别:

  • CMS收集器是老年代的收集器,可以配合新生代的Serial和ParNew收集器一起使用;
  • G1收集器收集范围是老年代和新生代,不需要结合其他收集器使用;
  • CMS收集器以最小的停顿时间为目标的收集器;
  • G1收集器可预测垃圾回收的停顿时间
  • CMS收集器是使用“标记-清除”算法进行的垃圾回收,容易产生内存碎片
  • G1收集器使用的是“标记-整理”算法,进行了空间整合,降低了内存空间碎片。

System.gc() 和 Runtime.gc() 会做什么事情

java.lang.System.gc()只是java.lang.Runtime.getRuntime().gc()的简写,两者的行为没有任何不同

其实基本没什么机会用得到这个命令, 因为这个命令只是建议JVM安排GC运行, 还有可能完全被拒绝。 GC本身是会周期性的自动运行的,由JVM决定运行的时机,而且现在的版本有多种更智能的模式可以选择,还会根据运行的机器自动去做选择,就算真的有性能上的需求,也应该去对GC的运行机制进行微调,而不是通过使用这个命令来实现性能的优化

gc引用计数法的缺点,除了循环引用,说一到两个

  1. 在每次赋值操作的时候都要做相当大的计算,尤其这里面还有递归调用。这是比较麻烦的。
  2. 一个致命缺陷是循环引用,就是, objA引用了objB,objB也引用了objA,但是除此之外,再没有其他的地方引用这两个对象了,这两个对象的引用计数就都是1。这种情况下,这两个对象是不能被回收的。如下图所示:

img

这是引用计数法的一个致命缺陷。

说说你知道的几种主要的JVM参数

思路: 可以说一下堆栈配置相关的,垃圾收集器相关的,还有一下辅助信息相关的。

我的答案:

1)堆栈配置相关

java -Xmx3550m -Xms3550m -Xmn2g -Xss128k 
-XX:MaxPermSize=16m -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxTenuringThreshold=0
复制代码

-Xmx3550m: 最大堆大小为3550m。

-Xms3550m: 设置初始堆大小为3550m。

-Xmn2g: 设置年轻代大小为2g。

-Xss128k: 每个线程的堆栈大小为128k。

-XX:MaxPermSize: 设置持久代大小为16m

-XX:NewRatio=4: 设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。

-XX:SurvivorRatio=4: 设置年轻代中Eden区与Survivor区的大小比值。设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代的1/6

-XX:MaxTenuringThreshold=0: 设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。

2)垃圾收集器相关

-XX:+UseParallelGC
-XX:ParallelGCThreads=20
-XX:+UseConcMarkSweepGC 
-XX:CMSFullGCsBeforeCompaction=5
-XX:+UseCMSCompactAtFullCollection:

-XX:+UseParallelGC: 选择垃圾收集器为并行收集器。

-XX:ParallelGCThreads=20: 配置并行收集器的线程数

-XX:+UseConcMarkSweepGC: 设置年老代为并发收集。

-XX:CMSFullGCsBeforeCompaction:由于并发收集器不对内存空间进行压缩、整理,所以运行一段时间以后会产生“碎片”,使得运行效率降低。此值设置运行多少次GC以后对内存空间进行压缩、整理。

-XX:+UseCMSCompactAtFullCollection: 打开对年老代的压缩。可能会影响性能,但是可以消除碎片

3)辅助信息相关

-XX:+PrintGC
-XX:+PrintGCDetails
复制代码

-XX:+PrintGC 输出形式:

[GC 118250K->113543K(130112K), 0.0094143 secs] [Full GC 121376K->10414K(130112K), 0.0650971 secs]

-XX:+PrintGCDetails 输出形式:

[GC [DefNew: 8614K->781K(9088K), 0.0123035 secs] 118250K->113543K(130112K), 0.0124633 secs] [GC [DefNew: 8614K->8614K(9088K), 0.0000665 secs][Tenured: 112761K->10414K(121024K), 0.0433488 secs] 121376K->10414K(130112K), 0.0436268 secs

怎么打出线程栈信息。

思路: 可以说一下jps,top ,jstack这几个命令,再配合一次排查线上问题进行解答。

我的答案:

  • 输入jps,获得进程号。
  • top -Hp pid 获取本进程中所有线程的CPU耗时性能
  • jstack pid命令查看当前java进程的堆栈状态
  • 或者 jstack -l > /tmp/output.txt 把堆栈信息打到一个txt文件。
  • 可以使用fastthread 堆栈定位,fastthread.io/


调优

2.你说你做过 JVM 调优和参数配置,请问如何盘点查看 JVM 系统默认值

JVM参数类型

  • 标配参数

    • -version (java -version)
    • -help (java -help)
    • java -showversion
  • X 参数(了解)

    • -Xint 解释执行

    • -Xcomp 第一次使用就编译成本地代码

    • -Xmixed 混合模式

  • xx参数

    • Boolean 类型

      • 公式: -xx:+ 或者 - 某个属性值(+表示开启,- 表示关闭)

      • Case

        • 是否打印GC收集细节

          • -XX:+PrintGCDetails
          • -XX:- PrintGCDetails

          添加如下参数后,重新查看,发现是 + 号了

        • 是否使用串行垃圾回收器

          • -XX:-UseSerialGC
          • -XX:+UseSerialGC
    • KV 设值类型

      • 公式 -XX:属性key=属性 value

      • Case:

        • -XX:MetaspaceSize=128m

        • -xx:MaxTenuringThreshold=15

        • 我们常见的 -Xms和 -Xmx 也属于 KV 设值类型

          • -Xms 等价于 -XX:InitialHeapSize
          • -Xmx 等价于 -XX:MaxHeapSize

    • jinfo 举例,如何查看当前运行程序的配置

      • jps -l
      • jinfo -flag [配置项] 进程编号
      • jinfo -flags 1981(打印所有)
      • jinfo -flag PrintGCDetails 1981
      • jinfo -flag MetaspaceSize 2044

这些都是命令级别的查看,我们如何在程序运行中查看

    long totalMemory = Runtime.getRuntime().totalMemory();
    long maxMemory = Runtime.getRuntime().maxMemory();

    System.out.println("total_memory(-xms)="+totalMemory+"字节," +(totalMemory/(double)1024/1024)+"MB");
    System.out.println("max_memory(-xmx)="+maxMemory+"字节," +(maxMemory/(double)1024/1024)+"MB");

}

盘点家底查看JVM默认值

  • -XX:+PrintFlagsInitial

    • 主要查看初始默认值

    • java -XX:+PrintFlagsInitial

    • java -XX:+PrintFlagsInitial -version

    • 等号前有冒号 := 说明 jvm 参数有人为修改过或者 JVM加载修改

      false 说明是Boolean 类型 参数,数字说明是 KV 类型参数

  • -XX:+PrintFlagsFinal

    • 主要查看修改更新
    • java -XX:+PrintFlagsFinal
    • java -XX:+PrintFlagsFinal -version
    • 运行java命令的同时打印出参数 java -XX:+PrintFlagsFinal -XX:MetaspaceSize=512m Hello.java
  • -XX:+PrintCommondLineFlags

    • 打印命令行参数
    • java -XX:+PrintCommondLineFlags -version
    • 可以方便的看到垃圾回收器

3. 你平时工作用过的 JVM 常用基本配置参数有哪些?

  • -Xms

    • 初始大小内存,默认为物理内存1/64
    • 等价于 -XX:InitialHeapSize
  • -Xmx

    • 最大分配内存,默认为物理内存的1/4
    • 等价于 -XX:MaxHeapSize
  • -Xss

    • 设置单个线程的大小,一般默认为 512k~1024k
    • 等价于 -XX:ThreadStackSize
    • 如果通过 jinfo ThreadStackSize 线程 ID 查看会显示为 0,指的是默认出厂设置
  • -Xmn

    • 设置年轻代大小(一般不设置)
  • -XX:MetaspaceSize

    • 设置元空间大小。元空间的本质和永久代类似,都是对 JMM 规范中方法区的实现。不过元空间与永久代最大的区别是,元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制
    • 但是元空间默认也很小,频繁 new 对象,也会 OOM
    • -Xms10m -Xmx10m -XX:MetaspaceSize=1024m -XX:+PrintFlagsFinal
  • -XX:+PrintGCDetails

    • 输出详细的 GC 收集日志信息

    • 测试时候,可以将参数调到最小,

      -Xms10m -Xmx10m -XX:+PrintGCDetails

      定义一个大对象,撑爆堆内存,

      public static void main(String[] args) throws InterruptedException {
          System.out.println("==hello gc===");
      
          //Thread.sleep(Integer.MAX_VALUE);
      
          //-Xms10m -Xmx10m -XX:PrintGCDetails
      
          byte[] bytes = new byte[11 * 1024 * 1024];
      
      }![](https://tva1.sinaimg.cn/large/007S8ZIlly1gehkvas3vzj31a90u0n7t.jpg)
    • GC

    • Full GC

  • -XX:SurvivorRatio

    • 设置新生代中 eden 和S0/S1空间的比例
    • 默认 -XX:SurvivorRatio=8,Eden:S0:S1=8:1:1
    • SurvivorRatio值就是设置 Eden 区的比例占多少,S0/S1相同,如果设置 -XX:SurvivorRatio=2,那Eden:S0:S1=2:1:1
  • -XX:NewRatio

    • 配置年轻代和老年代在堆结构的比例
    • 默认 -XX:NewRatio=2,新生代 1,老年代 2,年轻代占整个堆的 1/3
    • NewRatio值就是设置老年代的占比,如果设置-XX:NewRatio=4,那就表示新生代占 1,老年代占 4,年轻代占整个堆的 1/5
  • -XX:MaxTenuringThreshold

    • 设置垃圾的最大年龄(java8 固定设置最大 15)

参数不懂,推荐直接去看官网,

https://docs.oracle.com/javacomponents/jrockit-hotspot/migration-guide/cloptions.htm#JRHMG127

https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html#BGBCIEFC

https://docs.oracle.com/javase/8/

Java SE Tools Reference for UNIX](https://docs.oracle.com/javase/8/docs/technotes/tools/unix/index.html)

4. 强引用、软引用、弱引用、虚引用分别是什么?

5. 请谈谈你对 OOM 的认识

  • java.lang.StackOverflowError

    • public class StackOverflowErrorDemo {
      
          public static void main(String[] args) {
              stackoverflowError();
          }
      
          private static void stackoverflowError() {
              stackoverflowError();
          }
      }
      
  • java.lang.OutOfMemoryError: Java heap space

    • new个大对象,就会出现
  • java.lang.OutOfMemoryError: GC overhead limit exceeded (GC上头,哈哈)

    • public class StackOverflowErrorDemo {
      
          public static void main(String[] args) {
              stackoverflowError();
          }
      
          private static void stackoverflowError() {
              stackoverflowError();
          }
      }
  • java.lang.OutOfMemoryError: Direct buffer memory

  • java.lang.OutOfMemoryError: unable to create new native thread

  • java.lang.OutOfMemoryError:Metaspace

6. GC垃圾回收算法和垃圾收集器的关系?分别是什么,请你谈谈?

7.怎么查看服务器默认的垃圾收集器是哪个?生产上如何配置垃圾收集器?谈谈你对垃圾收集器的理解?

8.G1 垃圾收集器?

9.生产环境服务器变慢,诊断思路和性能评估谈谈?

10.假设生产环境出现 CPU占用过高,请谈谈你的分析思路和定位

11. 对于JDK 自带的JVM 监控和性能分析工具用过哪些?你是怎么用的?

  • jconsole 直接在jdk/bin目录下点击jconsole.exe即可启动
  • VisualVM jdk/bin目录下面jvisualvm.exe

https://www.cnblogs.com/ityouknow/p/6437037.html