一、java是解释型语言还是编译型语言

通常我们把java分为编译期和运行时。

编译期:.java文件经过Javac编译成.class文件,即字节码文件,这个不是在jvm中进行的。JDK9中的AOT(Ahead-of-Time Compilation)在编译期直接将字节码编译成机器码,避免了JIT预热等各方面的开销。

运行时:jvm通过类加载器加载字节码,解释或者编译执行。解释是指jvm内嵌的解释器对字节码进行逐行解释执行,这种方式的执行速度相对比较慢,每次调用都得对字节码重新进行逐行解释执行。编译是指JIT(Just-In-Time)即时编译器将热点代码编译成本地机器码,缓存到codecache里,这样运行时再遇到这类代码时直接可以执行,而不是先解释再执行。

二、java语言特性:面向对象、反射、泛型。

1、面向对象:

面向对象和面向过程的思想有本质的区别。

(1)、当你拿到一个问题的时候,想着第一步干嘛,然后第二步干嘛,这是面向过程的思维;

(2)、拿到问题,分析这个问题中有哪些类和对象,这是第一点;然后再分析这些类和对象中应该有哪些属性和方法,这是第二点;最后分析类与类之间具体有什么关系,这是第三点。

(3)、面向对象有一个非常重要的设计思维:合适的方法出现在合适的类里面。

2、对象和类的概念:

类是一类事物的抽象。类中的属性和方法分别对应事物的静态属性和动态属性。

对象是这个类具体的实例,一般通过属性值来区分不同的对象。

3、反射:

java反射机制是在运行时获取类的所有属性和方法。.class文件在运行时被jvm读到jvm内存中,类加载器调用defineClass方法进行加载得到class对象,Class类中提供了方法来获取该Class类的所有属性和方法(包括Method、Field、Constructor等)。由于泛型检查在编译期进行擦除,所以可以通过反射来越过泛型检查。

4、泛型:

泛型的本质是为了参数化类型,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法上,分别被称为泛型类、泛型接口和泛型方法。

三、java虚拟机:垃圾收集器、分代策略

1、jvm内存结构:

线程独享:程序计数器、虚拟机栈、本地方法栈

线程共享:堆、方法区、运行时常量池

程序计数器:每个线程独有,互不影响。jvm字节码解释器通过改变计数器的值来选取线程的下一条执行指令。

虚拟机栈:保存方法的栈帧。每个方法从调用到执行结束,对应其栈帧在JVM栈上的入栈到出栈的过程。栈帧用于存储局部变量、操作数栈、动态连接、方法出口等信息。

本地方法栈:为Native方法服务(指使用除java以外其他的语言)

堆:生命周期与JVM相同。从垃圾收集器的角度来看,可以细分为:新生代、老年代和永久代。从内存分配的角度来看,可以划分出每个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB),减少线程同步。HotSpot VM通过"-XX:+UserTLAB"指定是否启动TLAB。

方法区:生命周期与JVM相同。为类加载器加载Class文件并解析后的类结构信息提供存储空间;以及提供JVM运行时常量存储的空间。JDK1.8中永久代被删除,类元数据存储空间在本地内存中分配,元数据使用由mmap分配的空间,而不是由malloc分配的空间;通过"-XX:MetaspaceSize"制定元数据的内存阈值-超过将触发垃圾回收。

运行时常量池:方法区的一部分。主要包括几类常量:编译期可知的字面量和符号引用,即Class文件结构中的常量池;运行时解析后获得的方法或字段的直接引用;运行时创建的新变量(String.intern()方法)

直接内存:使用Native函数直接分配队外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。可以通过"-XX:MaxDirectMemorySize"指定直接内存的最大空间,不会受到Java堆大小的限制。

2、垃圾回收:who—GC Roots对象,when—可达性分析,how—垃圾回收算法,where—安全点(safepoint)

GC Roots对象:虚拟机栈中引用的对象、方法区类静态变量引用的对象、方法区常量引用的对象、本地方法栈中JNI(Native方法)引用的对象。

可达性分析算法:从通过一系列GC Roots对象作为起始点,开始往下搜索,当一个对象到GC Roots没有任何引用链时(从GC Roots到这个对象不可达),则证明该对象不可用。

OopMap:从外部记录下类型信息,存成映射表。要实现这种功能,需要JVM里的解释器和JIT编译器来生成足够的元数据提供给GC。

safepoint:循环的末尾、方法返回前、调用方法的call指令后、可能抛异常的位置。被JIT编译后的方法在这些特定的位置记录下OopMap,记录了执行到该方法的某条指令的时候,栈上和寄存器里哪些位置是引用。这样GC在扫描栈的时候就会查询这些OopMap就知道哪些是引用了。而在解释器中执行的方法则可以通过解释器里的功能自动生成OopMap出来供GC用。平时这些OopMap都是压缩了存在内存里的;在GC的时候才需要解压出来使用。

safeRegion:线程执行进入safe region,在该区域任意地方开始GC都是安全的,在被唤醒离开safe region时,需要检查系统是否已经完成根节点的枚举(或整个GC)。如果已经完成,就继续执行,否则必须继续等待,直到收到可以安全离开safe region的信号通知。

3、垃圾回收算法:标记—清除算法、复制算法、标记—整理算法、分代收集算法、火车算法

标记—清除算法:经过标记(可能两次标记),标记出所有需要被回收的对象,然后统一回收。优点:最基础,最简单。缺点:标记和清除过程效率都不高,标记清除后产生大量不连续的内存碎片,分配大内存对象时,无法找到足够的连续内存,提前触发下一次垃圾收集操作。

复制算法:把内存分成大小相等的两块,一块内存用完了,将存活对象复制到另一块内存中,清理掉已经使用的内存。优点:每次只需要对半个分区回收,不用考虑内存碎片的问题(可使用指针碰撞的方式分配内存),实现简单,运行高效。缺点:空间浪费,当对象存活率较高时,需要进行较多复制操作,效率将变低。

HotSpot VM新生代内存布局及算法:默认Eden:From:to=8:1:1,即每次可以使用90%的空间,只有一块Survivor的空间被浪费。如果另一块Survivor空间没有足够空间存放上一次新生代收集下来的存活对象,这些对象将直接通过分配担保机制进入老年代。 基于弱代理论:大多数对象在出于年轻代时就死掉,很少有对象从年老代变成年轻代。

标记—整理算法:在清理之前将所有存活的对象都移向一端,然后直接清理掉端边界以外的内存。根据老年代的特点提出的算法:对象存活率高,没有额外的空间可以分配担保,老年代一般不能直接选用复制算法。优点:不会产生内存碎片。缺点:多了整理过程,效率更低。

分代收集算法:结合不同的算法处理不同的区域。新生代:复制算法。老年代:标记—清除或者标记—整理算法。优点:可以根据各个年代的特点采用最适当的收集算法。缺点:仍然不能控制每次垃圾收集时间。

火车算法:

4、垃圾收集器:新生代收集器、老年代收集器、并行收集器、并发收集器

新生代收集器:Serial、ParNew
老年代收集器:Serial Old、CMS

并行收集器:多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。

并发收集器:用户线程与垃圾收集线程同时执行(不一定并行,可能交替执行)(CMS、G1)

Minor GC:新生代GC,指发生在新生代的垃圾收集工作,由于java对象大多朝生夕死,所以Minor GC非常频繁,一般回收速度也比较快。触发条件:Eden区满时触发。

Full GC:老年代GC,出现Full GC经常会伴随至少一次的Minor GC,Full GC一般比Minor GC慢10倍。触发条件:System.gc()、老年代空间不足、方法区不足、通过Minor GC后进入老年代的对象平均大小大于老年代的可用内存。

CMS收集器:针对老年代和方法区,基于标记—清除算法,以获取最短回收停顿时间为目标,并发收集,低停顿。第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。

CMS处理过程:

1、初始标记(CMS-initial-mark),会导致stw,标记老年代中存活的对象和年轻代中活着的对象引用到老年代的对象。可以通过开启初始标记并行化,-XX:+CMSParallelInitialMarkEnabled,减少停顿时间。

2、并发标记(CMS-concurrent-mark),与用户线程同时运行。从“初始标记”阶段标记的对象开始找出所有存活的对象。因为是并发运行的,在运行期间会有新生代的对象晋升到老年代、或者是直接在老年代上分配对象、或者更新老年代对象的引用关系等等,对于这些对象,都是需要进行重新标记的。为了提高重新标记的效率,该阶段会把上述对象所在的Card标识为Dirty,后续只需扫描这些Dirty Card的对象,避免扫描整个老年代。这个阶段因为是并发的容易导致concurrent mode failure。

3、预清理(CMS-concurrent-preclean),与用户线程同时运行。这个阶段是用来处理前一个阶段即并发标记阶段因为引用关系改变导致没有标记到的存活对象的,它会扫描所有标记为Dirty的Card,如果Dirty区的对象引用了新的对象,则将引用的对象标记为存活。

4、可被终止的预清理(CMS-concurrent-abortable-preclean),与用户线程同时运行。此阶段最大持续时间为5秒,期待这5秒内能够发生一次minor GC,清理年轻代的作用是为了下个阶段的重新标记阶段,扫描年轻代指向老年代的引用时间减少。

5、重新标记(CMS-final-remark),会导致stw。重新标记的内存范围是整个堆,包含新生代和老年代。可以加入参数 -XX:+CMSScavengeBeforeRemark,在重新标记之前,先执行一次Minor GC,回收掉年轻代中的无用对象,并将对象放入Survivor区或晋升到老年代,这样再进行年轻代扫描时,只需要扫描Survivor区的对象即可,一般Survivor区非常小,大大减少了扫描时间。

6、并发清除(CMS-concurrent-sweep),与用户线程同时运行。清除那些没有标记的对象并且回收空间。这个阶段用户线程还在运行,产生的垃圾称为“浮动垃圾”。

7、并发重置状态等待下次CMS的触发(CMS-concurren-reset),与用户线程同时运行。重新设置CMS算法内部的数据结构,准备下一个CMS生命周期的使用。

后续待补充。。。