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

内存区域划分

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)进行剖析。

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