大数据常见面试题之JVM

一.请说明一下Java虚拟机地作用是是什么

  • 解释运行字节码程序消除平台相关性
  • JVM将Java字节码解释为具体平台地具体指令.一般地高级语言如果在不同地平台上运行,至少需要编译成不同地目标代码.而引进JVM之后,Java语言在不同地平台上运行时不需要重新编译.Java语言使用模式Java虚拟机屏蔽了与具体平台相关地信息,使得Java语言编译程序只需生成在Java虚拟机上运行地目标代码(字节码),就可以在多平台上不加修改地运行.Java虚拟机在执行字节码时,把字节码解释成具体平台上地机器指令执行

二.Java内存结构

  • 方法区是所有线程共享地内存区域:而Java栈,本地方法栈和程序计数器是运行在线程私有地内存区域

  • Java堆(Heap):是Java虚拟机所管理地内存中最大的一块.Java堆是被所有线程共享地一块内存区域,在虚拟机启动时创建.此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存

  • 方法区(Method Area):与堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据

  • 程序计数器(Program Counter Register):是一块较小的内存空间,它的作用可以看作是当前线程所执行的字节码的行号指示器

  • JVM栈(JVM Stacks):与程序计数器一样也是私有的,它的生命周期与线程相同.虚拟机描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表,操作栈,动态链接,方法出口等信息.每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机中从入栈到出栈的过程

  • 本地方法栈(Native Method Stacks):与虚拟机所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到Native方法服务

三.解释栈,堆,方法区的用法

  • 通常我们定义一个基本数据类型的变量,一个对象的引用,还有就是函数调用的现场保存都是以JVM中的栈空间
  • 而通过new关键字和构造器创建的对象则放在堆空间,堆是垃圾收集器管理的主要区域,由于现在的垃圾收集器都采用分代收集算法,所以堆空间还可以细分为新生代和老生代,再具体一点可以分为Eden,Survivor(又可分为From Survivor,To Survivor),Tenured
  • 方法区和堆都是各个线程共享的内存区域,用于存储已经被JVM加载的类信息,常量,静态变量,JIT编译器编译后的代码等数据
  • 程序中的字面量(literal)如直接书写的100,"hello"和常量都是放在常量池中,常量池是方法区的一部分
  • 栈空间操作起来最快但是栈很小,通常大量的对象都是放在堆空间内,栈和堆的大小都可以通过JVM的启动参数来进行调整,栈空间用光了会引发StackOverflowError,而堆和常量池空间不足则会引发OutOfMemoryError

四.对象分配规则

  • 对象优先分配在Eden区,如果Eden区没有足够的空间时,虚拟机执行一次Minor GC.
  • 大对象直接进入老年代(大对象是指需要大量连续内存空间的对象),这样做的目的是避免在Eden区和两个Survivor区之间发生大量的内存拷贝(新生代采用复制算法收集内存)
  • 长期存活的对象进入老年代,虚拟机为每个对象定义了一个年龄计数器,如果对象经过了一次Minor GC 那么对象会进入Survivor区,之后每经过一次Minor GC那么对象的年龄加1,直到达到阀值对象进入老年区
  • 动态判断对象的年龄,如果Survivor区中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于等于该年龄的对象可直接进入老年代
  • 空间分配担保,每次进行Minor GC 时,JVM会计算Survivor区移至老年区的对象的平均大小,如果这个值大于老年区的剩余值大小则进行一次 Full GC,如果小于检查HandlePromotionFailure设置,如果true则只进行Monitor GC,如果false则进行 Full GC

五.假设一个场景,要求stop the world时间非常短,你会怎么会设计垃圾回收机制

  • 绝大多数新创建的对象分配在Eden区
  • 在Eden区发生一次GC后,存活的对象移到其中一个Survivor区
  • 在Eden区发生一次GC后,对象是放到Survivor区,这个Survivor区已经存在其他存活的对象
  • 一旦一个Survivor区已满,存活的对象移动到另外一个Survivor区,然后之前那个空间已满Survivor区将置空,没有任何数据
  • 经过重复多次这样的步骤后依旧存活的对象将被移动到老年代

六.Eden区和Survial区的含义和工作原理

  • 目前主流的虚拟机实现都采用了分代收集的思想,把整个堆区划分为新生代和老年代,老年代又被划分为Eden空间,From Survivor和 To Survivor 三块区域
  • 我们把Eden:From Survivor:To Survivor空间大小设成8:1:1,对象总是在Eden区出生,From Survivor保存当前的幸存对象,To Survivor为空.一次GC发生后:
  • 1.Eden区活着的对象+From Survivor存储的对象被复制到To Survivor
  • 2.清空Eden 和 From Survivor
  • 3.颠倒From Survivor和To Survivor的逻辑关系:From变To,To变From.
  • 可以看出,只有在Eden区快满的时候才会触发Minor GC.而Eden空间占据新生代的绝大部分,所以Minor GC的频率得以降低.当然,使用两个Survivor这种方式我们也付出了一定的代价,如
    10%的空间浪费,复制对象的开销等

七.垃圾回收算法

标记清除

  • 标记-清除算法将垃圾回收分为两个阶段L标记阶段和清除阶段.在标记阶段首先通过根结点,标记所有从根节点开始的对象,未被标记的对象就是未被引用的垃圾对象.然后在清除阶段,清除所有未被标记的对象.标记清除算法带来的一个问题是会存在大量的空间碎片,因为回收后的空间是不连续的,这样给大对象分配内存的时候可能会提前触发full gc

复制算法

  • 将现有的内存空间分为两块,每次只使用其中一块,在垃圾回收时将正在使用的内存中的存活对象复制到未被使用的内存块中,之后,清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收
  • 现有的商业虚拟机都采用这种收集算法来回收新生代,IBM研究表明新生代中的对象98%都是朝夕生死的,所以并不需要按照1:1的比例划分内存空间,而是将内存划分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中的一块Survivor.当回收时,将Eden和Survivor中还存活的对象一次性地拷贝到另外一个Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间.HotSpot虚拟机默认Eden和Survivor地大小比例是8:1(可以通过SurvivorRattio来配置),也就是每次新生代中可用内存空间为整个新生代容量地90%,只有10%地内存会被"浪费".当然,98%的对象可回收只是一般场景下的数据,我们也没有办法保证回收都只有不多于10%的对象存活,当Survivor空间不够用时吗,需要依赖其他内存(这里指老年代)进行分配担保

标记整理

  • 复制算法的高效性是建立在存活对象少,垃圾对象多的前提下的.这种情况在新生代经常发生,但是在老年代更常见的情况是大部分对象都是存活对象.如果依然使用复制算法,由于存活的对象较多,复制的成本也很高
  • 标记-压缩算法是一种老年代的回收算法,他在标记-清除的算法的基础上做了一些优化.首先也需要从根节点开始对所有可达对象做一次标记,但之后,他并不简单地清理未标记的对象,而是将所有的存活对象压缩到内存的一端.之后,清理边界外所有的空间.这种方法既避免了碎片的产生,又不需要两块相同的内存空间,因此,性价比比较高

增量算法

  • 增量算法的基本思想是,如果一次性将所有的垃圾进行处理,需要造成系统长时间的停顿,那么就可以让垃圾收集线程和应用程序线程交替运行.每次,垃圾收集线程只收集一小片区域的内存空间,接着切换到应用程序线程,依次反复,直到垃圾收集完成.使用这种方法,由于在垃圾回收过程中,间断性地还执行了应用程序代码,所以能减少系统地停顿时间.但是,由于线程切换和上下文转换的消耗,会使得垃圾回收的总体成本上升,造成系统吞吐量的下降

八.垃圾回收器

1.Serial收集器

  • Serial收集器是最古老的收集器,他的缺点是当Serial收集器想进行垃圾回收时,必须暂停用户的所有进程,即stop the world.到现在为止,他依然是虚拟机运行在client模式下的默认新生代收集器,与其他收集器相比,对于限定在单个CPU的运行环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾回收自然可以获得最高的单线程收集效率\
  • Serial Old是Serial收集器的老年代版本,它同样是一个单线程收集器,使用"标记-整理"算法.这个收集器的主要意义也是client模式下的虚拟机使用.Serial模式下,它主要还有两个用途:一个是在jdk1.5以及以前的版本中与ParallelSscanvenge收集器搭配使用,另外一个就是作为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure的时候使用
  • 通过指定-UseSerialGC参数,使用Serial+Serial Old的串行收集器组合进行内存回收

2.ParNew 收集器

  • ParNew收集器是Serial收集器新生代的多线程实现,注意在进行垃圾回收的时候依然会stop the world,只是相比较Serial收集器而言它会运行多条进程进行垃圾回收
  • ParNew收集器在单CPU的环境中绝对不会比Serial收集器有更好的效果,甚至由于存在线程交互的开销,该收集器在通过超线程奇数实现的两个CPU的环境中都不能百分百的保证能超越Serial收集器.当然,随着可以使用的CPU数量增加,它对于GC时系统资源的利用还是很有好处的.它默认开启的收集线程数与CPU数量相同,在CPU非常多(比如32个,现在CPU最少4核加超线程,服务器超过32个逻辑CPU的情况越来越多)的环境下,可以使用-XX:ParallelGCThreads参数来限制垃圾收集的线程数
  • -UseParNewGC:打开此开关后,使用ParNew+Serial Old的收集器组合进行内存回收,这样新生代使用并行收集器,老年代使用串行收集器

3.Parallel Scavenge收集器

  • Parallel是采用复制算法的多线程新生代垃圾回收器,似乎和ParNew收集器有很多相似的地方.但是Parallel Scanvenge收集器的一个特点是它所关注的目标是吞吐量(Throughput).所谓吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即吞吐量=运行运行用户代码时间/(运行用户代码时间+垃圾收集时间).停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能够提升用户的体验;而高吞吐量则可以最高效率地利用CPU时间,尽快地完成程序地运算任务,主要适合在后台运算而不需要太多交互地任务.
  • Parallel Old收集器 是Parallel Scavenge收集器的老年代版本,采用多线程和标记整理算法.这个收集器是在jdk1.6中才开始提供的.在此之前,新生代的Parallel Scavenge收集器一直处于比较尴尬的状态.原因是如果新生代Parallel Scavenge收集器,那么老年代除了Serial Old(PS MarkSweep)收集器外别无选择.由于单线程的老年代Serial Old收集器在服务端应用性能上的拖累,即使使用了Parallel Scavenge收集器也未必能在整体应用上获得吞吐量最大化的效果,又因为老年代收集中无法充分利用服务器多CPU的处理能力,在老年代很大而且硬件比较高级的环境中,这种组合的吞吐量甚至还不一定有ParNew加CMS组合给力.直到Parallel Old收集器出现后,吞吐量优先收集器终于有了比较名副其实的应用祝贺,在注重吞吐量和CPU资源敏感的场合,都可以考虑Parallel Scavenge加Parallel Old收集器
  • -UseParallelGC:虚拟机运行在Server模式下的默认值,打开此开关后,使用Parallel Scavenge+Serial Old 的收集器组合进行内存回收.-UseParallelOldGC:打开此开关后,使用Parallel Scavenge+Parallel Old的收集器组合进行垃圾回收

4.CMS收集器

  • CMS(Concurrent Mark Sweep)收集器是一个比较重要的收集器,现在应用非常广泛,我们重点来看下,CMS一种获取最短回收停顿时间为目标的收集器,这使得它很适用于和用户交互的业务,从名字就可以看出,CMS收集器是基于标记和清除算法实现的,分为四个步骤
  • 1)初始标记(initial mark)
  • 2)并发标记(concurrent mark)
  • 3)重新标记(remark)
  • 4)并发清除(concurrent sweep)
  • 注意初始标记和重新标记还是会stop the world,但是在耗费时间更长的并发标记和并发清除两个阶段都可以和用户进程同时工作

5.G1收集器

  • G1收集器是一款面向服务端应用的垃圾收集器.HotSpot团队赋予它的使命是在未来代替换掉jdk1.5中发布的CMS收集器.与其他GC收集器相比,G1具备如下特点:
  • 并发与并发:G1能更充分的利用CPU,多核环境下的硬件优势来缩短stop the world的停顿时间
  • 分代收集:和其他收集器一样,分代的概念在G1中依然存在,不过G1不需要其他的垃圾回收器的配合就可以独自管理整个GC堆
  • 空间整合:G1收集器有利于程序长时间运行,分配大对象时不会无法得到连续的空间而提前触发一次GC
  • 可预测的非停顿:这是G1相对于CMS的另一大优势,降低停顿时间是G1和CMS共同的关注点,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集器的时间不得超过N毫秒

九.主要的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:SurvivorRation=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	//由于并发收集器不对内存空间进行压缩整理,所以运行一段时间后会产生碎片,使得运行效率降低.此值设置运行多少次GC后对内存空间进行压缩整理
-XX:+UseCMSCompactAtFullCollection:	//打开对老年代的压缩.可能会影响性能,但可以消除碎片

3.辅助信息相关

-XX:+PrintGC //输出形式
-XX:+PrintGCDetails //输出形式

猜你喜欢

转载自blog.csdn.net/sun_0128/article/details/107656879
今日推荐