关于java:JVM面试速成篇

35次阅读

共计 6284 个字符,预计需要花费 16 分钟才能阅读完成。

老规矩,先赞在珍藏,不做白嫖党。

内存区域划分

JVM 的内存区域如何划分?并解释每个给区域的作用。

  • 程序计数器(公有):简略了解成行号。字节码解释器工作的时候就是通过程序计数器来寻取下一条字节码指令的。
  • 虚拟机栈(公有):形容 Java 办法执行的内存模型,每个办法执行的时候都会创立一个栈帧,用于存储局部变量表、操作数栈、动静链接、办法进口等。
    虚拟机栈是形容办法的模型,所以为了不便记忆,想一下办法都有什么货色。
    public int Hello(int x){ 
        int a=1,b=1; # a b x 都放在局部变量表
        int c=a+b; # + 操作栈
        getHello(); # 被调用的指标办法在编译期无奈被确定下来,#只能从运行时常量池中将符号援用转换成间接援用,这个过程就是动静链接。return c; # 这个返回就是办法的进口。}
  • 本地办法栈(公有):虚拟机栈的作用是一样的,只不过虚拟机栈是服务 Java 办法的,而本地办法栈是为虚拟机调用 Native 办法服务的。
  • 堆(共享):Java 虚拟机中内存最大的一块,是被所有线程共享的,简直所有的对象实例都在这里分配内存。
  • 办法区(共享):用于存储已被虚拟机加载的类信息、常量、动态变量、即时编译后的代码(JIT 生成的符号援用就是放在运行时常量池)等数据。

JVM 堆内存的外部如何划分?

  堆分为年老代和老年代,年老代又分为 Eden 区和 Survivor 区, Survivor 分为 From 和 To 区域
其中默认比例为 老年代:年老代 =2:1;eden:from:to=8:1:1

办法区、永恒代、元空间区别是什么?

  办法区是 JVM 内存标准,永恒代是这种标准的 JVM 实现。元空间则是在 1.8 之后取代了永恒代的一种实现。并且只有 HotSpot 才有 永恒代。

为什么用元空间替换成永恒代?

  因为永恒带有 MAX 下限,容易遇到内存溢出问题。最典型的场景就是,在 jsp 页面比拟多的状况,容易呈现永恒代内存溢出。所以 1.8 之后应用元空间代替永恒代,元空间应用的是本地内存,只有本地内存足够大就能够解决 oom 问题。

深拷贝和浅拷贝的区别是什么?

  浅拷贝:减少了一个指针指向已存在的内存地址
  深拷贝:减少了一个指针并且申请了一个新的内存,使这个减少的指针指向这个新的内存

类加载机制

一个 Java 类是如何运行起来的?

  首先通过打包工具将 java 类编译成.class 文件。而后 JVM 通过类加载器将.class 文件加载到内存,最初 JVM 就会基于本人的 字节码执行引擎,来执行加载到内存里的咱们写好的那些类了。

说说 JVM 类的加载过程

  加载 - 链接 - 初始化
  链接包含:验证 -> 筹备 -> 解析

能具体解释一下每一步都是做什么的?

  • 加载:将 class 文件加载到内存。
  • 验证:依据相干标准去验证你的.class 文件是否合规。
  • 筹备:给对应的类、变量分配内存空间,赋初始值。
  • 解析:符号援用变为间接援用。(具体何为符号援用,上一篇内存换分中动静链接处有说。)
  • 初始化:真正执行类中定义的 java 程序代码。包含逻辑解决赋值等操作。

    什么时候才会初始化一个类?

  1. 执行须要援用类或者接口的 java 虚拟机指令(new,getstatic, putstatic, invokestatic)的时候
  2. 调用类库中的某些反射办法的时候。
  3. 初始化子类发现父类没初始化时候,会先初始化父类。
  4. 蕴含“main()”办法的主类,是立马初始化的。

    Java 中类加载器有几种?

  5. 启动类加载器 Bootstrap ClassLoader:次要负载加载 Java 目录下的外围类的。在 jdk 装置目录下有一个 lib 目录,这下边的就是 java 最外围的类库。启动类加载器就是加载这下边的类。
  6. 扩大类加载器 Extension ClassLoader:他和启动类加载器相似,只不过他加载 lib/ext 目录下的类。
  7. 利用类加载器 Application ClassLoader:次要是更具你的需要去加载你本人的类
  8. 自定义类加载器:这个没啥说的,就是你自定义的类加载器。

    为什么还须要自定义类加载器呢?

     大家晓得 java 代码很容易被反编译,如果你须要把本人的代码加密避免反编译,这个时候就用到了自定类加载器了。

    说说什么是双亲委派加载机制?

  打比方咱们 Hello.class 这个类,他在被加载到内存的时候就会先问他爸爸 - 扩大类加载器,而后扩大类加载器再问本人老爸 - 启动类加载器,而后启动类加载器就开始在 lib 下找 Hello.class,没找到,通知扩大类加载器,你本人玩去,我这没有。扩大类一看老爸没有那我就本人来吧,找了半天他也没有,而后就通知利用类加载器说我和你爷爷帮不了你了,你还得靠本人去找。这个过程就是双亲委派。

双亲委派的作用是什么?

  1. 安全性:避免外围类被篡改。比方你自定义一个 java.lang.String 类,他在加载的时候发现父类加载器曾经加载了,就不会在进行加载这个类。
  2. 防止反复加载:父加载器曾经加载过的类,子加载器就不会再进行加载了,无效的避免了反复加载问题。

    举例说明突破双亲委派机制

  3. 设计上的缺点,因为越根底的类越由下层加载器进行加载,但如果有些根底类又要调用用户代码,这个时候就会突破(SPI)。如何你你对 dubble 理解就可从这个 spi 向 dubble 上疏导。
  4. 因为用户程序动态性导致(热部署)OSGI。
    tomcat 突破双亲委派实例
  • tomcat 是 web 容器,一个 web 容器能够部署多个程序,不同程序依赖的第三方类库的不同版本,不能要求同一个类库在同一个服务器只有一个份,因而要保障好互相的隔离。并且为了平安 tomcat 容器依赖的类库和利用所以来的类库也须要隔离。这就突破了双亲委派。
  • 家喻户晓 jsp 文件也是最终编译成 class 文件被夹在到虚拟机的。对于 class 批改了,如果类名一样类加载器会间接去取内存中曾经存在的 class, 批改就不会失效,tomcat 在批改 jsp 页面不须要重启服务也能够失效。阐明也突破了双亲委派机制。tomcat 是如何做的呢?他给每个 JSP 都筹备了一个 Jsp 类加载器。当 jsp 扭转就卸载类加载器,从新创立类加载器,从新加载 jsp 文件。

    垃圾回收

    如何判断对象是垃圾

  1. 援用计数:给对象增加一个援用计数器,当有中央援用时 +1,生效时 -1。当这个计数器的数值位 0 的时候示意对象已死亡,能够回收了。
    毛病:这种算法会呈现循环援用 (A->b b->A) 无奈回收问题。
  2. 可达性剖析:当一个对象到 GCRoots 没有任何援用的时候,示意该对象不可用
    对于 GCRoots 咱们能够了解为办法的 局部变量和类的动态变量。实例变量是不是。

四种援用类型都是什么?

  • 强援用:任何时候都不能被回收。
  • 软援用:内存不足时会进行回收。能够利用在缓存上。
  • 弱援用:下一次 GC 的时候就会被回收。
  • 虚援用:又称作幽灵援用;无奈通过虚援用取得对象,用 PhantomReference 实现虚援用,虚援用的用处是在 GC 时返回一个告诉。

    垃圾回收算法

    标记革除

      标记出无用的对象,对立革除。
    存在问题:①效率问题,②空间利用率问题,容易呈现悬浮碎片,在创立大对象的时候会再次触发垃圾回收。

复制

  复制:将内存划分为大小雷同的两块,每次只用其中的一块,当内存满了的时候,咱们将存活的复制到另一块,而后剩下局部全副删除。
存在问题:尽管解决了悬浮碎片问题,然而内存应用效率降落,毕竟每次只有个别内存可用。并且如果对象存率较高,那么将频繁触发垃圾回收。
<image src= https://img-blog.csdnimg.cn/20210331222241782.png>

标记整顿

  标记整顿:标记无用对象,让所有存活的对象都向一端挪动,而后间接革除掉端边界以外的内存。他可良好的解决掉标记革除的悬浮碎片问题。
存在问题:效率问题

分代收集

  依据对象存活周期的不同将内存划分为几块,个别是新生代和老年代,新生代根本采纳复制算法,老年代采纳标记革除算法。

垃圾回收器

serial 和 serial old

  单线程的垃圾回收,从名字能够看出一个作用在新生代,一个作用在老年代。当初曾经简直不应用了。

parnew

  多线程垃圾收集器,他作用在新生代,常和 cms 一起应用。

parallel

  他也叫做“吞吐量优先收集器”(吞吐量 = 代码运行工夫 / 代码运行工夫 + 垃圾回收工夫)少的进展工夫能够减少用户的体验感,大的吞吐量能够进步 cpu 效率;他也常和 cms 一起应用

cms

  应用标记革除算法,实用于老年代。
cms 的四个阶段:

  1. 初始标记:标记的是 GCRoot,这个过程会 stop the world.
  2. 并发标记:开始通过 GCRoot 去追踪所有存活对象,这个过程是程序是能够工作的。
  3. 从新标记:因为并发标记过程程序还在跑还会产生一些垃圾,须要从新标记。这个过程 stop the world.
  4. 并发革除:并发的革除垃圾。
    以下边代码为例进行解释阐明:

    public class Hello {private static A a = new A();
    }
    class A{private B b =new B();
    }
    class B{}

毛病:

  • 标记革除的毛病会产生悬浮的空间碎片
  • 耗费 cpu 资源。并发革除和并发标记的过程 cpu 占用比拟高。CMS 默认启动的线程数 =(cpu 核 +3)/4。如果 2 核的 4G 默认就是须要占用一个 cpu。
  • Concurrent Mode Failure 问题,因为并发清理的过程中是容许程序运行的,如果在清理过程中再次 minorGC, 并且老年期待的闲暇空间不足以存储对象,这个时候就会触发 Concurrent Mode Failure,而后垃圾回收器被强制切回 Serial old 收集器。

G1

  G1 收集器是 JDK7 提供的一个新收集器,JDK9 之后变为默认垃圾回收算法。G1 收集器基于“标记 - 整顿”算法实现,也就是说不会产生内存碎片。
G1 是将内存划分为多个大小相等的 Region。默认是 2048 个大小从 1 -32MB 之间。

G1 的收集过程

  • 初始标记:同 cms 一样,初始标记 GCRoot,这个阶段也须要 stop the world
  • 并发并发:同 cms 一样,通过 GCRoot 进行追踪存活的对象,这个过程比较慢,所以是 - – 并发执行的,然而这个过程是不影响零碎失常工作的。
  • 最终标记:用于解决并发阶段完结后仍遗留下来的垃圾对象。stop the morld
    筛选回收:这一步是最为要害的,G1 之所以能够管制回收预期的进行工夫,就靠它了。G1 会依据回收价和老本进行选择性回收。(如通过追踪发现回收一个 region10m 须要 1s 另外一个回收 200m 须要 1ms; 他必定抉择回收 200m 的那个。)

ZGC

  他是 JDK 11 中推出的一款低提早垃圾回收器,ZGC 和 G1 一样是基于 reigon 的,简直所有阶段都是并发的,整堆扫描,局部收集并且局部代。如果你能够答复出这个证实你对新常识存在敏感性。你就是面试官找打那个他。因为是速成片,就不去具体的介绍了。

对象入住老年代的条件

  • 通过屡次 minorGC 都没都能回收。默认是 15 次就会到大老年代。能够通过 -XX:MaxTenuringThreshold 进行批改。
  • 大对象间接进入到老年代;可通过 -XX:PretenureSizeThreshold 设置大对象的规范。
  • minorGC 后值大于 survivor 的大小。间接放到老年代
  • 动静判断,survivor 区中一批对象总大小大于 survivor 区域的 50%,那么年龄大于等于这批对象的都会被挪动到老年区。
    年龄 1 + 年龄 2 + 年龄 n 的多个年龄对象总和超过了 Survivor 区 域的 50%,此时就会把年龄 n 以上的对象都放入老年代。
    如下图就会把 >= 2 岁的全副挪动到老年代。

    降职老年代了解记忆图

    说说新生代 GC 的过程以及算法

    新生代对象个别都是朝生夕死,所以应用的是复制算法。对象出世在 eden 区域后,当不能承载后会触发 minorGC 将存活的对象挪动到 survivor 两块中的一块空间上,下次 GC 的时候会将这一块空间和 eden 一起回收,将存存活对象挪动到量一块空间上。因为默认的 eden:from:to=8:1:1 所以之后 10% 空间限度,也极大的解决了复制算法的节约空间问题。

    什么是空间调配担保

      在产生 Minor GC 之前,虚构机会查看 老年代最大可用的间断空间 是否大于新生代所有对象的总空间,如果大于相安无事,如果小于虚构机会查看 HandlePromotionFailure 设置值是否容许担保失败,如果 HandlePromotionFailure=true,那么会持续查看老年代最大可用间断空间是否大于历次降职到老年代的对象的均匀大小,如果大于,则尝试进行一次 Minor GC,但这次 Minor GC 仍然是有危险的;这个过程就空间调配担保。
    阐明:HandlePromotionFailure jdk1.6 之后就被移除。

    FullGC 触发机会

    空间担保参数移除之后咱们就不去思考空间担保这件事件了。

    • 内存老年代可用内存小于历次新生代 GC 后进入老年代的均匀对象大小
    • 新生代 Minor GC 后的存活对象大于 Survivor,那么就会进入老年代,此时老年代内存不足
    • -XX:CMSInitiatingOccupancyFaction 参数,老年代应用的内存超出了这个参数的比例也会主动触发。

JVM 调优

调优工具

jps

能够查看过程号;jps -v 能够查看运行的参数。

jstat

jstat 用来统计 gc 相干信息. jstat -gcutil 过程号

jinfo

查看虚构参数的;还能够调整虚拟机参数。

jmap

内存快照(也能够通过配置虚拟机参数获取快照)
jmap -dump:format=b,file=d:\aa.bin pid

jhat

剖析快照的工具,他占用 cpu 比拟多,个别咱们会将生成的快照放到本地进行剖析;

jstack

线程的堆栈监控。查看以后这一时刻的线程调用堆栈状况(又叫 threaddump)能够用来剖析死锁,死循环,申请内部资源长时间期待;

jconsole、visual vm

图形化的 jvm 调优工具。

调优参数

  • -Xms 最小分配内存,初始化内存;
  • -Xmx 最大调配的内存;
  • -Xmn 设置年老代的大小;
  • -Xss 设置每个线程堆栈的大小
  • -XX:NewRatio=2 年老代和老年代的比例 1:2
  • -XX:SurvivorRatio=8 eden 和 survivor 的比例 8:2
  • -XX:+PrintGCDetails:打印 gc 详细信息。
  • -XX:+UseParNewGC 开 ParNew 收集器
  • XX:+UseConcMarkSweepGC 开启 cms
  • -XX:PretenureSizeThreshold 降职老年代的大对象界线
  • -XX:MaxTenuringThreshold 对象进行老年代的门槛

你们公司我的项目参数个别如何配置?

不同的我的项目不一样,你能够 jps -v 查看你公司的配置然后背下来。

常见场景分析题

线上时常呈现卡顿景象

 首先呈现卡顿咱们要向是不是 sql 慢了;开启慢查问日志,查看 sql 执行工夫。
而后思考是不是垃圾回收的锅,通过内存监控工具查看是不是频繁的 fullGC,如果是 fullGC 引起的解决方案:更换垃圾回收器,部署多个利用而后通过 nginx 进行方向代理。

线上呈现 oom 如何解决

  首先线上问题应先想方法让程序可用,在去想解决的方法,因为用户就在那里等着你呢,影响了他们就是影响了上帝,个别都是负载平衡的我的项目,能够先将 oom 的这台机器敞开,然而这个时候还要思考一些定时工作啊,音讯的生产,剩下的机器是否能够抗的住漂泊啊等问题。
而后去剖析 OOM 起因,通过 jmap 产生 heapdump 文件,将文件从线上拿到本地通过 Eclipse Memory Analyzer(MAT)进行剖析。

最初看完别急着走给个关注,回绝白嫖从我做起。

正文完
 0