JVM 虚拟机

JVM 虚拟机

一、GC相关

1、JVM运行时数据区分为哪几块?分别都存储哪些数据?

答:程序计数器、虚拟机栈(线程)、本地方法栈、堆、方法区
程序计数器:每个线程都会有一个,存储正在执行的JVM字节码指令的地址指针【字节码执行引擎修改】
虚拟机栈:每个线程都会被分配一块内存区域,即栈;每个方法执行时都会创建一个栈帧内存区域,用于存储局部变量表(存放局部变量)、操作数栈(操作数的临时中转区域)、动态链接(将符号引用转化为方法的直接引用)、方法出口(行号指针)等信息
本地方法栈:每个线程独有,和虚拟机栈一样,只不过是为native方法服务
(堆又分为由Eden*From* survivor**\To** survivor{From\To是名义上的}构成的新生代和老年代两部分组成):所有的对象实例(对象头中存在一个类元指针->对象的地址)、数组
方法区(元空间):类信息、常量、静态变量、类元信息【直接内存】(字节码执行引擎->执行指令码)

java 字节码 javap -c 文件 反汇编成为JVM 指令码

2、什么时候一个对象会被GC?JVM GC算法有哪些,策略分别都有什么优劣势,都适用于什么场景?目前的JDK版本采用什么回收算法?(这些所有的问题就是:Java GC机制?)

答:(1)什么时候回收GC?

​ GC判断对象是否可以被回收(对象存活判定):
引用计数法(但主流的JVM没有选用该算法,因为存在循环引用问题)、
可达性分析算法(主流实现)-GC Roots有哪些?:

虚拟机栈中引用的对象
方法区中类静态属性引用的对象
方法区中常量引用的对象
本地方法栈中JNI引用的对象(其实就是运行时的一些对象和变量,比如局部变量表,方法区的元数据比如fianl,static变量,本地方法栈执行的方法)
(2)GC算法:标记清除,复制算法,标记整理,分代收集
a.标记清除(分为标记和清除两个阶段)

-优势:实现简单,不需要进行对象进行移动。
-劣势:1)效率问题,标记和清除两个过程效都不高;2)空间问题:产生大量不连续的内存碎片,提高了垃圾回收的频率
-场景:老年代中的对象存活率高、没有额外空间对它进行分配担保,就必须使用标记清除算法或标记整理算法

b.复制算法(内存分两块,只用一块,复制存活的对象到另一块,直接清除这块)

-优势:按顺序分配内存即可,实现简单、运行高效,不用考虑内存碎片。
-劣势:可用的内存大小缩小为原来的一半;对象存活率高时会频繁进行复制;需要用额外空间来进行担保以应对被使用的内存中所有对象都100%存活的极端情况。
-场景:新生代

c.标记整理(分为标记和整理两个阶段,整理=移动+清除)

-优势:解决了标记-清理算法存在的内存碎片问题。
-劣势:仍需要进行局部对象移动,一定程度上降低了效率。
-场景:老年代[老年代使用标记清除有什么问题????]

c.分代收集算法:老年代使用标记-清理或标记-整理算法,新生代使用复制算法
(3)目前的JDK版本采用什么回收算法

分代收集算法 JVM系列(四) - JVM GC回收算法(每种回收算法的优势和劣势)

3、一个对象从创建到销毁都是怎么在JVM的五大块里存活和转移的?

答:

Student stu = new Student(“zhangsan”);
stu.add();
stu=null;

1)用户创建了一个Student对象,运行时JVM首先会去方法区寻找该对象的类型信息,没有则使用类加载器classloader将Student.class字节码文件加载至内存中的方法区,并将Student类的类型信息存放至方法区
2)接着JVM在堆中为新的Student实例分配内存空间,这个实例持有着指向方法区的Student类型信息的引用,引用指的是类型信息在方法区中的内存地址。
3)在此运行的JVM进程中,会首先起一个线程跑该用户程序,而创建线程的同时也创建了一个虚拟机栈,虚拟机栈用来跟踪线程运行中的一系列方法调用的过程,每调用一个方法就会创建并往栈中压入一个栈帧,栈帧用来存储方法的参数,局部变量和运算过程的临时数据。上面程序中的stu是对Student的引用,就存放于栈中,并持有指向堆中Student实例的内存地址。
4)JVM根据stu引用持有的堆中对象的内存地址,定位到堆中的Student实例,由于堆中实例持有指向方法区的Student类型信息的引用,从而获得add()方法的字节码信息,接着执行add()方法包含的指令。
5)将stu指向null

6)JVM GC

4、JVM分代回收的过程?

答:我们从一个object来说明其在分代垃圾回收算法中的回收轨迹。
1)新建的object对象会被分配到新生代的Eden区(但大对象直接进入老年代),之后,会有其他对象进入Eden区。若Eden区空间不足,JVM将发起一次Minor GC(在Minor GC前会进行空间分配担保检查)
2)Minor GC通过复制算法会把Eden区还存活的object,移动到From Suvivor空间(本质上是,Eden空间先复制到To Suvivor,然后To Suvivor的角色换为From Suvivor空间),并且设置object的年龄为1(对象在Survivor区中每熬过一次Minor GC,年龄就会增加1岁,当它的年龄增加到一定程度时,就会被移动到老年代中),此时还在新生代。
3)之后,又有其他对象进入Eden区,导致Eden空间不足,JVM又发起一次Minor GC
4)在From Suvivor空间中,仍存活的object对象会根据它的年龄值来决定去向。年龄达到一定值(年龄阈值,可以通过-XX:MaxTenuringThreshold来设置)的对象会被移动到年老代中,没有达到阈值的对象会被复制到To Survivor空间。object的年龄暂时没达到阈值(但也存在动态的对象年龄判断),所以被移到To Survivor区,此时object的年龄+1。Eden空间的存活对象也会被移动到To Survivor空间。
5)经过这次GC后,Eden区和From Survivor区已经被清空。“From”和“To”会交换他们的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎样,都会保证名为To的Survivor区域是空的
6)Minor GC会一直重复这样的过程,直到“To”区被填满,“To”区被填满之后,会将所有对象(从Eden和FromSurvivor区存活下来的所有对象)移动到年老代中。
(7)object存活一段时间后,发现此时object不可达GC Roots,而且此时老年代空间比率已经超过了阈值,触发了majorGC(也可以认为是fullGC,但具体需要垃圾收集器来联系),此时object1被回收了。full GC会触发stop the world。
注释:
大对象直接进入老年代:什么是大对象,那种很长的字符串和数组,这个量化是可以通过-
XX:PretenureSizeThread 参数控制的
空间分配担保
动态的对象年龄判断:如果在Survivor空间中,所有同龄对象的总大小超过Survivor空间的一半,那么大于或等于该年龄的对象就可以直接进入老年代,而无需达到年龄阈值
Minor GC:指发生在新生代的GC,采用复制算法,特点-非常频繁、速度较快
Major GC(Full GC):指发生在老年代的GC,采用标记清理或标记整理算法,特点-出现Major GC经常会伴随至少一次Minor GC、速度比Minor GC慢10倍以上

5、-Xmx10240m -Xms10240m -Xmn5120m -XXSurvivorRatio=8

答:

Xmx 最大堆大小 memory max
Xms 初始化堆大小 memory storage
Xmn 新生代内存 memory new
XXSurvivorRatio Eden:Survivor=(8:1:1)

6、为什么要有Survivor区?

答:Survivor的存在意义,就是减少被送到老年代的对象,进而减少Full GC的发生,Survivor的预筛选保证,只有经历16次Minor GC还能在新生代中存活的对象,才会被送到老年代。

7、为什么要设置两个Survivor区?

答:设置两个Survivor区最大的好处就是解决了碎片化

假设现在只有一个survivor区,第一次Eden满了,触发一次Minor GC,Eden中的存活对象就会被移动到Survivor区。下一次Eden满了的时候 ,此时进行Minor GC,Eden和Survivor各有一些存活对象 。如果此时把Eden区的存活对象硬放到Survivor区,很明显这两部分对象所占有的内存是不连续的,也就导致了内存碎片化

8、知道SurvivorRatio这个参数为啥初始是默认的8:1:1吗?

答:新生代中大多数对象都是朝生夕死的,有IBM的研究表明,死98%,也就是说一般场景只有2%左右的对象存活。8:1:1的配置可以接受10%的对象存活,而且每次新生代中浪费的空间只有10%

9、哪些情况会产生full GC,哪些情况产生minor GC?

答:

1)minor GC会在eden区满了或者新创建的对象大小 > Eden所剩空间的时候发生(在Minor GC前进行的空间分配担保检查引起的)

2)(full GC产生在老年代的剩余空间不足且((HandlePromotionFailure不允许担保失败)||(HandlePromotionFailure允许担保失败,但老年代的可用空间少于历次晋升的平均大小))或(HandlePromotionFailure担保失败),以及永久代空间满都会引起full GC

10、两个对象的引用循环依赖,会不会被垃圾回收,什么情况下会什么情况下不会呢

答:看这个循环引用是否挂在根上,如果挂在根上,如果这个根还被JVM的Java代码所执行的话,就不会GC掉,如果说这个根已经被释放掉了,这个对象不挂在跟上了,那个这个对象就会被GC掉。

11、项目中查看垃圾回收(jvm查看gc命令)?如何排查堆内存的问题?

答:说使用jmap转储dump文件分析或者使用jstat等工具检测gc。

12、垃圾收集器

答:

(1)新生代收集器:Serial 收集器、ParNew 收集器、Parallel Scavenge 收集器

(2)老年代收集器:Serial Old 收集器、Parallel Old 收集器、CMS收集器(Concurrent Mark Sweep)

  • Serial

    • 单线程的收集器 ;

    • 只使用一个CPU或一收集线程去完成垃圾收集工作 ;

    • 在它进行垃圾收集时,必须暂停其他所有的工作线程 ;

      虚拟机运行在Client模式下的默认新生代收集器

  • parNew

    Serial收集器的多线程版本

    虚拟机运行在Server模式下首选的新生代收集器(除了Serial收集器外,目前只有它能与CMS收集器配合工作)

  • Parallel Scavenge

    • 并行(也是多线程收集器,看上去和ParNew差不多,但它是一个吞吐量优先的收集器)的多线程收集器 (多条垃圾线程并行工作,但此时用户线程任然处于等待状态)

    • 【(代码)吞吐量优先的收集器】
      适合在后台运算而不需要太多交互(与停顿有关)的任务

      通过三个参数控制吞吐量
      ? 最大GC停顿时间(-XX:MaxGCPauseMillis):以牺牲新生代空间和吞吐量为代价
      ?吞吐量大小(GCTimeRatio):是代码时间:GC的值
      ?GC自适应调节

  • Serial Old

    • Serial Old是Serial收集器的老年代版本,它同样是一个单线程收集器,使用标记-整理算法。
    • Serial Old收集器的主要意义也是在于给Client模式下的虚拟机使用
    • 在Server模式下 ,一种用途是与Parallel Scavenge收集器搭配 ,另一种用途就是作为CMS收集器的后备预案
  • Parallel Old

    Parallel Old是Parallel Scavenge收集器的老年代版本 ,使用标记-整理算法。
    在注重吞吐量以及CPU资源敏感的场合,都可以优先考虑Parallel Scavenge加Parallel Old收集器。

  • CMS

    • 一种以【低停顿】时间为目标的收集器,基于“标记—清除” ,希望系统停顿时间最短,以给用户带来较好的体验

    • CMS收集器的内存回收过程是与用户线程一起并发执行的

      缺点:
      ?CMS收集器对CPU资源非常敏感(由于并发的设计)
      ?CMS收集器无法处理浮动垃圾 (并发清理过程中,用户线程也会产生垃圾)
      ?CMS收集器会产生大量空间碎片 (标记-清除算法带来的)

  • G1

    • G1的使命是未来可以替换掉JDK 1.5中发布的CMS收集器
    • 并发 ,独立管理整个GC堆
    • G1从整体来看是基于“标记—整理”算法实现的收集器,从局部(两个Region之间)上来看是基于“复制”算法实现的 。不会产生内存空间碎片
    • 可预测的停顿时间模型

    可预测的停顿时间模型原理:G1跟踪各个Region里垃圾堆积的价值大小(回收的时间和空间的经验值),在后台维护一个优先级列表,每次根据允许回收的时间,选择价值最大的Region。

13、G1回收过程?

答:

初始标记(Initial Marking)
----》并发标记(Concurrent Marking)
----》最终标记(Final Marking)
----》筛选回收
对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划

14、CMS(并发收集、低停顿。 )和G1了解么,CMS解决什么问题,说一下回收的过程。CMS回收停顿了几次,为什么要停顿两次

答:

  • 初始标记(CMS initial mark)

    初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快,需要“Stop The World”

  • 并发标记(CMS concurrent mark)

    并发标记阶段就是进行GC Roots Tracing的过程。

  • 重新标记(CMS remark)

    重新标记阶段是为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短,仍然需要“Stop The World”。

  • 并发清除(CMS concurrent sweep)

    并发清除阶段会清除对象。

二、JMM相关(Java 【线程】内存模型)

1、介绍一些虚拟机的内存模型?Java的内存模型是怎么设计的?为什么要这么设计?结合内存模型的设计谈谈Volatile关键字的作用?

答:

(虚拟机的内存模型和运行时的数据区域不是一回事),虚拟机内存模型又叫JMM:

JMM规定了线程之间的共享变量(实例域{实例变量},静态域{静态变量}和数组元素)存储在主内存中每个线程都有一个私有的工作内存,工作内存中存储了该线程以读/写共享变量的副本,线程对变量的所有操作都必须在工作内存中,而不能直接读写在主内存中的变量,不同的线程间也无法访问对方工作内存中的变量,必须通过主内存交互完成。

当线程A和线程B需要通信时,线程A首先会把自己本地内存中修改后的x值刷新到主内存中,此时主内存中的x值变为了1。随后,线程B到主内存中去读取线程A更新后的x值,此时线程B的本地内存的x值也变为了1
内存间交互8种操作:

lock、unlock、

read、load(载入,用于工作内存)、

use(使用,用于工作内存,把变量值传递给执行引擎)、assign(赋值,用于工作内存,将执行引擎的值赋给变量)、

store(存储,用于工作内存,将变量值传送到主内存)、write

Volatile
介绍:关键字 volatile 可以说是 Java 虚拟机提供的最轻量级的同步机制,当一个变量定义为 volatile,它具有内存可见性以及禁止指令重排序两大特性olatile 不能保证复合原子性比如 比如: i++;
原理:

volatile 变量进行写操作时,JVM 会向处理器发送一条 Lock 前缀的指令,将这个变量所在缓存行的数据写会到系统内存。
Lock 前缀指令实际上相当于一个内存屏障(也成内存栅栏),它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成。

注:Volatile 不能保证复合原子性比如 比如: i++;

并发编程的三个概念:原子性、可见性、有序性

Synchronized
概念:

重量级锁、重入锁、jvm 级别锁

原理:

​ 方法和代码块(对象锁和类锁):
​ ⚫ 对于普通同步方法,锁是当前实例对象。
​ ⚫ 对于静态同步方法,锁是当前类的 Class 对象。
​ ⚫ 对于同步方法块,锁是 Synchonized 括号里配置的对象。

场景:

​ 资源竞争

先行发生原则(happens-before)原则包括了:

(1)程序顺序原则(2)锁原则(unlock lock)(3)volatile变量规则、(4)线程启动规则(5)线程终止规则(6)线程中断原则(7)对象终结规则(构造函数 finalize)(8)传递性性

2、内存溢出和内存泄露

答:

内存泄漏:你用new申请了一块内存,后来很长时间都不再使用了(按理应该释放),但是因为一直被某个或某些实例所持有导致 GC 不能回收,也就是该被释放的对象没有释放。
内存溢出:你申请了10个字节的空间,但是你在这个空间写入11或以上字节的数据,出现溢出。
产生该错误的原因主要包括
(1)JVM内存过小。
(2)程序不严密,产生了过多的垃圾

三、类加载机制

1、Java的类加载器都有哪些?每个类加载器都加载哪些类?类加载之间的父子关系是怎样的(双亲委派模型是什么)?双亲委派机制?为什么要使用使用双亲委派机制(类会被重复加载吗,如果有重复类加载会发生什么)?类在什么时候加载?

答:JVM提供了3种类加载器;
启动类(BootStrap)加载器:负责加载 JAVA_HOME\jre\lib 目录中的,或通过-Xbootclasspath参数指定路径中的,且被虚拟机识别(按文件名识别,如rt.jar)的类。
扩展类(Extension)加载器:负责加载 JAVA_HOME\jre\lib\ext 目录中的,或通过java.ext.dirs系统变量指定路径中的类库。
应用程序(Application)类加载器:负责加载用户路径(classpath)上的类库。(idae或eclipse运行时会修改为classpath为项目所在路径)
类加载之间的父子关系是怎样的:自定义类加载器-》应用程序类加载器-》扩展类加载器-》启动类加载器(称呼类加载器之间的层次关系为双亲委派模型
双亲委派机制:如果一个类加载器收到了类加载的请求,它首先不会自己 去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是 如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈 自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自 己去加载。
如果用户没有自定义过类加载器,用户编写的类都是由应用程序类加载器加载
Java中的核心类比如rt.jar中的String类等,都是由启动类加载器加载;启动类加载器是一个纯C实现,所以String.class.getClassLoader() 会返回null
双亲为null,有两种情况:
(1)当前类加载器就是启动类加载器,它没有双亲
(2)当前的类加载器的双亲就是启动类加载器

Unsafe unsafe = Unsafe.getUnsafe();为什么会抛异常?
Unsafe类是rt.jar中的类, 
 getUnsafe()函数会判断,如果执行Unsafe unsafe = Unsafe.getUnsafe();
代码所在的类的classloader不是启动类加载器就会抛出安全异常
如果程序员必须要实例化Unsafe可以通过反射实现		

**双亲委派机制使用原因:**双亲委托模型使各个类加载器的基础类统一,解决了类载入过程中的安全性问题假设有一个开发者自己编写了一个名为java.lang.Object的类,想借此欺骗JVM。现在他要使用自定义ClassLoader来加载自己编写的java.lang.Object类。 然而幸运的是,双亲委托模型不会让他成功。因为JVM会优先在Bootstrap ClassLoader的路径下找到java.lang.Object类,并载入它,这样避免系统中出现多个不同的Object类。(也就是不会重复加载同一个类)
类在什么时候加载:虚拟机规范中并没强行约束,这点可以交给虚拟机的的具体实现自由把握,但是对于初始化阶段虚拟机规范是严格规定了如下几种情况,如果类未初始化会对类进行初始化。:
①创建类的实例、读取或设置类的静态字段、调用类的静态方法
②反射
③子类初始化会触发父类初始化
④JVM启动时指定的主类

2、jvm类加载机制?

答:JVM类加载机制分为五个部分:加载,验证,准备,解析,初始化五个过程
加载:类加载过程中的一个阶段,这个阶段会通过一个类的全限定名来获取定义此类的二进制字节流,将这个二进制字节流所代表的静态存储结构转化为方法区的运行时数据结构,在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。(注意这里不一定非得要从一个Class文件获取,这里既可以从ZIP包中读取(比如从jar包和war包中读取),也可以在运行时计算生成(动态代理),也可以由其它文件生成(比如将JSP文件转换成对应的Class类)。)
验证:验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息 符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
准备:准备阶段是正式为类变量分配内存并设置类变量的初始值阶段(没有final修饰的类变量赋数据类型的 零值;有final修饰的类变量赋指定值),即在方法区中分配这些变量所使用的内存空间。
解析:解析阶段是指虚拟机将常量池中的符号引用替换为直接引用的过程。
初始化:到了初始化阶段,才真正开始执行类中定义的Java程序代码(或者说是字节码),初始化阶段是执行类构造器方法的过程。(方法是由编译器自动收集类中的类变量的赋值操作和静态语句块中的语句合并而成的){什么时候进行静态变量的初始化?}

3、自定义类加载器怎么实现?其中哪个方法走双亲委派模型,哪个不走?不走的话怎么加载类(破坏双亲委派模型的自定义类加载器的实现逻辑)

答:loadClass默认实现 :
java.lang.ClassLoader的loadClass(String, boolean)函数即实现了双亲委派模型!整个大致过程如下:

首先,检查一下指定名称的类是否已经加载过,如果加载过了,就不需要再加载,直接返回。
如果此类没有加载过,那么,再判断一下是否有父加载器;
如果有父加载器,则由父加载器加载(即调用parent.loadClass(name, false);)
如果没有父加载器或者是调用bootstrap类加载器来加载。

如果父加载器及bootstrap类加载器都没有找到指定的类,那么调用当前类加载器的findClass方法来完成类加载。换句话说,如果自定义类加载器,就必须重写findClass方法!

4、类加载器的类的缓存,key是什么?

答:key是限定类名,就是类名全称,例如: java.lang.String。(非限定(non-qualified)类名也叫短名,不带包的,例如:String。)

四、其他

1、字节码结构

答:
魔数+版本+常量池+类信息+字段信息+方法信息

魔数:确定是否为JVM可以接受的Class文件
次版本号
主版本号:JVM向下兼容,拒绝执行超过其版本号的Class文件
常量池大小:表示有多少项常量,其中第0项不用
访问标志:这个Class的类、接口信息
类索引、父类索引、接口索引集合 :这些索引都是指常量池的索引
字段描述索引:如果是I,就代表int

2、是否有GC调优实践过?GC调优调什么?

答:JVM内存模型和垃圾收集算法 (调整各代的内存比例和垃圾回收算法 )
希望达到一些目标:

  • GC的时间小、次数少

    要想GC时间小必须要一个更小的堆,要保证GC次数足够少,必须保证一个更大的堆,我们只能取其平衡

  • Full GC的周期长

    (1)JVM堆的设置 ,-Xms -Xmx

    (2)年轻带和老年代的比例(默认1:2),NewRadio

    大的年轻代会延长普通GC的周期,增加每次GC的时间,小的年老代会导致更频繁的Full GC
    小的年轻代会导致普通GC很频繁,但每次的GC时间会更短;大的年老代会减少Full GC的频率
    存在大量的临时对象,应该选择更大的年轻代;如果存在相对较多的持久对象,年老代应该适当增大
    原则:(A)本着Full GC尽量少的原则 ,一个项目半个月的Full GC就2次左右
    (B) 在不影响Full GC的前提下,根据实际情况加大年轻代
    

    (3)年轻带中伊甸园区和幸存者区的比例

    (4)垃圾收集器调整

    注重吞吐量的场景下用Parellel Scavenge + Parallel Old
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值