JVM八股文自述

1.介绍一下Java运行时内存区
答:在jdk1.6及之前,java内存区是分为堆区,方法区(运行时常量)也叫永久代,直接内存区(不属于运行时内存区),这是线程共享的,线程私有的是虚拟机栈,本地方法栈,程序计数器,jdk1.7将方法区移入了堆区,1.8直接将永久代移除,增加了元空间,是放在直接内存区的。
2.程序计数器
答:程序计数器是java内存中唯一一个不会出现OutOfMemoryError的区域。程序计数器的作用主要有两个,首先程序计数器控制了我们程序流程,如顺序执行,循环执行等等,我把它理解为下一条要读取指令的行号。其次就是当我们线程切换时,根据程序计数器来恢复到上一次程序执行的位置。程序计数器的生命周期是随着线程的创建而创建,随着线程的结束而死亡。
3.Java虚拟机栈
答:Java虚拟机栈的生命周期是与线程同步的,即随着线程创建而创建,随着线程死亡而结束。Java虚拟机栈是线程私有的,Java虚拟机栈里面存放了的是栈帧,栈帧里面有局部变量表以及动态链接。动态链接是指向该方法在运行常量区的位置(然后运行常量区的引用再指向堆)。局部变量表里存放数据的基本类型byte,short,int,long,char,boolean,float,double,以及对象的引用reference,当虚拟机栈不可动态扩容时,当线程请求栈深度超出虚拟机栈的时候会发生StackOverFlowError,当虚拟机栈可动态扩容的时候,当线程请求栈深度超出虚拟机栈深度,会发生OutOfMemoryError
4.方法或函数如何被调用
答:方法调用都有对应的栈帧被压入,方法执行完了对应的栈帧会被弹出。
5.本地方法栈
答:(在HotSpot虚拟机中,本地方法栈和Java虚拟机栈是合在一起的)本地方法栈主要是为了native方法(c/c++)服务的,基本单元也是栈帧。
6.堆
答:Java堆是虚拟机所管理内存中最大的一块,它唯一的目的就是存放对象实例。且Java堆是垃圾收集器管理的地方,因此也叫GC堆,Java堆分为新生代与老年代,再细分新生代又分为eden,s0,s1,即幸存0区,幸存1区。首先对象初始分配都会在eden,如果在第一次回收之后幸存下来,年龄加1进入幸存区(进入0还是1是随机的),当年龄加到15(默认值)就会进入老年代。进一步分区的目的是为了更好的回收内存和很快的分配内存。
7.方法区(永久代,1.8以移除改为元空间,放在直接内存)也叫非堆
答:里面存放了已加载的类信息,常量,静态变量,编译后的代码等数据。元空间存的是类的元数据。
8.JDK1.8为什么将永久代移除
答:之前永久代大小受限于JVM的内存大小,改为元空间之后使用的是直接内存,受计算机内存大小限制,避免发生OutOfMemoryError,这只是一个原因,更多的底层原因我也不晓得了。
9.运行时常量池
答:JDK1.7及之后的版本已将其从永久代中移出,放入堆中。会抛出OutOfMemoryError。存了静态变量和常量。
10.直接内存
答:不属于Java运行时数据区,不在虚拟机定义的内存范围内,受计算机内存大小的限制,也会产生OutOfMemoryError。
11.说⼀下Java对象的创建过程
答:Java对象创建分为5个步骤。类加载检查,分配内存,初始化零值,设置对象头,init对象。类加载检查是JVM遇到一条new指令的时候,首先看是否能定位到对应的符号引用,若有则判断符号引用代表的类是否被加载解析初始化过,若没有则先进行对应的类加载。分配内存则是为对象在堆内分配对应大小的内存空间。分配方式有指针碰撞和空闲列表两种。接下来就将对象除了头部的其他信息都初始化为对应类型的默认值或null,这保证了对象不赋值也可以使用。接着设置对象头,包括元数据信息,哈希码,GC分代年龄信息等。最后就是初始化类,如构造方法。
12.指针碰撞和空闲列表
答:当堆内存规整时,使用的内存全都放在一边,未使用的放在另一边,中间有个分界指针,然后我们只需以分界指针为起始移动对象内存大小位置即可,这种就是指针碰撞方式。当堆内存不规整时,虚拟机会维护一个列表,表示哪些被使用了哪些未被使用,根据这张表给对象分配一个足够大的位置即可,然后更新表,这就是空闲列表法。
13.堆是否规整和什么有关系
答:与GC收集器是否带有压缩功能有关,有压缩则规整,无压缩,则不规整。
14.内存分配的并发问题
答:在实际开发中,创建对象分配内存是很频繁的操作,且是有并发安全问题的,最简单的现象就是将同一块内存区域分配给两个对象,JVM是采用了两种方式来保证并发时内存分配的安全性。首先会为每个线程在eden区预先分配一段内存,每个线程创建对象优先在该区域创建,当该区域满了,再向公共的eden区域分配,此时会采用一个CAS+自旋的一个操作,在操作时比较中立指针位置是否和操作前相同,若相同则分配,若不相同则在自旋然后进行CAS。
15.对象的访问定位有哪两种方式
答:首先对对象的访问是通过存于虚拟机栈中的栈帧的reference来访问对应实例,根据reference指向不同分为两种访问方式。句柄形式以及直接指针。句柄形式就是refference指向的是堆中句柄池中的对应句柄,对应句柄再指向对象实例,好处是当对象实例地址发生变动,无需改变rference只需改动句柄就好。直接指针则是reference直接指向实例,好处是少了一层地址转换。
16.堆中对象分配策略
答:对象首先会在eden分配内存,当经过一次GC之后,对象头中的分代年龄变为1,并进入s0或s1区,之后随着每次GC,存活下来则年龄加1,当加到15则进入老年代。大对象会直接进入老年代。对于大对象的定义是有一个阈值。
17.minor GC,major GC,full GC
答:minorGC是新生代GC,当eden区内存不够的时候会进行minorGC,minorGC比较频繁,速度较快。majorGC则是老年代GC,速度比minorGC慢10倍以上。major GC是与full GC等价的,major GC也是一个对于整个堆的GC
18.如何判断对象是否死亡
答:主要有两种方法来判断对象是否死亡,第一种是引用计数法,给对象添加一个引用计数器,当每有一个地方引用它,计数器加一,当引用失效,计数器减1,当计数器为0时,就可以判断已死亡可以回收了。但是该方法有一个巨大的bug,就是无法解决循环依赖的问题,当对象A引用B,B引用A,这样的话A和B永远无法回收。第二种方法叫做可达性分析法,有一系列GC root节点,他们下面挂着他们引用的对象,这些子对象下面有挂着他们引用的对象,当某个对象无法从所有的GC root中找到的时候,说明该对象已死亡,可以回收。可以做GC root的节点有栈帧中引用的对象,(static)静态属性引用的变量,(final)常量属性引用的变量,native方法引用的对象(其实也是栈帧中引用的对象)
19.强引用,弱引用,软引用,虚引用
答:jdk1.2之前只有一种引用类型就是reference,jdk1.2之后对引用做了扩充分为强引用,软引用,弱引用,虚引用。其中强引用是指Object o=new Object()这个o就是一个强引用,就算发生OOM也不会回收强引用,软引用则是SoftReference s=new SoftReference(o);在内存充足的时候不会回收,当内存不够的时候会回收,适合做内存敏感的缓存,弱引用则是GC一旦发现就会回收,适合存那些可有可无的东西,WeakReference w=new WeakReference(o);虚引用则是需要和队列一起使用,用来监控对象的回收,当GC准备回收对象有虚引用前,会将虚引用放入队列,若检测到虚引用被放入队列,则可以在对象回收前采取一些行动PhantomReference
20.如何判断一个常量是废弃常量
答:比如运行时常量池中“abc”,若没有任何String对象引用该常量,若此时发生了GC且有必要的话“abc”会被回收
21.如何判断一个类是无用的类
答:需要满足以下三个条件才能算作无用的类。首先该类已经不存在任何的实例对象了,其次加载该类的ClassLoader已经被回收,最后对应的.Class文件没有被任何地方引用,即无法通过反射方式访问该类。无用的类可能被回收,但是不一定被回收。
22.垃圾收集有哪些算法?分别有什么特点
答:常见的有四种收集算法,分别是标记清除,标记整理,复制以及分代收集。标记清除就是先标记需要回收的对象,在标记完后在进行统一的清除,回产生比较多的不连续碎片。标记整理则先标记需要回收的对象,然后将不需要回收的对象向端边界移动,之后回收非端边界的。这样的好处就是不会产生不连续的内存碎片。复制收集算法,将堆内存分为大小的两块,每次回收将存活对象移到另外一块,剩下的全部回收。分代收集则是新生代与老年代采用不同的收集算法,如新生代每次都要回收大量对象,那么可以采用复制算法,仅将少许存活的对象复制到另外一半,剩下全部回收。老年代则由于存活时间较长,回收对象较少,则可以采用标记清除或者标记整理算法。
23.HotSpot为什么要分新生代和老年代
答:主要是为了提升GC的效率,在新生代垃圾对象占比很高,所以我们就操作存活对象。老年代存活对象占比很高,所以我们就操作较少的垃圾对象。
24.常见的垃圾回收器有哪些(7种)
答:Serial收集器,是一种单线程的回收器,用于新生代的回收,采用复制回收算法,在单CPU环境下,没有线程切换开销,简单高效。当Serial收集器开始工作,需要Stop the World。ParNew收集器,新生代,Serial收集的多线程版本,其余都一样。Parallel adventage收集器(jdk8默认收集器),新生代,在ParNew的基础上更加注重程序的吞吐量(吞吐量=(程序运行时间)/(程序运行时间+GC时间)),高吞吐量可以极大的提高CPU利用率尽快的完成运算。Serial Old,单线程,用于老年代的收集版本,采用标记整理算法。 Parallel Old,多线程,用于老年代的收集版本,采用标记整理算法。CMS收集器,第一款真正意义上的并发收集器,可以保证GC线程与用户线程几乎同时运行。是用于老年代的收集器,注重降低GC的停顿时间。GC过程分为四个步骤,分别是初始标记,会停止工作线程,用来初步标记与GC Root直连的对象。并发标记,继续进行GC Root跟进过程,不需要要STW,重新标记,检查那些因为并发而导致标记变动的对象,需要STW。清除标记,清除GC Root不可达对象,不需要STW。CMS的缺点是无法处理浮动垃圾,且由于采用的是标记清理算法,因此会导致较多不连续的内存碎片。G1收集器,JDK1.7出现的,将全堆分为大小相等的多个区域,eden,survior,old,humongous(巨大的)。每块区域只需管理块内对象的引用关系,当标记时,无需扫描整个堆。G1还维护了一张表用来记录每个区域回收的价值大小(回收后获取空间的大小以及回收时间的经验值),从而保证在有限时间内获得最大的价值。分为四个步骤,初始标记,并发标记,最终标记,筛选回收。会根据回收后的价值排序,根据用户期望的停顿时间来执行回收计划,回收时会STW。
25.类加载过程
答:一个类的初始化步骤为装载,连接以及初始化。装载是将.class文件装入内存(来源可能是jar包等等),连接又分为三部,分别验证,准备和解析。验证则是验证class文件的正确性,是否符合JVM规范,准备则是为静态变量分配内存空间,以及为final变量赋值,解析是将运行时常量取得符号引用指向分配好的内存,初始化则是初始化静态变量以及静态代码块
26.类加载器
答:Bootsstrap classloader启动类加载器,比如java.util包,java.lang包,java.io包,extention classloader扩展类加载器,如javax.Swing,appclassloader,应用类加载器,如我们自定义的类
27.双亲委派
答:每个类都有自己的类加载器,当我们加载一个类的时候,首先判断该类是否有被加载过,若有则直接返回。若没有,则先交给BootsStrap classloader加载若无法加载则让extenionclassloader加载若还无法加载则由appclassloader加载。
28.双亲委派有什么好处
答:保证了Java程序的稳定运行,防止类重复加载,也避免Java核心类被修改

你可能感兴趣的:(Java基础,面试问题,jvm,java)