1.1 支持面向对象编程oop
强调支持,因为java同样可以面向过程编程。
oop的三大特性是:封装、继承、多态。
封装主要针对成员变量而言,oop的思想要求成员变量均为私有,不应该对外能够访问,一个符合oop思想的类应该只有公共方法对外能够访问;
继承,主要理解继承体系,private、protected、public在继承中的使用场景。理解java是单继承多实现的(与C++的区别);
多态主要指一个类的实例是运行时决定的,而不是声明时决定的。父类 a = new 子类();是可以的。这种作用在于可以面向抽象编程、面向接口编程,对象不必必须和声明的类一致,只要是它的子类、孙子类等即可;
1.2 jdk版本对java语言的改进
1996年发布jdk1.0,java语言具备基础的oop语法;
1997年发布jdk1.1,引入内部类;
2004年发布jdk1.5,引入语法糖,自动拆装箱、泛型、动态注解、枚举、可变长参数、遍历循环(foreach);
2014年发布jdk8,引入Lambda表达式;
二、jvm虚拟机(本文特指官方默认的HotSpot虚拟机)
2.1 发展
由一家小公司Longview Technologies开发出来,1997年被sun公司收购,jdk1.3之后正式成为官方默认虚拟机;
HotSpot得名来自于其热点代码探测技术,可以有效的把热点代码探测出来,并利用JIT编译器将热点代码进一步优化并编译成机器代码,提高运行效率;
2.2 jvm虚拟机的内存区域组成
程序计数器,线程私有,指向字节码指令;
java虚拟机栈,线程私有,主要就是描述java方法的,配合程序计数器一起一步一步往下执行方法(理解为什么是栈);
本地方法栈,线程私有,跟java虚拟机栈类似,区别是它用来执行非java方法;
java堆,线程共享,这是最大的一块虚拟机内存区域,主要就是我们new的对象都会分配在这里,这里分为新生代(Eden、Survivor1、Survivor2)和老年代;
方法区,线程共享,在HotSpot里叫永久代(Permanent Generation),存放加载的类信息、常量、静态变量等,static代码块、static变量、static方法都会存放在这里有一个副本。为什么叫永久代,主要是对这部分的对象实例回收的效率不高,这部分对象实例存活率较高;
运行时常量池,是方法区的一部分;
直接内存,不是虚拟机内存的一部分,指申请虚拟机内存外的内存。
2.3 垃圾收集算法
怎么样判断对象可以回收?有引用计数算法和可达性分析算法。引用计数算法很简单,给每个对象一个引用计数器,每当有一个地方引用了它,那么就给它计数器+1,当这个引用失效之后计数器-1,这样做非常高效,但有一个缺陷是互相引用的对象,无法被回收,造成内存泄露。HotSpot使用可达性分析算法,可达性分析算法从GC Roots对象是否可达来判断对象是否可以回收,GC Roots对象包括虚拟机栈引用的对象、方法区类静态属性引用的对象、方法区常量引用的对象、本地方法栈中引用的对象;
IBM研究指出98%的对象都是朝生夕死,故新生代中回收频率要较高,每次可以回收大量内存,老年代中经过两次以上的回收仍存活,说明回收的效率不高,回收频率可以低一点。另外,大对象不在新生代中分配,而是直接进入老年代。
①标记-清除算法。算法的思想是首先把需要回收的对象标注出来,然后统一清除回收。实现起来很简单,但标记和清除的效率不高,还会产生大量不连续的内存空间,影响后续为新对象分配内存,尤其是大对象。
②复制算法。针对标记-清除算法的问题,复制算法的思想是把内存区域均等的分成两块,比如10M的内存均等分为两块5M,每个时刻只能使用一块,先从第一块标记仍存活对象的对象,然后统一复制到第二块内存中,并按内存空间顺序排好,第一块内存则全部回收。第二次回收时就先从第二块开始,循环往复。这样做效率很高,并且内存空间可以连续分配。但造成一个问题是本来10M的内存只能用一半,造成内存的浪费。
HotSpot在实际实现复制算法时,将内存空间划分为Eden和两个Survivor,且默认Eden和Survivor的比例是8:1:1,新对象分配在Eden中,第一次回收后存活对象被复制到Survivor 1中,Eden全部清除,第二次新对象仍分配在Eden中,第二次回收时Eden和Survivor 1中存活对象被复制到Survivor 2中,Eden和Survivor 1全部清除。
这里有一个问题:如果存活对象超过内存的10%怎么办?这时候就需要从老年代中进行分配担保(Handle Promotion)。