JAVA

面向对象编程

面向对象主要有四大特性:抽象、封装、继承和多态
OO开发范式大致为:划分对象→抽象类→将类组织成为层次化结构(继承和合成) →用类与实例进行设计和实现几个阶段。

https://juejin.im/entry/59f292635188254115701364
https://blog.csdn.net/cancan8538/article/details/8057095

  1. 抽象就是将一类实体的共同特性抽象出来,封装在一个新的概念(类) 中,所以抽象是面向对象语言的基础。
  2. 封装特性是由类来体现的。将现实生活中的一类实体定义成类,其中包括属性和行为(在Java中就是方法)。
  3. 一个类可以继承另一个类的一些特性,从而可以代码重用,其实继承体现的是is-a关系,父类和子类在本质上还是一类实体。继承是一个对象获得另一个对象的属性的过程,使一个对象成为一个更具通用类的一个特定实例(可传递可扩展、可复用、可维护)通过继承创建的新类称为“子类”或“派生类”;被继承的类称为“基类”、“父类”或“超类”。
  4. 多态是允许一个接口被多个同类动作使用的特性,具体使用哪个动作与应用场合有关。多态就是通过传递给父类对象引用不同的子类对象从而表现出不同的行为,多态可为程序提供更好的可扩展性,同样也可以代码重用。
    “向上转型”: Animal a = new Dog(); 定义了一个Animal类型的引用,指向新建的Dog类型的对象。父类引用a可以直接调用Animal中未被dog重写(override)的方法,调用被重写的方法时会调用子类中的这个方法,这就是动态连接。这样的函数调用在编译期间是无法确定的(调用的子类的虚函数的地址无法给出)。当需要在子类中调用父类的被重写方法时,要使用 super 关键字。

注意:重载(overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。编译器根据函数不同的参数表,对同名函数的名称做修饰,然后这些同名函数就成了不同的函数(对于编译器来说)。对于这两个函数的调用,在编译器间就已经确定了,是静态的(记住:是静态)。也就是说,它们的地址在编译期就绑定了(早绑定),因此,重载只是一种语言特性,与多态无关,与面向对象也无关。无法以返回值类型作为重载函数的区分标准。

接口和抽象类区别

https://www.cnblogs.com/dolphin0520/p/3811437.html
https://www.jianshu.com/p/038f0b356e9a

抽象方法是一种特殊的方法:它只有声明,而没有具体的实现:abstract void fun();因为抽象类中含有无具体实现的方法,所以不能用抽象类创建对象。包含抽象方法的类称为抽象类,抽象方法必须为public或者protected(因为如果为private,则不能被子类继承,子类便无法实现该方法),缺省情况下默认为public;抽象类不能用来创建对象;如果一个类继承于一个抽象类,则子类必须实现父类的抽象方法。如果子类没有实现父类的抽象方法,则必须将子类也定义为为abstract类。
接口泛指供别人调用的方法或者函数。接口中的变量会被隐式地指定为public static final变量,方法会被隐式地指定为public abstract方法,并且接口中所有的方法不能有具体的实现,也就是说,接口中的方法必须都是抽象方法。可以看出,接口是一种极度抽象的类型。
允许一个类实现(implements)多个特定的接口。
如果一个非抽象类实现(implements)某个接口,就必须实现该接口中的所有方法。
对于实现(implements)某个接口的抽象类,可以不实现该接口中的抽象方法。

  1. 抽象类可以有默认的方法实现完全是抽象的。接口根本不存在方法的实现。
  2. 抽象类使用extends关键字来继承抽象类。如果子类不是抽象类的话,它需要提供抽象类中所有声明的方法的实现。子类使用关键字implements来实现接口。它需要提供接口中所有声明的方法的实现。
  3. 抽象类可以有构造器,而接口不能有构造器。
  4. 抽象方法可以有public、protected和default这些修饰符 ;接口方法默认修饰符是public,不可以使用其它修饰符。
  5. 抽象类在java语言中所表示的是一种继承关系,一个子类只能存在一个父类,但是可以存在多个接口。
  6. 抽象方法比接口速度要快,接口是稍微有点慢的,因为它需要时间去寻找在类中实现的方法。
  7. 如果你往抽象类中添加新的方法,你可以给它提供默认的实现。因此你不需要改变你现在的代码。如果你往接口中添加方法,那么你必须改变实现该接口的类。

类的加载机制

类加载过程

加载,连接,初始化

有哪些类加载器,能否自定义 Java.Object.String 的类加载器 ?

双亲委派机制介绍 & 作用

类加载器加载类时先委派给父类加载,只有父类无法加载时,自己才尝试加载。
保证java类库中的类不受用户类影响,防止用户自定义一个类库中的同名类,引起问题。

集合

Java标准库自带的java.util包提供了集合类:Collection,它是除Map外所有其他集合类的根接口。
Java的java.util包主要提供了以下三种类型的集合:
List(有序可重复): LinkedList, ArrayList, Vector(——Stack)
Set(无序不可重复): HashSet(——LinkedHashSet), SortedSet(——TreeSet)
Map:一种通过键值(key-value)查找的映射表集合

  • 实现了接口和实现类相分离,例如,有序表的接口是List,具体的实现类有ArrayList,LinkedList等
  • 支持泛型,可以限制在一个集合中只能放入同一种数据类型的元素
  • List内部并不是通过==判断两个元素是否相等,而是使用equals()方法判断两个元素是否相等

Java集合框架

https://www.runoob.com/java/java-collections.html

集合框架是一个用来代表和操纵集合的统一架构。所有的集合框架都包含如下内容:
接口:是代表集合的抽象数据类型。例如 Collection、List、Set、Map 等。之所以定义多个接口,是为了以不同的方式操作集合对象
实现(类):是集合接口的具体实现。从本质上讲,它们是可重复使用的数据结构,例如:ArrayList、LinkedList、HashSet、HashMap。
算法:是实现集合接口的对象里的方法执行的一些有用的计算,例如:搜索和排序。这些算法被称为多态,那是因为相同的方法可以在相似的接口上有着不同的实现。

ArrayList

ArrayList是一个动态数组,它是线程不安全的,允许元素为null。O(1)时间随机快速访问。增删效率低,但是改查效率高.
ArrayList自己实现了序列化和反序列化的方法
ArrayList基于数组方式实现,无容量的限制(会扩容)
添加元素时可能要扩容(所以最好预判一下),删除元素时不会减少容量(若希望减少容量,trimToSize()),删除元素时,将删除掉的位置元素置为null,下次gc就会回收这些元素所占的内存空间
线程不安全
add(int index, E element):添加元素到数组中指定位置的时候,需要将该位置及其后边所有的元素都整块向后复制一位
get(int index):获取指定位置上的元素时,可以通过索引直接获取(O(1))
remove(Object o)需要遍历数组
remove(int index)不需要遍历数组,只需判断index是否符合条件即可,效率比remove(Object o)高
如果list中全是int,remove(2)代表删除index=2,而remove(Integer.valueOf(2))代表删除2这个元素
contains(E)需要遍历数组并调用indexOf()这个函数(如果包含则返回第一个index,否则-1)
使用iterator遍历可能会引发多线程异常

https://blog.csdn.net/zxt0601/article/details/77281231

LinkedList

LinkedList增删由于不需要移动底层数组数据,其底层是链表实现的,只需要修改链表节点指针,所以效率较高。而改和查,都需要先定位到目标节点,所以效率较低。LinkedList是线程不安全的,允许元素为null的双向链表。时间效率较高。不需要批量扩容,也不需要预留空间,所以空间效率比ArrayList高。缺点就是需要随机访问元素时,时间效率很低,虽然底层在根据下标查询Node的时候,会根据index判断目标Node在前半段还是后半段,然后决定是顺序还是逆序查询,以提升时间效率。不过随着n的增大,总体时间效率依然很低。
链表批量增加,是靠for循环遍历原数组,依次执行插入节点操作。对比ArrayList是通过System.arraycopy完成批量增加的。增加一定会修改modCount。
通过下标获取某个node 的时候,(add select),会根据index处于前半段还是后半段 进行一个折半,以提升查询效率
删也一定会修改modCount。 按下标删,也是先根据index找到Node,然后去链表上unlink掉这个Node。 按元素删,会先去遍历链表寻找是否有该Node,如果有,去链表上unlink掉这个Node。
改也是先根据index找到Node,然后替换值。改不修改modCount。
查本身就是根据index找到Node。
所以它的CRUD操作里,都涉及到根据index去找到Node的操作。

https://blog.csdn.net/zxt0601/article/details/77341098

Vector

Vector与ArrayLIst类似, 内部同样维护一个数组。
Stack:基于Vector实现的LIFO的栈。

1
2
3
4
5
6
7
8
public class Vector<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
//构造方法,提供初始大小,和增长系数
public Vector(int initialCapacity, int capacityIncrement){
...
}
}

每个方法中都添加了synchronized的关键字来保证同步,所以它是线程安全的,但正是这些方法的同步,让它的效率大大的降低了,比ArrayList的效率要慢。

https://www.jianshu.com/p/f50307de72b1

快速失败 (fail-fast) VS 安全失败 (fail-safe)

快速失败(fail—fast)
在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加、删除、修改),则会抛出Concurrent Modification Exception。
原理:迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历。
如果集合发生变化时修改modCount值刚好又设置为了expectedmodCount值,则异常不会抛出。因此,不能依赖于这个异常是否抛出而进行并发操作的编程,这个异常只建议用于检测并发修改的bug。
java.util包下的集合类都是快速失败的,不能在多线程下发生并发修改(迭代过程中被修改)。

安全失败(fail—safe)
采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。
原理:由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发Concurrent Modification Exception。
缺点:基于拷贝内容的优点是避免了Concurrent Modification Exception,但同样地,迭代器并不能访问到修改后的内容,即:迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的。
场景:java.util.concurrent包下的容器都是安全失败,可以在多线程下并发使用,并发修改。

快速失败和安全失败是对迭代器而言的: 快速失败:当在迭代一个集合的时候,如果有另外一个线程在修改这个集合,就会抛出ConcurrentModification异常,java.util下都是快速失败。 安全失败:在迭代时候会在集合二层做一个拷贝,所以在修改集合上层元素不会影响下层。在java.util.concurrent下都是安全失败

https://blog.csdn.net/qq_31780525/article/details/77431970

other

List、Map、Set 三个接口,存取元素时,各有什么特点?
Set 里的元素是不能重复的,那么用什么方法来区分重复与否呢? 是用 == 还是 equals()? 它们有何区别?
两个对象值相同 (x.equals(y) == true),但却可有不同的 hash code,这句话对不对?
heap 和 stack 有什么区别。
Java 集合类框架的基本接口有哪些?
HashSet 和 TreeSet 有什么区别?
HashSet 的底层实现是什么?
为什么集合类没有实现 Cloneable 和 Serializable 接口?
什么是迭代器 (Iterator)?
Iterator 和 ListIterator 的区别是什么?
数组 (Array) 和列表 (ArrayList) 有什么区别?什么时候应该使用 Array 而不是 ArrayList?
Java 集合类框架的最佳实践有哪些?
Set 里的元素是不能重复的,那么用什么方法来区分重复与否呢?是用 == 还是 equals()?它们有何区别?
Comparable 和 Comparator 接口是干什么的?列出它们的区别
Collection 和 Collections 的区别。

JVM内存结构

JVM内存结构主要有三大块:堆内存、方法区和栈。

方法区存储类信息、常量、静态变量等数据,是线程共享的区域,为与Java堆区分,方法区还有一个别名Non-Heap(非堆);栈又分为java虚拟机栈和本地方法栈主要用于方法的执行。

方法区和对是所有线程共享的内存区域;而java栈、本地方法栈和程序员计数器是运行是线程私有的内存区域。

https://mp.weixin.qq.com/s?__biz=MzI4NDY5Mjc1Mg==&mid=2247483949&idx=1&sn=8b69d833bbc805e63d5b2fa7c73655f5&chksm=ebf6da52dc815344add64af6fb78fee439c8c27b539b3c0e87d8f6861c8422144d516ae0a837&scene=178&cur_album_id=1326602114365276164#rd

Java堆(GC堆)

数据区所在所有线程共享,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。
堆内存是JVM中最大的一块由年轻代和老年代组成,而年轻代内存又被分成三部分,Eden空间、From Survivor空间、To Survivor空间,默认情况下年轻代按照8:1:1的比例来分配。必须逻辑上连续,可以物理上不连续。如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。

方法区(Method Area,non-heap,持久代(Permanent Generation))

各个线程共享的内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。逻辑上也是一个堆。当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。

程序计数器(Program Counter Register)

数据所在线程私有,当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。
此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。

局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它不等同于对象本身,根据不同的虚拟机实现,它可能是一个指向对象起始地址的引用指针,也可能指向一个代表对象的句柄或者其他与此对象相关的位置)和returnAddress类型(指向了一条字节码指令的地址)。
局部变量表作用小结:存储局部变量、函数调用时传递参数,很多字节码指令都是对局部变量表和操作数栈进行操作的。

局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小

虚拟机栈(Java Virtual Machine Stacks)

线程私有,生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
StackOverflowError异常:线程请求的栈深度大于虚拟机所允许的深度
OutOfMemoryError:可以动态扩展的虚拟机栈在扩展时无法申请到足够的内存
(当前大部分的Java虚拟机都可动态扩展,只不过Java虚拟机规范中也允许固定长度的虚拟机栈)

本地方法栈(Native Method Stacks)

虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务
一个Native Method就是一个java调用非java代码的接口。一个Native Method是这样一个java的方法:该方法的实现由非java语言实现,比如C。这个特征并非java所特有,很多其它的编程语言都有这一机制,比如在C++中,你可以用extern “C”告知C++编译器去调用一个C的函数。
“A native method is a Java method whose implementation is provided by non-java code.”
具体的虚拟机可以自由实现它。甚至有的虚拟机(譬如Sun HotSpot虚拟机)直接就把本地方法栈和虚拟机栈合二为一。与虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError和OutOfMemoryError异常。

垃圾回收GC

为新生成的对象分配内存,识别那些垃圾对象,并且从垃圾对象那回收内存

https://mp.weixin.qq.com/s?__biz=MzI4NDY5Mjc1Mg==&mid=2247483952&idx=1&sn=ea12792a9b7c67baddfaf425d8272d33&chksm=ebf6da4fdc815359869107a4acd15538b3596ba006b4005b216688b69372650dbd18c0184643&scene=178&cur_album_id=1326602114365276164#rd

判断对象死亡的方法

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

什么可以作为GCRoot?

方法区的数据引用、当前代码处的局部变量,基本就是用户能通过代码引用到的。

  • 虚拟机栈中引用的对象。
  • 方法区中类静态属性实体引用的对象。
  • 方法区中常量引用的对象。
  • 本地方法栈中JNI引用的对象。

Minor GC 和 FULL GC 触发条件 :

Minor GC 平均晋升空间大小 > 老年代连续剩余空间,则触发FULL GC
1、新建的对象,大部分存储在Eden中
2、当Eden内存不够,就进行Minor GC释放掉不活跃对象;然后将部分活跃对象复制到Survivor中(如Survivor1),同时清空Eden区
3、当Eden区再次满了,将Survivor1中不能清空的对象存放到另一个Survivor中(如Survivor2),同时将Eden区中的不能清空的对象,复制到Survivor1,同时清空Eden区
4、重复多次(默认15次):Survivor中没有被清理的对象就会复制到老年区(Old)
5、当Old达到一定比例,则会触发Major GC释放老年代
6、当Old区满了,则触发一个一次完整的垃圾回收(Full GC)
7、如果内存还是不够,JVM会抛出内存不足,发生oom(OutOfMemoryError),内存泄漏。

垃圾收集算法

标记-清除(Mark-Sweep)算法

首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。
优点:
缺点:1. 效率问题,标记和清除过程的效率都不高;2. 标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致,当程序在以后的运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。

复制(Copying)算法

将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。
这样使得每次都是对其中的一块进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。
缺点:将内存缩小为原来的一半,对象存活率较高时就要执行较多的复制操作,效率将会变低。如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法。

标记-压缩(Mark-Compact)算法

标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。

分代收集算法

GC分代的基本假设:绝大部分对象的生命周期都非常短暂,存活时间短。
在新生代中,每次垃圾收集时都发现有大批对象死去,少量存活,那就选用复制算法,只需要付出存活对象的复制成本就可以完成收集。
而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记-清理”或“标记-压缩”算法来进行回收。

垃圾收集器

stop the world(STW):应用程序暂停

Serial收集器

串行收集器是最古老,最稳定以及效率高的收集器,可能会产生较长的停顿,只使用一个线程去回收。新生代、老年代使用串行回收;新生代复制算法、老年代标记-压缩;垃圾收集的过程中会Stop The World(服务暂停)
参数控制: -XX:+UseSerialGC 串行收集器
ParNew收集器是Serial收集器的多线程版本。新生代并行,老年代串行;新生代复制算法、老年代标记-压缩
参数控制:
-XX:+UseParNewGC ParNew收集器
-XX:ParallelGCThreads 限制线程数量

Parallel收集器

Parallel Scavenge收集器类似ParNew收集器,Parallel收集器更关注系统的吞吐量。可以通过参数来打开自适应调节策略,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或最大的吞吐量;也可以通过参数控制GC的时间不大于多少毫秒或者比例;新生代复制算法、老年代标记-压缩
参数控制: -XX:+UseParallelGC 使用Parallel收集器+ 老年代串行

CMS收集器

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的Java应用都集中在互联网站或B/S系统的服务端上,这类应用尤其重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验。基于标记-清除算法。
初始标记(CMS initial mark)
并发标记(CMS concurrent mark)
重新标记(CMS remark)
并发清除(CMS concurrent sweep)
其中初始标记、重新标记这两个步骤仍然需要“Stop The World”。初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快,并发标记阶段就是进行GC Roots Tracing的过程,而重新标记阶段则是为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短。

优点: 并发收集、低停顿
缺点:
GC过程中会出现STW(Stop-The-World),若Old区对象太多,STW耗费大量时间。
CMS收集器对CPU资源很敏感。
CMS收集器无法处理浮动垃圾,可能出现“Concurrent Mode Failure”失败而导致另一次Full GC的产生。
CMS导致内存碎片问题。

参数控制:
-XX:+UseConcMarkSweepGC 使用CMS收集器
-XX:+ UseCMSCompactAtFullCollection Full GC后,进行一次碎片整理;整理过程是独占的,会引起停顿时间变长
-XX:+CMSFullGCsBeforeCompaction 设置进行几次Full GC后,进行一次碎片整理
-XX:ParallelCMSThreads 设定CMS的线程数量(一般情况约等于可用CPU数量)

G1收集器

使用G1收集器时,Java堆的内存布局与其他收集器有很大差别,它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔阂了,它们都是一部分(可以不连续)Region的集合。
G1的新生代收集跟ParNew类似,当新生代占用达到一定比例的时候,开始出发收集。和CMS类似,G1收集器收集老年代对象会有短暂停顿。
收集步骤:

  1. 标记阶段,首先初始标记(Initial-Mark),这个阶段是停顿的(Stop the World Event),并且会触发一次普通Mintor GC。对应GC log:GC pause (young) (inital-mark)
  2. Root Region Scanning,程序运行过程中会回收survivor区(存活到老年代),这一过程必须在young GC之前完成。
  3. Concurrent Marking,在整个堆中进行并发标记(和应用程序并发执行),此过程可能被young GC中断。在并发标记阶段,若发现区域对象中的所有对象都是垃圾,那个这个区域会被立即回收(图中打X)。同时,并发标记过程中,会计算每个区域的对象活性(区域中存活对象的比例)。
  4. Remark, 再标记,会有短暂停顿(STW)。再标记阶段是用来收集 并发标记阶段 产生新的垃圾(并发阶段和应用程序一同运行);G1中采用了比CMS更快的初始快照算法:snapshot-at-the-beginning (SATB)。
  5. Copy/Clean up,多线程清除失活对象,会有STW。G1将回收区域的存活对象拷贝到新区域,清除Remember Sets,并发清空回收区域并把它返回到空闲区域链表中。
  6. 复制/清除过程后。回收区域的活性对象已经被集中回收到深蓝色和深绿色区域。
    优点:
    空间整合:G1收集器采用标记整理算法,不会产生内存空间碎片。分配大对象时不会因为无法找到连续空间而提前触发下一次GC。
    可预测停顿,这是G1的另一大优势,降低停顿时间是G1和CMS的共同关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为N毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒,这几乎已经是实时Java(RTSJ)的垃圾收集器的特征了。
    关于Remembered Set概念:G1收集器中,Region之间的对象引用以及其他收集器中的新生代和老年代之间的对象引用是使用Remembered Set来避免扫描全堆。G1中每个Region都有一个与之对应的Remembered Set,虚拟机发现程序对Reference类型数据进行写操作时,会产生一个Write Barrier暂时中断写操作,检查Reference引用的对象是否处于不同的Region之间(在分代中例子中就是检查是否老年代中的对象引用了新生代的对象),如果是便通过CardTable把相关引用信息记录到被引用对象所属的Region的Remembered Set中。当内存回收时,在GC根节点的枚举范围加入Remembered Set即可保证不对全局堆扫描也不会有遗漏。

Java关键字

final

  1. final 用在变量的前面表示变量的值不可以改变,此时该变量可以被称为常量。
    final 修饰的局部变量必须使用之前被赋值一次才能使用。
    final 修饰的成员变量在声明时没有赋值的叫“空白 final 变量”。空白 final 变量必须在构造方法或静态代码块中初始化。
    final 修饰引用变量的时候只需要保证引用地址不变即可,即一直引用同一个对象,对象的内容可以改变。
    static final :全局变量

  2. final 用在方法的前面表示方法不可以被重写(子类中如果创建了一个与父类中相同名称、相同返回值类型、相同参数列表的方法,只是方法体中的实现不同,以实现不同于父类的功能,这种方式被称为方法重写,又称为方法覆盖)。
    final 方法可以被重载(方法名相同,输入输出不完全相同)

  3. final 用在类的前面表示该类不能有子类,即该类不可以被继承,意味着此类不需要扩展。
    当子类继承父类时,将可以访问到父类内部数据,并可通过重写父类方法来改变父类方法的实现细节,这可能导致一些不安全的因素。

Static

即使没有创建对象,也能使用的属性和调用方法

  • 修饰变量:static 数据类型 变量名
  • 修饰方法:【访问权限修饰符】 static 方法返回值 方法名(参数列表)

被static修饰的变量或者方法是独立于该类的任何对象,也就是说,这些变量和方法不属于任何一个实例对象,而是被类的实例对象所共享。
在类被加载的时候,就会去加载被static修饰的部分,所以在静态方法中没有this关键字,因为this是随着对象的创建而存在的,静态比对象优先存在。
被static修饰的变量或者方法是优先于对象存在的,也就是说当一个类加载完毕之后,即便没有创建对象,也可以去访问。强烈建议不要通过对象的方式去访问静态的变量或者方法。
静态可以访问静态的,但是静态不能访问非静态的。
非静态的可以去访问静态的。

Synchronized

Volatile

others

内存泄漏

例子: { 单例 , 容器 等等}
原因 : 长生命周期持有短生命周期引用

引用类型

强引用、 软引用、 弱引用 、 虚引用

如何高效进行数组拷贝(其实是System.arraycopy()的原理

equals 和 == 区别

为啥重写equals要重写hashCode()

如果两个对象相同(equals 方法返回 true),那么它们的hashCode 值一定要相同;
如果两个对象的 hashCode 相同,它们并不一定相同。

String StringBuffer StringBuilder 区别 和各自使用场景

深入一些 : String 是如何实现它不可变的? 为什么要设置String为不可变对象 ?

深拷贝和浅拷贝区别

Object的方法

{ finalize 、 clone、 getClass 、 equals 、 hashCode }

【高频】 设计模式

{ 单例模式 、 工厂模式 、 装饰者模式 、 代理模式 、 策略模式 等等} (此处我的掌握也不是很好)

深入一些

单例模式为什么采用双检测机制 ? 单例为什么用Volatile修饰? 装饰模式和代理模式区别?

B树怎么保证每次操作完都平衡的?

跟红黑树还是差不太多

JOOM 如何定位

说几个虚拟机指令以及虚拟机栈可能会发生什么错误

static变量什么作用,放在哪里

线程池的执行过程、核心参数以及常用的几个线程池

讲一下 volatile 。

内存可见性、指令重排、32位jvm对64数据的原子操作什么的

volatile 可以保证并发计数正确性?

不能

如果需要保证并发计数正确怎么办,只能加锁吗?

计数是个轻量级的操作,java 有原子变量,于是说 AtomicInteger。

为什么原子变量能保证高效正确计数?