1)Java内存模型
程序计数器:当前执行的字节码行号指示器,字节码指示器就根据这个计数器的值来选取下一条指令,分支、循环、跳转、异常处理、线程恢复等基础功能都要依赖这个计数器来完成。是Java虚拟机内存规范中唯一一个没有规定任何OutOfMemoryError区域。
本地方法栈:不是用Java语言写的,而是用C/C++编写的本地方法。它相当于一个接口,方便与Java环境外部和操作系统的交互。
虚拟机栈:为虚拟机执行Java方法服务。每个方法在执行的同时,创建一个栈帧,用于存储局部变量表、动态链接、方法出口等信息。每一个方法从调用到结束的过程对应的就是一个栈帧在虚拟机中入栈和出栈的过程。
方法区:所有Java线程共享的一块儿内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量部分、即时编译器编译之后的代码等数据。
堆:被所有Java线程共享的区域,存放Java对象实例和数组,是垃圾回收的主要区域。
运行时的常量池:方法区的一部分,用于存放编译器生成的各种字面量和符号引用,将在类加载后放到常量池中。
面试题:String str = new String("abc")内存如何分配?答:str是栈内变量,它的内存保存的是堆中的new String对象的地址,new String在堆中生成对象,并用常量池的字符串对象"abc"初始化堆中的对象。
面试题:虚拟机栈和本地方法栈的区别是什么?答:二者发挥的作用非常相似,区别是虚拟机栈为虚拟机执行Java方法服务,而本地方法栈则为执行虚拟机使用到的Native方法服务,与虚拟机栈一样,本地方法栈也会抛出StackOverFlow和OutOfMemoryError异常。
面试题:说下String.intern()?答:String.inern()是一个Native方法,它的作用是:如果字符串常量池中已经包含一个等于此Strig对象的字符串,则返回代表池中这个字符串的String对象,否则,将此String对象包含的字符串添加到常量池中,并返回此String对象的引用。
2)垃圾收集算法
如何判断对象是否存活?采用可达性分析来判断对象是否存活的。基本思路是:通过一系列的GC Roots作为起始点,向下搜索,看是否可以到达其他对象。不可到达就是可以回收的对象。如下图所示:其中,虽然Objects3和Obejects4是相连的,但是他们与GC Roots并没有相连,所以是可以回收的对象。
面试题:说下强引用、软引用、弱引用和幻象引用有什么区别?答:不同的引用类型,主要体现的是对象不同的可达性状态和对垃圾收集的影响。强引用,new出来的对象都是强引用,只要有强引用指向一个对象,证明该对象还活着,除非将其显示地赋值为null,就可以被垃圾收集了;软引用:比强引用弱一些的引用,当JVM认为内存不足时,就会试图去回收软引用指向的对象。JVM会确保在抛出OutOfMemoryError之前,清理软引用指向的对象。弱引用:强度比软引用更弱一些,他只能生存到下一次垃圾回收之前。当垃圾收集器工作时,无论当前内存是否充足,都会回收只被弱引用关联的对象。幻象引用:也叫虚引用,无法通过他获得对象实例,设置他的唯一目的是在这个对象被收集器回收时收到一个系统通知,有人利用幻象引用的对象进行监控对象的创建和销毁。
垃圾收集算法:有标记-清除算法、复制算法、标记-整理算法和分代收集算法。标记-清除算法:顾名思义,就是先标记再清除,把要回收的进行标记然后统一清除。缺点:效率低和产生大量不连续的空间碎片。复制算法:把内存分为大小相等的两块,每次只用一块,这块用完了就把存活的对象复制到另一块上,缺点是内存缩小了原来的一半。现代的商业虚拟机都采用这种算法进行垃圾回收,但是不是分为大小相等的两块,而是分为一块较大的Eden和两块较小的Survivor区域,每次使用一块Eden和Survivor区域,把存活的复制到另外一块Survivor区域,然后清理刚才使用的Eden和Survivor。标记-整理算法:标记要回收的对象,让存活的对象向一端移动,然后清理掉端外的对象。分代收集算法:当代虚拟机都采用这种垃圾收集算法,新生代采用复制算法,老年代采用标记-清理或者标记-整理算法。
3)垃圾收集器
Serial收集器:虚拟机在Client模式下默认的垃圾收集器,是单线程收集器,在他进行垃圾回收的时候必须暂停其他所有的工作线程,直到其收集完毕。
ParNew收集器:是Serial收集器的多线程版本,是许多运行在Server模式下的虚拟机中首选的新生代收集器,只有他能与CMS配合工作。
Parallel Scavenge收集器:是一个新生代收集器,是并行的多线程收集器,他的主要目的是达到一个可控制的吞吐量(吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间))。
Serial Old收集器:是Serial收集器老年代的并发版本,同样是一个单线程收集器,使用标记-整理算法。
Parallel Old收集器:是Parallel Scavenge收集器的老年代版本,使用多线程和标记-整理算法。
CMS收集器:Concurrent mark sweep收集器,使用标记-清除算法,关注点事停顿时间短,主要包含四个步骤:
1. 初始标记:标记一下GC Roots能直接关联到的对象,速度快,需要“Stop the world”;
2. 并发标记:进行GC Roots Tracing的过程,不停顿;
3. 重新标记:为了修正并发标记的时候,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,需要停顿;
4. 并发清除:不需要停顿。
缺点:对CPU资源非常敏感,无法处理浮动垃圾,有大量的空间碎片产生,容易引发FullGC。
G1收集器:兼顾了吞吐量和停顿时间的收集器,是面向服务端应用的垃圾收集器,是JDK9以后默认的GC选项,在JKD9中,CMS被标记为废弃的(deprecated)。大致分为以下四个步骤:
1. 初始标记:标记一下GC Roots能直接关联到的对象,速度快,需要停顿;
2. 并发标记:从GC Root开始对堆中对象进行可达性分析,找出存活的对象,这步骤耗时长,但可与用户程序并发执行;
3. 最终标记:为了修正在并发标记的时候,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,虚拟机将这段时间对象变化记录在线程的 Remembered Set Logs 里面,最终标记阶段需要把 Remembered Set Logs 的数据合并到 Remembered Set 中。需要停顿,但可并行执行;
4. 筛选回收:首先对各个 Region 中的回收价值和成本进行排序,根据用户所期望的 GC 停顿时间来制定回收计划。此阶段其实也可以做到与用户程序一起并发执行,但是因为只回收一部分 Region,时间是用户可控制的,而且停顿用户线程将大幅度提高收集效率。
注:主要参考《深入理解Java虚拟机_JVM高级特性与最佳实战》周志明