JVM与垃圾回收面试总结

1. JVM垃圾回收机制与实现

  • 堆:所有的对象实例与数组,GC堆,分为新生代与老年代
  • 栈:栈帧包含局部变量表(基本数据类型 8种、对象引用类型)、操作数栈、动态链接、方法出口
  • 方法区:类信息、常量、静态变量、即时编译器编译后的代码等数据,也成为永久代

一般说栈指的是 虚拟机栈,或者说是虚拟机栈中的局部变量表

TLAB:本地线程分配缓冲,线程分配内存,现用TLAB分配,用完重新分配新的TLAB
可以设置是否启用TLAB

Mark Word:对象头:对象自身的运行时数据,哈希码,GC分代年龄,锁状态,持有的锁,偏向线程ID,偏向时间戳

HotSpot采用直接指针访问,栈中直接指向对象的地址,对象移动时,需要改变栈中的reference

2. 垃圾回收算法

  • 标记清除算法 Mark-sweep
    存在效率问题与空间问题(产生大量不连续的内存碎片)
  • 复制算法(Copying):应用十分广泛,将内存分为一块较大的Eden和两块较小的Survivor空间,每次使用Eden和其中一块Survivor。回收时,将Eden与Survivor复制到另一块Survivor是哪个。
    默认Eden与Survivor的比例是8:1
  • 标记整理算法(Mark-Compact) 适用于老年代,将所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存

3. 垃圾回收的其他概念

  • Stop The World:从GC Roots进行可达性分析是,为了保持一致性,停顿所有的Java执行线程。就像时间冻结
  • 准确式GC:虚拟机可以知道内存中某个位置的数据具体是什么类型
  • OopMaps:用来记录对象引用,HotSpot使用,类加载过程中,把对象内的偏移量上是什么数据计算出来。
  • 安全点:SafePoint 既程序执行时并非在所有地方都能停顿下来,只有安全点才能停顿。程序长时间执行的特征,例如:方法调用、循环跳转、异常跳转等
  • 抢先式中断和主动式中断:抢先式首先把所有的线程全部中断,如果发现有线程不在安全点上,则回复线程;主动式中断:通过设置标志,线程执行时轮询标志,发现标志则自己中断。
  • 安全区域(Safe Region):如果程序不执行的时候,就没法中断,所以需要安全区域,一段代码片段中,应用关系不发生变化,则为安全区域

4. 垃圾回收器

  1. Serial:新生代,采用复制算法,老年代采用标记整理算法,单线程,需要Stop The Worls,Client模式下的默认新生代收集器
  2. ParNew:Serial的多线程版本,新生代采用复制算法,老年代采用标记整理算法。Server模式的首选新生代收集器,因为目前只有它与Serial能与CMS收集器配合工作,在多CPU>2的情况下,有更好的性能
  3. Parallel(并行) Scavenge:新生代收集器,复制算法,关注吞吐量,吞吐量=运行用户代码的时间/(运行用户代码的时间+垃圾收集时间),适合在后台运算而不需要太多交互的任务。可以设置吞吐量大小eg:GCTimeRatio,也成为吞吐量优先收集器。可以自动控制新生代的大小、Eden与Suvivor的比例、晋升老年代的年龄,自适应调节策略是Paralle Scavenge与Parnew的最大区别
  4. Serial Old:serial老年代版本,client模式下使用,可以为Paralle Scavenge收集器搭配使用,或者作为CMS的后备
  5. Parallel Old:Parallel Scavenge的老年代版本,标记整理算法,注重吞吐量与CPU资源敏感,可以用优先使用Parallel Scavengen+Parallel Old
  6. CMS(Concurrent并发 Mark Sweep):一种获取最短回收停顿时间为目标的收集器。目前大量使用在互联网站或者B/S系统的服务端上,尤其重视服务的响应速度。基于标记-清除算法

  7. image
4个步骤
1.初始标记 需要Stop the World标记一下GCRoots能够直接关联到的对象,速度很快
2.并发标记 进行GCRoots Tracing的过程,耗时较长,并发
3.重新标记 需要Stop the World为了修正并发标记期间因为用户程序继续运作
而导致标记产生变动的那一部分对象的标记记录,比初始标记阶段阶段稍长,比并发标记时间短得多
4.并发清除:耗时较长,并发
5.并发重置 :这个阶段,重置CMS收集器的数据结构,等待下一次垃圾回收。

CMS的优点:并发收集、低停顿
对CPU资源非常敏感,因为并发标记与并发清理的过程会占用CPU,
默认启动的回收线程数是(CPU数量+3)/4,当CPU 小于4时,占用了不少于%25的CPU资源。
CPU越多,占用降低,因此提出了ICMS(Incremental Concurrent Mark Sweep)增量式并发收集器

CMS无法处理浮动垃圾(Floating Garbage)因此可能出现Concurrent Mode Failure,
导致另一次Full GC的出现。
老年代使用达到一定标准,就会激活CMS,JDK1.5默认%68,1.6默认%92,
如果运行期间预留的内存无法满足程序需要,就会出现Concurrent Mode Failure,临时启用Serial OLD

由于使用标记-清除算法,产生了大量空间碎片,
如果无法找到足够大的连续空间来分配当前对象,不得不触发另一个Full GC。

G1 收集器 Garbage First。一款新的面向服务端的收集器

 并行与并发:充分利用多CPU来缩短Stop The World的时间
 分代收集:不需要其他收集器,可以独立管理GC堆
 空间整合:整体上是标记-整理算法,局部上是复制算法
 可预测的停顿:建立可预测的停顿时间模型

 通过将堆空间划分成多个相等的独立区域,新生代与老年代不再是物理隔离的,他们都是一部分Region的集合。

 有计划的避免Full GC,G1跟踪各个Region里面的垃圾的价值大小
 (回收所获得的的空间大小与回收所需时间的比值),
 在后台维护一个优先列表。每次都根据允许的收集时间,优先回收价值最大的Region。
 1 初始标记 标记GCRoots 能够直接关联到的对象,并且修改TAMS
 2 并发标记 从GC roots进行可达性分析
 3 最终标记 修正标记期间因为用户程序继续运作而导致的标记变化
 4 筛选回收 制定回收计划,进行回收,与CMS不同的是,筛选回收过程在最终标记后直接进行,需要Stop The world

 由于只回收一部分Region,时间是用户可以控制的,而且停顿用户线程将大幅度提高收集效率。

5. 新生代与老年代的划分

  1. 对象有限分配在Eden,Eden没空间则进行一次Minor GC(新生代GC)
  2. 长期存活的对象,进入老年代,默认为15次,初始为0,每经过一次Minor GC+1,默认15晋升老年代,也可以调整阀值
  3. 大于阀值 PretenureSizeTreshold的直接进入老年代,避免Eden与两个Survivor产生大量的内存复制。
  4. 动态对象年龄判定:如果在Survivor中相同连年大小的总和大于Survivor的一般,年龄大于或者等于该年龄的对象直接进入老年代

    空间分配担当:如果老年代最大可用的连续空间大于新生代所有的对象的总空间,Minor Gc则可以确保是安全的的,可以设置是否允许担保失败,如果允许,则尝试进行MinorGC,否则进行FullGC。如果MinorGC存活对象过多,出现了HandlePromotionFailure,则在失败后发起FullGC。

    一般允许担保失败,避免FullGC过于频繁。

6. FullGC 、Minor Gc 、Major GC

Full==MajorGC

Minor GC:新生代垃圾回收,非常频繁,一般速度比较快
Major Gc:老年代垃圾回收,经常会伴随一次MinorGC,一般比MinorGC慢10倍以上

7. 判断对象是否存活一般有两种方式

  • 引用计数:每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收。此方法简单,无法解决对象相互循环引用的问题。
  • 可达性分析(Reachability Analysis):从GC Roots开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,不可达对象。

8. 哪些对象可作为GC Roots对象?

虚拟机栈中应用的对象

方法区里面的静态对象

方法区常量池的对象

本地方法栈JNI应用的对象

9. class加载的过程

首先,在代码编译后,就会生成JVM(Java虚拟机)能够识别的二进制字节流文件(*.class)。而JVM把Class文件中的类描述数据从文件加载到内存,并对数据进行校验、转换解析、初始化,使这些数据最终成为可以被JVM直接使用的Java类型,这个说来简单但实际复杂的过程叫做JVM的类加载机制。

image

我们平常说的加载大多不是指的类加载机制,只是类加载机制中的第一步加载。

JVM主要完成三件事:

  1. 通过一个类的全限定名(包名与类名)来获取定义此类的二进制字节流(Class文件)。而获取的方式,可以通过jar包、war包、网络中获取、JSP文件生成等方式。

  2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。这里只是转化了数据结构,并未合并数据。(方法区就是用来存放已被加载的类信息,常量,静态变量,编译后的代码的运行时内存区域)

  3. 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。这个Class对象并没有规定是在Java堆内存中,它比较特殊,虽为对象,但存放在方法区中。

启动类加载器:Bootstrap ClassLoader,负责加载存放在JDK\jre\lib(JDK代表JDK的安装目录,下同)下,或被-Xbootclasspath参数指定的路径中的,并且能被虚拟机识别的类库(如rt.jar,所有的java.*开头的类均被Bootstrap ClassLoader加载)。启动类加载器是无法被Java程序直接引用的。

扩展类加载器:Extension ClassLoader,该加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载JDK\jre\lib\ext目录中,或者由java.ext.dirs系统变量指定的路径中的所有类库(如javax.*开头的类),开发者可以直接使用扩展类加载器。

应用程序类加载器:Application ClassLoader,该类加载器由sun.misc.Launcher$AppClassLoader来实现,它负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

应用程序都是由这三种类加载器互相配合进行加载的,如果有必要,我们还可以加入自定义的类加载器。因为JVM自带的ClassLoader只是懂得从本地文件系统加载标准的java class文件,因此如果编写了自己的ClassLoader,便可以做到如下几点:

1)在执行非置信代码之前,自动验证数字签名。

2)动态地创建符合用户特定需要的定制化构建类。

3)从特定的场所取得java class,例如数据库中和网络中。

JVM类加载机制

•全盘负责,当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入

•父类委托,先让父类加载器试图加载该类,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类

•缓存机制,缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区寻找该Class,只有缓存区不存在,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区。这就是为什么修改了Class后,必须重启JVM,程序的修改才会生效

类加载有三种方式:

1、命令行启动应用时候由JVM初始化加载

2、通过Class.forName()方法动态加载

3、通过ClassLoader.loadClass()方法动态加载

10. 双亲委托模型

从JDK1.2开始,java虚拟机规范推荐开发者使用双亲委派模式(ParentsDelegation Model)进行类加载,其加载过程如下:

(1).如果一个类加载器收到了类加载请求,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器去完成。

(2).每一层的类加载器都把类加载请求委派给父类加载器,直到所有的类加载请求都应该传递给顶层的启动类加载器。

(3).如果顶层的启动类加载器无法完成加载请求,子类加载器尝试去加载,如果连最初发起类加载请求的类加载器也无法完成加载请求时,将会抛出ClassNotFoundException,而不再调用其子类加载器去进行类加载。

双亲委派模型意义:

  • 系统类防止内存中出现多份同样的字节码

  • 保证Java程序安全稳定运行

双亲委派 模式的类加载机制的优点是java类它的类加载器一起具备了一种带优先级的层次关系,越是基础的类,越是被上层的类加载器进行加载,保证了java程序的稳定运行。双亲委派模式的实现:

image

http://blog.csdn.net/p10010/article/details/50448491

11. 如何 加载自定义的Jar包

两种办法
1. 使用URLClassloader来进行加载,需要指定路径,可以加载子类和方法的实现类
2. 使用manifest.mf文件来加载,将jar包填入classpath里面

12. Java JMX

JAVA_OPTS=-Dcom.sun.management.jmxremote

13. Java远程调试

Java远程调试的原理是两个VM之间通过debug协议进行通信,然后以达到远程调试的目的。两者之间可以通过socket进行通信。

首先被debug程序的虚拟机在启动时要开启debug模式,启动debug监听程序。jdwp是Java Debug Wire Protocol的缩写。

java -Xdebug -Xrunjdwp:server=y,transport=dt_socket,address=8000,suspend=n zhc_application

这是jdk1.7版本之前的方法,1.7之后可以这样用:

java -agentlib:jdwp=transport=dt_socket,address=8000,server=y,suspend=n zhc_application

然后用一个debug客户端去debug远程的程序了,比如用Eclipse自带的debug客户端,填写运行被debug程序的虚拟机监听的端口号和地址,选择connect方式为attach。

14. 调优参数

参数 说明 默认值
-Xms 初始堆大小 物理内存的1/64(<1GB) 默认(MinHeapFreeRatio参数可以调整)空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制.
-Xmx 最大堆大小 物理内存的1/4(<1GB) 默认(MaxHeapFreeRatio参数可以调整)空余堆内存大于70%时,JVM会减少堆直到 -Xms的最小限制
-Xmn 年轻代大小(1.4or lator)注意:此处的大小是(eden+ 2 survivor space).与jmap -heap中显示的New gen是不同的。 整个堆大小=年轻代大小 + 年老代大小 + 持久代大小.增大年轻代后,将会减小年老代大小.此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8
-Xss 每个线程的堆栈大小 JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K.更具应用的线程所需内存大小进行 调整.在相同物理内存下,减小这个值能生成更多的线程.但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右一般小的应用, 如果栈不是很深, 应该是128k够用的 大的应用建议使用256k。这个选项对性能影响比较大,需要严格的测试。(校长)和threadstacksize选项解释很类似,官方文档似乎没有解释,在论坛中有这样一句话:””-Xss is translated in a VM flag named ThreadStackSize”一般设置这个值就可以了。

你可能感兴趣的:(学习笔记,面试攻略)