本文是在网上看了各种面试指南收集的题目及答案。无意冒犯各位原创作者,如果在您的博客或者写作平台有相似问题答案可以跟我说,我给您链接加上,我只是为了方便以后自己需要的时候刷一刷,不用在到处找题。
JVM垃圾处理方法(标记清除、复制、标记整理)
• 标记-清除算法
○ 标记阶段:先通过根节点,标记所有从根节点开始的对象,未被标记的为垃圾对象
○ 清除阶段:清除所有未被标记的对象
• 复制算法
○ 将原有的内存空间分为两块,每次只使用其中一块,在垃圾回收时,将正在使用的内存中的存活对象复制到未使用的内 存块中,然后清除正在使用的内存块中的所有对象。
• 标记-整理
○ 标记阶段:先通过根节点,标记所有从根节点开始的可达对象,为被标记的为垃圾对象
○ 整理阶段:将所有的存活对象压缩到内存的一段,之后清理边界所有的空间
• 三种算法的比较
○ 效率:复制算法 > 标记/整理算法 > 标记/清除算法(此处的效率只是简单的对比时间复杂度,实际情况不一定如 此)。
○ 内存整齐度:复制算法=标记/整理算法>标记/清除算法。
○ 内存利用率:标记/整理算法=标记/清除算法>复制算法。
JVM如何GC,新生代,老年代,持久代,都存储哪些东西,以及各个区的作用?
• 新生代
○ 在方法中去new一个对象,那这方法调用完毕后,对象就会被回收,这就是一个典型的新生代对象。
• 老年代
○ 在新生代中经历了N次垃圾回收后仍然存活的对象就会被放到老年代中。而且大对象直接进入老年代
○ 当Survivor空间不够用时,需要依赖于老年代进行分配担保,所以大对象直接进入老年代
• 永久代
○ 即方法区。
GC用的引用可达性分析算法中,哪些对象可作为GC Roots对象?
• Java虚拟机栈中的对象
• 方法区中的静态成员
• 方法区中的常量引用对象
• 本地方法区中的JNI(Native方法)引用对象。
什么时候进行MinGC,FullGC
• MinGC
○ 新生代中的垃圾收集动作,采用的是复制算法
○ 对于较大的对象,在Minor GC的时候可以直接进入老年代
• FullGC
○ Full GC是发生在老年代的垃圾收集动作,采用的是标记-清除/整理算法。
○ 由于老年代的对象几乎都是在Survivor区熬过来的,不会那么容易死掉。因此Full GC发生的次数不会有Minor GC那么 频繁,并且Time(Full GC)>Time(Minor GC)
各个垃圾收集器是怎么工作的
• Serial收集器
○ 是一个单线程的收集器,不是只能使用一个CPU。在进行垃圾收集时,必须暂停其他所有的工作线程,直到收集结束。
○ 新生代采用复制算法,Stop-The-World
○ 老年代采用标记-整理算法,Stop-The-World
○ 简单高效,Client模式下默认的新生代收集器
• ParNew收集器
○ ParNew收集器是Serial收集器的多线程版本
○ 新生代采用复制算法,Stop-The-World
○ 老年代采用标记-整理算法,Stop-The-World
○ 它是运行在Server模式下首选新生代收集器
○ 除了Serial收集器之外,只有它能和CMS收集器配合工作
• ParNew Scanvenge收集器
○ 类似ParNew,但更加关注吞吐量。目标是:达到一个可控制吞吐量的收集器。
○ 停顿时间和吞吐量不可能同时调优。我们一方面希望停顿时间少,另外一方面希望吞吐量高,其实这是矛盾的。因为:在GC的时候,垃圾回收的工作总量是不变的,如果将停顿时间减少,那频率就会提高;既然频率提高了,说明就会频繁的进行GC,那吞吐量就会减少,性能就会降低。
• G1收集器
○ 是当今收集器发展的最前言成果之一,对垃圾回收进行了划分优先级的操作,这种有优先级的区域回收方式保证了它的高效率
○ 最大的优点是结合了空间整合,不会产生大量的碎片,也降低了进行gc的频率
○ 让使用者明确指定指定停顿时间
• CMS收集器:(Concurrent Mark Sweep:并发标记清除老年代收集器)
○ 一种以获得最短回收停顿时间为目标的收集器,适用于互联网站或者B/S系统的服务器上
○ 初始标记(Stop-The-World):根可以直接关联到的对象
○ 并发标记(和用户线程一起):主要标记过程,标记全部对象
○ 重新标记(Stop-The-World):由于并发标记时,用户线程依然运行,因此在正式清理前,再做修正
○ 并发清除(和用户线程一起):基于标记结果,直接清理对象
○ 并发收集,低停顿
Java虚拟机内存的划分,每个区域的功能
• 程序计数器(线程私有)
○ 线程创建时创建,执行本地方法时其值为undefined。
• 虚拟机栈(线程私有)
○ (栈内存)为虚拟机执行java方法服务:方法被调用时创建栈帧-->局部变量表->局部变量、对象引用
○ 如果线程请求的栈深度超出了虚拟机所允许的深度,就会出现StackOverFlowError。-Xss规定了栈的最大空间
○ 虚拟机栈可以动态扩展,如果扩展到无法申请到足够的内存,会出现OOM
• 本地方法栈(线程私有)
○ java虚拟机栈是为虚拟机执行java方法服务的,而本地方法栈则为虚拟机执使用到的Native方法服务。
○ Java虚拟机没有对本地方法栈的使用和数据结构做强制规定。Sun HotSpot把Java虚拟机栈和本地方法栈合二为一
○ 会抛出StackOverFlowError和OutOfMemoryError
• Java堆
○ 被所有线程共享,在Java虚拟机启动时创建,几乎所有的对象实例都存放到堆中
○ GC的管理的主要区域
○ 物理不连续,逻辑上连续,并可以动态扩展,无法扩展时抛出OutOfMemoryError
• 方法区
○ 用于存储已被虚拟机加载的类信息、常量、静态变量、即使编译器编译后的代码的数据
○ Sun HotSpot 虚拟机把方法区叫做永久待(Permanent Generation) • 运行时常量池
○ 受到方法区的限制,抛出OutOfMemoryError
用什么工具可以查出内存泄漏
• MemoryAnalyzer:一个功能丰富的JAVA堆转储文件分析工具,可以帮助你发现内存漏洞和减少内存消耗
• EclipseMAT:是一款开源的JAVA内存分析软件,查找内存泄漏,能容易找到大块内存并验证谁在一直占用它,它是基于Eclipse RCP(Rich Client Platform),可以下载RCP的独立版本或者Eclipse的插件
• JProbe:分析Java的内存泄漏。
JVM如何加载一个类的过程,双亲委派模型中有哪些方法有没有可能父类加载器和子类加载器,加载同一个类?如果加 载同一个类,该使用哪一个类?
• 双亲委派概念
○ 如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的加载器都是如此,因此所有的类加载请求都会传给顶层的启动类加载器,只有当父加载器反馈自己无法完成该加载请求(该加载器的搜索范围中没有找到对应的类)时,子加载器才会尝试 自己去加载。
• 加载器
○ 启动(Bootstrap)类加载器:是用本地代码实现的类装入器,它负责将 <Java_Runtime_Home>/lib下面的类库加载到内存中(比如rt.jar)。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。
○ 标准扩展(Extension)类加载器:是由Sun的ExtClassLoader(sun.misc.LauncherAppClassLoader)实现的。它负责将系统类路径(CLASSPATH)中指定的类库加载到内存中。开发者可以直接使用系统类加载器。除 了以上列举的三种类加载器,还有一种比较特殊的类型 — 线程上下文类加载器。
• 如果加载同一个类,该使用哪一个类?
○ 父类的
JVM线程死锁,你该如何判断是因为什么?如果用VisualVM,dump线程信息出来,会有哪些信息
常常需要在隔两分钟后再次收集一次thread dump,如果得到的输出相同,仍然是大量thread都在等待给同一个 地址上锁,那么肯定是死锁了
什么是类的加载
类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。
类的生命周期
•加载,查找并加载类的二进制数据,在Java堆中也创建一个java.lang.Class类的对象
•连接,连接又包含三块内容:验证、准备、初始化。1)验证,文件格式、元数据、字节码、符号引用验证;2)准备,为类的静态变量分配内存,并将其初始化为默认值;3)解析,把类中的符号引用转换为直接引用
•初始化,为类的静态变量赋予正确的初始值
•使用,new出对象程序中使用
•卸载,执行垃圾回收
判断对象是否存活一般有两种方式:
•引用计数:每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收。此方法简单,无法解决对象相互循环引用的问题。
•可达性分析(ReachabilityAnalysis):从GCRoots开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GCRoots没有任何引用链相连时,则证明此对象是不可用的,不可达对象。