GC算法
标记-清除算法,标记-整理算法,复制算法,分代收集算法
垃圾收集器
(1)Serial收集器
只用一个线程回收,新生代和老年代串行,新生代复制算法,老年代标记-整理算法。需要“Stop The World”
(2)ParNew收集器
Serial收集器多线程版本,新生代并行,老年代串行,新生代复制算法,老年代标记-整理算法
(3)Parallel Scavenge收集器
新生代多线程收集器,复制算法
(4)Parallel Old收集器
Parallel收集器的老年代版本,老年代并行,老年代标记-整理算法
(5)CMS收集器
老年代收集器,标记-清除算法
分为四步:初始标记、并发标记、重新标记、并发清除
初始标记和重新标记仍需要“Stop The World”
(6)G1收集器
相比CMS具有两大优点:
空间整合:采用标记-整理算法
可预测停顿:降低停顿时间外,能让使用者在明确指定消耗在垃圾收集上的时间不得超过N毫秒。
取消了新生代和老年代的物理空间划分,将堆划分为若干个区域
什么时候触发Full GC
1、调用System.gc()
2、老年代空间不足
3、在堆中创建大对象
在堆中分配占用大量连续内存的大对象时,会直接进入老年代。此时,即使老年代有足够空间,但是没有足够大的连续空间,也会触发full gc
4、方法区空间不足
方法区中存放一些class的信息、常量、静态变量等数据,当系统中要载入的类,反射的类和调用的方法较多时,方法区可能会被占满。在未配置采用CMS GC时会触发full gc
5、CMS GC时出现promotion failed和concurrent mode failure
6、minor gc时统计到的晋升到老年代的平均大小大于老年代的剩余空间时
JVM内存组成
1、堆
2、方法区
堆空间分为新生代和老年代,新生代分为(Eden和Survivor),Survivor分为FromSpace和ToSpace
3、虚拟机栈
4、本地方法栈
5、程序计数器
GC如何判断一个对象为垃圾
1、引用计数法
对象拥有一个引用计数器,每当一个地方引用它时,计数值加一,引用失效时,计数减一。当计数值为0时,对象可以被回收。
目前主流的JVM不采用此种方法,因为无法解决循环引用的问题。
2、根搜索算法
以“GC Roots”为起点,向下搜索,走过的路径为引用链,当一个对象到GC Roots没有任何引用链相连时,此对象可被回收。
被GC判定为垃圾的对象一定会被回收吗?
不一定。宣告对象死亡要经过两次标记过程。
1、在对象与GC Roots间没有引用链时会被第一次标记并进行筛选。筛选条件为对象是否有必要进行finalize方法。若对象没有重写finalize方法或者finalize方法已经被调用过,则判定为没有必要执行finalize方法,直接回收。否则判定为有必要。
2、被判定为有必要执行finalize方法的对象会被放入一个F-Queue队列中,由一个虚拟机自动建立的低优先级的finalize线程去执行它,触发对象的finalize方法。GC会对F-Queue中的对象进行第二次小规模的标记,对象只要重新与引用链中的任意对象关联即可从F-Queue中移除,避免被回收。若第二次标记时还没有被引用,则会被回收。
finalize线程所谓的“执行”,表示虚拟机会触发对象的finalize方法,但不确保会等待它运行结束。因为如果一个对象的finalize方法执行缓慢或陷入死循环,将导致F-Queue队列中对象永远 陷入等待,甚至导致内存回收系统的崩溃。
新生代和老年代特点及区别
1、新生代:大部分情况java新建对象都从新生代分配内存,新生代分为Eden和Survivor(包括FromSpace和ToSpace)。大部分对象存活时间较短,会频繁触发minor gc。可以通过-Xmn设置新生代大小,-XX:SurvivorRation调整Eden和Survivor比例。
2、老年代:存放新生代中经过多次垃圾回收仍然存活的对象。major gc不会频繁执行。新建的对象也有可能直接在老年代上分配内存。
新生代老年代采用的收集器算法也不一样。
新生代转移到老年代的触发条件
1、长期存活的对象
2、大对象直接进入老年代
3、minor gc后survivor仍然放不下
4、动态年龄判断,大于等于某个年龄的对象超过survivor的一半,大于等于某个年龄的对象直接进入老年代
哪些对象可作为GC Roots
1、虚拟机栈中引用的对象
2、方法区中类静态属性引用的变量
3、方法区中常量引用的对象
4、本地方法栈中JNI(即一般说的Native方法)引用的对象
如何减少gc的开销
1、不要显式地调用System.gc()
2、尽量减少临时对象的使用
3、对象不用时最好显式置为null
4、尽量使用StringBuffer而不是String来累加字符串
5、能用基本类型int,long等就不用Integer,Long等包装类
6、尽量少使用静态对象变量
静态对象变量不会被gc,会一直占用内存
7、分散对象创建或删除的时间
集中创建大量对象,尤其是大对象,会突然要求大量内存,引发gc。集中删除大量对象,会产生大量垃圾对象。
引起内存溢出的原因
1、内存中加载的数据量过于庞大,如一次从数据库取出过多数据
2、集合类中有对对象的引用,使用完后未清空,使JVM无法回收
3、代码中存在死循环或循环产生过多重复的对象实体
4、使用的第三方软件中的BUG
5、启动参数内存值设置的过小
类加载机制
加载、验证、准备、解析、初始化、使用、卸载
加载:
1、根据类全名获得此类的二进制字节流
2、将二进制的静态存储结构转化为方法区的运行时数据结构
3、在堆中生成一个代表此类的java.lang.Class对象,作为方法区数据的访问入口
验证:
确保class文件中的字节流符合当前虚拟机的要求
准备:
为类变量分配内存并为类变量赋初值。只为类变量(static修饰)分配内存,初值指的是数据类型的零值。
解析:
将符号引用替换为直接饮用
初始化:
执行类构造器初始化类变量和其他资源
Http和Https
Http:超文本传输协议,明文传输
Https:安全套接字层超文本传输协议,Http+SSL协议,依靠证书来验证服务器身份,主要作用:加密传输、身份认证。(建立安全通道,保证数据传输的安全;确认网站的真实性)
主要区别:
1、https需要到ca申请证书,一般免费证书较少,需要一定费用
2、http是明文传输,https是具有安全性的SSL加密协议
3、http和https使用的完全不同的连接方式,用的端口也不一样,http是80端口,https是443端口
4、http的连接是无状态的;https协议是由HTTP+SSL协议构成的可进行加密传输,身份认证的网络协议比http协议安全。
通信步骤
1、客户端请求https连接
2、服务端将包含公钥的证书信息传给客户端
3、客户端与服务端协商SSL的安全等级,即信息加密等级
4、客户端根据加密等级建立密钥,根据公钥对密钥进行加密,传送给服务端
5、服务端利用私钥解密出密钥
6、服务端利用密钥加密与客户端的通信
单例模式
一个单一的类负责创建自己的对象,同时保证只有单个对象被创建。提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
1、单例类只能有一个实例
2、单例类必须自己创建唯一的实例
3、单例类必须给其他所有对象提供这一实例
主要解决一个全局使用的类频繁创建与销毁
解决方案:判断是否存在该单例,若存在则返回,否则创建
关键代码:构造函数是私有的
class SingleObject {
//创建一个SingleObject对象
private static SingleObject instance;
//构造函数为private,保证无法实例化
private SingleObject() {}
//获得唯一可用对象
public static SingleObject getInstance() {
return instance;
}
public static void doSomething() {
System.out.println("Hello World!");
}
}
几种实现方式:
1、懒汉式,线程不安全(没有加锁)
是否 Lazy 初始化:是
是否多线程安全:否
实现难度:易
描述:这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。
这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作。
class SingleObject {
private static SingleObject instance;
private SingleObject() {}
public static SingleObject getInstance() {
if(instance == null) {
instance = new SingleObject();
}
return instance;
}
}
2、懒汉式,线程安全
是否 Lazy 初始化:是
是否多线程安全:是
实现难度:易
描述:这种方式具备很好的 lazy loading,能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步。
优点:第一次调用才初始化,避免内存浪费。
缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。
getInstance() 的性能对应用程序不是很关键(该方法使用不太频繁)。
class SingleObject {
private static SingleObject instance;
private SingleObject() {}
public static synchronized SingleObject getInstance() {
if(instance == null) {
instance = new SingleObject();
}
return instance;
}
}
3、饿汉式
是否 Lazy 初始化:否
是否多线程安全:是
实现难度:易
描述:这种方式比较常用,但容易产生垃圾对象。
优点:没有加锁,执行效率会提高。
缺点:类加载时就初始化,浪费内存。
它基于 classloder 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用 getInstance 方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 显然没有达到 lazy loading 的效果。
class SingleObject {
private static SingleObject instance = new SingleObject();
private SingleObject() {}
public static SingleObject getInstance() {
return instance;
}
}
4、双检锁/双重校验锁(DCL)
JDK 版本:JDK1.5 起
是否 Lazy 初始化:是
是否多线程安全:是
实现难度:较复杂
描述:这种方式采用双锁机制,安全且在多线程情况下能保持高性能。
getInstance() 的性能对应用程序很关键。
class SingleObject {
private static SingleObject instance;
private SingleObject() {}
public static SingleObject getInatance() {
if(instance == null) {
synchronized(SingleObject.class) {
if(instance == null) {
instance = new SingleObject();
}
}
}
return instance;
}
}
5、登记式/静态内部类
是否 Lazy 初始化:是
是否多线程安全:是
实现难度:一般
描述:只有当调用getInstance时才会显式装载SingletonHolder类
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton(){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
6、枚举
JDK 版本:JDK1.5 起
是否 Lazy 初始化:否
是否多线程安全:是
实现难度:易
描述:实现单例模式的最佳方法,更简洁,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}
B树和B+树区别
1、B+树内部节点中,关键字的个数与子树的个数相同,B树中关键字的个数总比子树个数少1
2、B+树中所有指向文件的关键字及其指针都在叶子节点中,B树中有的指向文件的关键字在内部节点中。
3、B+树中内部节点仅仅起索引作用。
4、B+树中在搜索时如果查询和内部节点的关键字一致,搜索过程不停止,而是继续向下搜索这个分支,而B树会停止。
5、B+树的叶子节点都是相连的,便于区间查找和遍历
B+树的优势
1、磁盘读写代价更低
内部节点没有指向关键字具体信息的指针,一次性读入内存中需要查找的关键字也就越多。
2、查询效率更加稳定
任何关键字的查找都必须偶从根节点到叶子节点,查询路径长度相同,导致每一次查询效率相当。
3、更有利于对数据库的扫描
由于叶子结点相连且存储全部数据,只要扫描叶子节点就可以完成遍历。
Collection和Collections区别
1、Collection是集合类的上层接口,是一个接口,包含集合类的基本操作
2、Collections是集合类的工具类,是一个类,用于对集合进行排序,搜索和线程安全等操作。