【第二十六天】总结

语法部分

1. Java的技术结构

A. JAVASE --- 标准版/基础版

B. JAVAEE --- 企业版/商务版

C. JAVAME --- 移动版/微型版

2. Java的跨平台

A. 不同的操作系统有不同的JVM与之对应。---JAVA语言是跨平台的,但是JVM不是跨平台的。 --- JAVA跨平台的前提是因为有JVM

3. 入门程序

A. 编译:将java文件转化为class文件的过程

B. javac -d 编译完成之后存放的路径 java文件 --- 用于编译java文件

C. java -cp class文件的路径 类的全路径名 --- 用于编译

D. 一个java文件中只能有一个公共类,但是可以有多个类,而每一个类对应了一个class文件

4. 关键字

A. 被java内部占用,所有的关键字都是小写的

B. 一共有53个关键字,51个在用,2个保留字:goto,const --- true,false,null

5. 标识符

A. 在程序中自定义的名称

B. 可以由字母、数字、_、$组成;数字不能开头;不能使用关键字;见名知义;java大小写敏感的语言

C. 驼峰命名法

6. 注释

A. 作用:解释说明程序,辅助排错

B. 单行注释 //

C. 多行注释 /* */

D. 文档注释 /** */

E. javadoc -d 将文档提取到指定目录 要提取文档的java文件 --- 用于提取文档的---可以将文档注释中的文字取出形成文档

7. 计算机常量

A. 整数常量,小数常量,字符常量,字符串常量,布尔常量,空常量

B. 这六类常量在程序运行的时候都会存储到运行时常量池中

C. 这六类常量的哈希码是固定不变

8. 进制及其转换

A. 进制,0-1,满2进1,从jdk1.7开始以0b开头标志二进制数字

B. 进制,0-7,满8进1,以0开头

C. 十进制,0-9,满十进1

D. 十六进制,0-9,A-F,满16进1,以0x开头

E. 重点掌握二进制和十进制的转换

9. 变量

A. 变量先声明后使用

B. 变量先给值后使用

C. 变量在哪儿在哪儿用

10. 数据类型及其转换

A. 基本类型

byte - 字节型 --- 1个字节 --- -128~127 --- 多种情况选择

short - 短整型 --- 2个字节 --- -215~215-1

int - 整型 --- 4个字节 --- -231~231-1 ---  整数默认为int

long - 长整型 --- 8个字节 - -263~263-1 --- 时间,系统运行参数,内存 --- 以L作为结尾标志

float - 单精度 --- 4个字节 --- -1038~1038 --- 要求以F作为结尾

double --- 双精度 --- 8个字节 --- -10308~10308 --- 小数默认为double --- 科学计数法

char --- 字符型 --- 2个字节 --- 0~65535

boolean --- 布尔型 --- 现在boolean类型基本上是占用了4个字节

B. 引用类型 

类-class 

接口-interface 

C. 数据类型转换

隐式转换:小转大,整转小---整数转化为小数的时候会产生精度损失

显式转换:小转整,大转小---小数转整数的时候会舍弃所有的小数位;一个大的整数转化为一个小的类型的时候可能也会产生精度损失

int i = 200;

byte b = (byte)i; -> -56

11. 运算符

A. 算术:+ - * / % ++ --

byte/short/char在运算的时候会自动提升为int

整数运算完成之后的结果一定是整数

整数/0 -> 算术异常  非零数字/0.0 或者是 小数/0 -> Infinity 0/0.0 -> NaN

取余结果看%左边的符号

++/-- 在前表示先自增/自减然后参与后续运算;++/-- 在后先参与运算再自增/自减

byte/short/char可以参与++/--

B. 赋值:= += -= *= /= %= &= |= ^= <<= >>= >>>=

除了=以外,其余的都是在本身的基础上运算

除了=以外,其余的都要求变量先有值才能使用

byte/short/char可以参与赋值运算

C. 关系/比较:== != > < >= <= instanceof

instanceof用于判断对象与类的关系

“abc” instanceof Object -> true

D. 逻辑:& | ! ^ && ||

^的运算规则:true^true = false^false = false; true^false=true

&&和|| 的短路特性:||能把&&给短路掉

E. 位:& | ^ << >> >>> ~

针对的整数的补码进行运算

正数的原反补三码一致;负数的反码是在原码的基础上最高位不变,其余位1->0,0->1;补码在反码的基础上+1---计算机在存储数据的时候存的就是补码

F. 三元: ? :

G. 运算符的优先级: 一元运算 . () ! ~ ++ -- > 二元运算 > 三元运算 > 赋值运算

12. 流程控制

A. 判断: if,if-else, if-else if

equals  统计文件个数

B. 选择:switch-case

可以byte/short/char/int,jdk1.7开始支持String,jdk1.5支持枚举常量

每一个case之后是否有break会决定case的顺序是否影响结果

C. 循环:while, do-while for

while:变化不规律或者不确定循环次数的时候 --- 输入,线程状态的判断,迭代器

for:不长比较固定或者次数固定 --- 遍历数组或者是列表

13. 数组

A. 存储多个同一类型的元素的容器---大小固定

B. 数组的应用:

数组元素的排序:冒泡、选择排序---时间复杂度O(n2),空间复杂度o(1)

数组元素的查找:折半查找---O(logn)

数组的反转:头尾互换

数组的复制:System.arraycopy,Arrays.copyOf --- 数组在扩容完之后地址会发生改变

14. 二维数组

A. 存储的元素是一维数组

B. 应用:杨辉三角,螺旋填数

15. 方法

A. 为了重复利用一段逻辑,将这段逻辑提取出来进行封装,这种封装的形式就叫方法

B. 方法的返回值:只要一个方法有返回值类型,就要保证这个方法有返回值

C. 方法的重载:发生在一个类中,方法名一致而参数列表不同,编译时多态

D. 方法的重写:基于继承/实现,两等两小一大,运行时多态---静态方法不存在重写

E. 方法的递归:方法内部调用自己本身---需要控制递归结束的条件 --- StackOverFlowError --- 栈溢出错误

F. 作用:提高复用性,使代码结构更加清晰

面向对象

1. 面向对象与面向过程的比较

A. 面向对象基于面向过程的

B. 相对复杂的事务建议使用面向对象,相对简单的事务适合使用面向过程

2. 类与对象的关系

A. 类是对象的抽取, 对象是类的实例化

B. 属性是一种特征,方法是一种行为

3. 成员变量与局部变量

A. 定义位置, 作用范围,内存位置,生命周期

4. 构造方法

A. 与类同名而没有返回值类型

B. 构造方法可以重载,但是不能被继承

C. 用于创建对象和给对象的属性初始化

5. 代码块

A. 局部代码块:限制变量的生命周期,提高栈内存的利用率

B. 构造代码块:在创建对象的时候先于构造函数执行;用于完成对象的初始化操作

6. this

A. 表示当前对象的引用,用于调用本类中的方法或者是属性‘

B. this语句:表示在本类的构造函数中调用本类其他形式的构造函数---必须放在首行

7. 面向对象的特征

A. 封装、继承、多态(抽象)

8. 封装

A. 体现形式:行为的封装---方法; 类的封装---属性的私有化,内部类

B. 优点:复用性,安全性(数据的合法性)

9. 继承

A. 用extends,单继承

B. 单继承和多继承的优劣性的比较

C. 优点:复用性、安全性(方法调用的混乱问题)

10. 多态

A. 编译时多态:重载;运行时多态:向上造型和方法的重写---基于继承

B. 行为多态:重载、重写;对象的多态:向上造型

C. 优点:提高代码的灵活性,配合反射完成解耦操作

11. 权限修饰符

A. public protected 默认 private

B. 注意protected和默认权限的修饰范围

12. super

A. 在子类中表示父类对象的引用,用于调用父类中的方法或者属性

B. super语句:在子类构造函数中调用父类对应形式的构造函数来创建一个父类对象---如果父类中没有无参构造,则子类构造函数中必须手动提供super;super语句必须放在构造函数的第一行

class A {

public A(int i){}

}

class B extends A{

public B() {

super(5);

}

public B(int i){

super(i);

}

}

13. static

A. 静态变量。在类第一次使用的时候加载到了方法区中静态区,并且在静态区赋予了默认值。静态变量是先于对象存在的,所以静态变量可以通过类名来调用。所有的对象共享这个类的静态变量

B. 静态方法。随着类第一次使用的时候加载到方法区中的静态区,只是存储在静态区而不执行。在方法被调用的时候到栈内存中执行。静态方法先于对象存在,所以在静态方法中不能直接使用本类中的非静态的属性或者方法。静态方法可以重载,可以被继承,但是不能重写,但是在父子类中可以存在方法签名一致的静态方法---如果父子类中存在了方法签名一致的方法,要么都是静态的,要么都是非静态的。

C. 静态代码块。在类第一次被真正使用的时候执行。执行顺序:先父后子,先静后动 -> 父类静态 -> 子类静态 -> 父类非静态 -> 子类非静态

public class StaticDemo {

       public static void main(String[] args) {

              new B();

       }

}

class B {

       public static B b = new B();

       public static int i;

       public static int j = 6;

       static {

              System.out.println("static:");

              System.out.println(i);

              System.out.println(j);

 

       }

       public B() {

              i++;

              j++;

              System.out.println("Constructor:");

              System.out.println(i);

              System.out.println(j);

       }

}

14. final

A. 常量。定义好之后值不可变

B. 最终方法。 可以重载,可以被继承,不能被重写。

C. 最终类。不能被继承

15. abstract

A. 抽象类。不能实例化,有构造函数并且构造函数可以私有化。子类继承抽象类之后必须重写抽象类中的所有的抽象方法,除非这个子类也是抽象类。抽象类一定不是最终类。抽象类中不一定有抽象方法

B. 抽象方法。可以被重载,必须被继承必须被重写。---private/static/final不能修饰抽象方法。如果一个抽象方法使用默认权限修饰的,那么要求父子类必须同包。---抽象方法所在的类一定是最终类

16. interface

A. jdk1.7及其以前,接口只能声明抽象方法;从jdk1.8开始,接口中允许定义实体方法

B. 接口与类之间用implements关键字来关联,称之为实现关系。一个类可以实现多个接口,一个接口可以继承多个接口。

C. 接口中的抽象方法默认用public abstract修饰

D. 接口中的属性默认用public static final修饰

E. 作用:统一结构,便于模块化开发

17. 内部类

A. 方法内部类、成员内部类、静态内部类、匿名内部类

B. 除了静态内部类以外,其他内部类中都不允许定义静态变量和静态方法但是可以定义静态常量

C. 除了静态内部类以外,其他内部类中可以使用当前外部类中的非静态属性和非静态方法

D. 方法内部类在使用当前方法中的数据的时候要求这个数据是一个常量

18. 包

A. 声明包用的是package,导入包用的import

B. 注意通配符:* 表示导入当前包下的所有的类而不包括子包下的类

C. java.lang包下的类和同包类不需要导包

19. 垃圾分代回收机制

A. 堆内存分为了新生代和老生代。新生代分为伊甸园区和幸存区。

B. 如果一个对象在新生代放不开会直接放到老生代

C. 初代回收、完全回收

API

1. Object

A. 所有类的顶级父类

B. equals,getClass, toString, wait, notify, notifyAll

2. String

A. 表示字符串的类。

B. 字符串是一个常量;字符串是被共享的

C. 字符串本质上是一个不可变的字符数组

D. 如果拼接多个字符串,考虑效率建议使用StringBuilder;考虑安全性建议使用StringBuffer

E. 不同的方式下创建的String对象的个数

String str = “a” + “b”;  -> String str = “ab”; --- 1

String str = new String(“a” + “b”); --- 2

String str = new String(“a”) + “b”; --- 5

String str = new String(“a”) + new String(“b”); --- 6

3. 包装类

A. 基本类型对应的类形式--- int-Integer char-Character

B. 自动封箱/拆箱:将基本类型直接赋值给引用类型---valueOf/将引用类型直接赋值给基本类型---***Value---jdk1.5的特性之一

C. 注意四种整数型的范围判断

D. 包装类对象的哈希码在任何条件都是不变的

4. 数学类

A. Math:构造函数私有而且是一个最终类,提供了一系列的针对基本类型进行初等运算的方法。

B. BigDecimal:用于精确运算小数的类。要求参数以字符串形式传入

5. 日期类

A. Date/Calendar --- 要求掌握字符串和日期对象的转换--- SimpleDateFormat -> 字符串转日期用的parse,日期转字符串用的format

6. 异常

A. 是java中用于反馈和处理问题的机制

B. 顶级父类---Throwable

Error:错误。出现之后无法处理

Exception:例外、异常。---记住三到五个常见异常的名字

编译时异常(checked exception)---一旦出现要求必须处理:捕获或者抛出

运行时异常(unchecked exception)---RuntimeException,出现之后可以处理也可以不处理

C. 异常的捕获方式:

多个catch分别捕获分别处理

捕获父类异常统一处理

多个异常之间用|隔开进行分组处理---jdk1.7及其以后

D. try-catch-finally -> finally代码块必须执行一次

7. 集合

A. 存储一组数据的容器---大小不固定

B. 顶级接口---Collection

List:元素有序可重复

ArrayList:基于数组的。默认初始大小是10,每次扩容一半。内存空间连续。便于查询而不便于增删。线程不安全

LinkedList:基于节点的。内存空间不连续的。便于增删而不便于查询。线程不安全

Vector:基于数组。默认初始大小为10,每次扩容一倍。内存空间连续。不便于增删而便于查询。线程安全

Stack:继承了Vector。遵循先进后出。

Set:元素无序不可重复

HashSet:基于哈希码存储。默认初始大小为16,加载因子是0.75f,每次扩容一倍。每次扩容之后要做rehash操作。线程不安全

TreeSet:要求元素对应的类必须Comparable接口。在存储元素的时候会根据元素中的compareTo方法进行自然排序。

Queue:元素先进先出

8. 迭代器

A. Iterator:本质是通过指针的挪动来迭代遍历元素。在迭代过程中不允许直接修改原集合

B. 增强for循环本质山是一个迭代遍历的过程。要求遍历的对象对应的类必须实现Iterable接口 --- jdk1.5的特性之一

9. 比较器

A. Comparator : 重写其中的compare方法,指定比较规则。根据返回值的正负来确定参数大小。如果返回正数表示第一个参数大于第二个参数

B. 如果元素排序的时候没有指定比较规则要求元素对应的类必须实现Comparable,重写的compareTo方法来进行自然排序

10. 映射

A. 顶级接口Map --- 顶级父类Dictionary --- 键唯一,每一个键对应一个值 ---键值对--- Map.Entry

HashMap:允许键或者值为null。默认初始大小是16,加载因子0.75f,默认扩容一倍。异步式线程不安全的映射

Hashtable:不允许键或者值为null。默认容量是11,加载因子是0.75f。同步式线程安全的映射

Properties:继承了Hashtable。可以持久化的映射。键和值默认为String。只能存储在properties文件中---这个文件中不能存储中文,如果添加中文会变为对应的u16编码

B. 要求掌握映射的遍历:keySet, entrySet

11. File

A. 代表文件或者目录的类

B. 每一个file对象所对应的文件或者目录在计算机中不一定存在

C. 路径:绝对路径和相对路径

D. 注意File操作过程中的递归问题

12. IO

A. 用于数据传输的API

B. 方向:输入流和输出流; 形式:字符流和字节流

C. 字符输出流身上有自带的缓冲区,所以在使用的时候要冲刷缓冲区

D. 流中的异常处理

E. 转换流:在转换的时候需要考虑编码问题。如果没有指定编码,则默认使用系统平台码,也可以指定编码进行转换。 InputStreamReader ---将字节转化为字符,  OutputStreamWriter --- 将字符转化为字节

F. 合并流:SequenceInputStream --- 合并多个流的。这多个流在合并的时候需要放到一个Vector集合中,然后利用Vector集合来产生一个Enumeration对象,然后利用Enumeration对象来构建合并流对象

G. 序列化/反序列化流:ObjectOutputStream/ObjectInputStream --- 将对象中的信息完整保存/将对象还原回来的过程。一个接口---Serializable,两个关键字--- static/transient,一个版本号 --- serialVersionUID,大部分的集合和映射都不能被序列化

13. 线程

A. 进程中执行的任务或者逻辑

B. 定义方式:Thread,Runnable,Callable

C. 多线程的并发安全问题:多个线程之间相互抢占资源导致出现不合法的数据---解决方案:同步代码块---锁对象要求所有线程都认识:共享资源、类的字节码、this

D. 死锁:由于多个线程之间的锁相互嵌套导致程序无法继续执行。---避免:减少线程数量,统一锁对象,减少锁嵌套

E. 等待唤醒机制:利用wait/notify/notifyAll方法来调节线程的执行顺序。---线程的等待唤醒和锁是对应的。--- 线程池本质上是存储线程的队列

F. 守护线程:只要被守护的线程结束,守护线程无论完成与否都得结束。---在代码中一个线程要么是守护线程,要么是被守护线程---守护线程是以最后一个被守护的线程作为结束标志。

G. 线程的状态:创建、就绪、运行、阻塞、消亡

H. 线程的优先级:1-10。优先级越高理论上抢占概率越大。但是相邻两个优先级的差别不明显。

14. 套接字

A. UDP:基于流。不建立连接,不可靠。传输速率相对较快。要求对数据封包,每个包不超过64K。适用于要求速度而对可靠性要求相对较低的场景---发送端和接收端(DatagranSocket, DatagramPacket)

B. TCP:基于流。建立连接,经过三次握手,可靠。传输速率相对较慢。理论上不限制数据的大小。适用于要求可靠性而对速度要求相对较低的场景---客户端(Socket)和服务器端(ServerSocket)

15. JDK1.5的特性

A. 自动封箱/拆箱

B. 增强for循环

C. 静态导入: import static 包名.类名.方法名;--提高了加载效率但是可读性不高

D. 可变参数: 用 ... 定义,本质上是一个数组。在使用的时候可以不传入参数或者是传入任意多个参数或者是数组。定义可变参数的时候必须放在参数列表的末尾,而且只能有一个。

E. 枚举:用enum来定义一个枚举。取值情况能够一一列举并且相对固定。---构造函数默认私有而且只能私有。枚举常量必须放在枚举类的第一行。枚举中可以定义一切方法和属性,也包括抽象方法。所有的枚举默认继承java.lang.Enum

F. 泛型:参数化类型---ParameterizedType。泛型的擦除(用具体类型替换泛型的过程)发生在编译期。要求看懂泛型的继承问题。 ? extends 类/接口---表示上限 ? super 类/接口 --- 表示泛型的下限

G. 反射:Class,Field,Method,Constructor,Annotation,Package --- 解剖一个类,获取这个类的字节码对象,然后根据字节码对象来获取实例对象或者执行指定方法的过程---获取字节码对象:类名.class,对象.getClass(),Class.forName(类的全路径名)

H. 注解:用@interface来定义一个注解。注解中的属性类型只能是基本类型、String、Class、其他注解类型、枚举以及这五种类型的一维数组形式。元注解(作用在注解上的注解):@Target---指定作用范围;@Retention---指定使用周期;@Documented---确定注解是否出现在文档中;@Inherited---确定此注解能否作用在子类上

I. 动态代理

J. 内省

16. JDK1.8的特性

A. 接口中的默认方法:要求必须用default/static修饰

B. Lambda表达式:重写接口中的唯一的抽象方法 (parameters) -> {statements;}

C. 函数式接口:接口中只有一个抽象方法---@FunctionalInterce

D. Stream:用于批量处理数据的流式结构,不是流。

E. 时间包:java.time

LocalDate---只含有日期而不含有时间的类

LocalTime---只含有时间而不含有日期的类

ChronoUnit---枚举类,用于指定时间单位

 

 

 

 

 

 

 

 

 

 

附录:GC以及JVM总结

 

JVM总结

1. JVM中常用参数:

-Xss 设置每个线程的栈内存大小

-Xmn 设置新生代大小

-Xms 设置堆内存的初始内存大小

-Xmx 设置堆内存的最大可用大小

例如: -Xss128k -Xmn5M -Xms10M -Xmx10M表示每个线程的大小是128k,新生代5M,可用堆内存10M,最大可用内存为10M

2. 栈内存溢出

如果方法运行时需要的栈的深度超过了虚拟机所允许的最大的栈的深度,那么会出现栈内存溢出,例如递归

public class Test {

public static void main(String[] args){

sum(100000);

}

 

public int sum(int i){

if(i == 0){

return 0;

}

return i + sum(--i);

}

}

3. 堆内存溢出

如果出现了堆内存溢出,表示程序需要的内存超过了虚拟机分配的最大内存,可以通过提高-Xmx来调节。例如:

public class Test {

public static void main(String[] args){

byte[] bs = new byte[1024 * 1024 * 10];

}

}

java -Xmn-5M -Xms10M -Xmx10M Demo

此时数组需要的内存大小是10M,但是堆内存只有10M,此时新生代为5M,老生代也为5M,所以此时分配不开,就会出现堆内溢出。

4. 对象回收

对象大于新生代剩余内存的时候,将直接放入老年代,当老年代剩余内存还是无法放下的时候,触发垃圾收集,收集后还是不能放下就会抛出内存溢出异常了

5. 方法区溢出

持久带溢出有可能是运行时常量池溢出,也有可能是方法区中保存的class对象没有被及时回收掉或者class信息占用的内存超过了我们配置可能在如下几种场景下出现:

a)使用一些应用服务器的热部署的时候,我们就会遇到热部署几次以后发现内存溢出了,这种情况就是因为每次热部署的后,原来的class没有被卸载掉。

b)如果应用程序本身比较大,涉及的类库比较多,但是我们分配给持久带的内存(通过-XX:PermSize和-XX:MaxPermSize来设置)比较小的时候也可能出现此种问题。

c)一些第三方框架,比如spring,hibernate都通过字节码生成技术(比如CGLib)来实现一些增强的功能,这种情况可能需要更大的方法区来存储动态生成的Class文件。

例如:

import java.util.*;   

import java.lang.*;   

public class Demo{   

public static void main(String... args){   

       List<String> list = new ArrayList<String>();   

     while(true){   

     list.add(UUID.randomUUID().toString().intern());   

       }   

}       

}  

6. Java内存模型

 

 

第一、程序计数器(PC)

程序计数器(Program Counter Register)是一块较小的内存空间,它可以看做当前线程所执行的字节码的行号指示器,字节码解释器工作时就是通过改变这个计数器的值来取下一条需要执行的字节码指令,分支、跳转、循环、异常处理、线程恢复等基础功能都需要这个计数器来完成

注:程序计数器是线程私有的,每条线程都会有一个独立的程序计数器

第二、Java栈(虚拟机栈)

Java栈就是Java中的方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧,这个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息,每个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。

注:Java栈也是线程私有的。

异常可能性:对于栈有两种异常情况:如果线程请求的栈深度大于栈所允许的深度,将抛出StackOverflowError异常,如果虚拟机栈可以动态拓展,在拓展的时无法申请到足够的内存,将会抛出OutOfMemoryError异常

第三、本地方法栈

本地方法栈与Java栈所发挥的作用是非常相似的,它们之间的区别不过是Java栈执行Java方法,本地方法栈执行的是本地方法。

注:本地方法栈也是线程私有的

异常可能性:和Java栈一样,可能抛出StackOverflowError和OutOfMemeryError异常

第四、Java堆

对于大多数应用来说,Java堆是Java虚拟机所管理的内存中最大的一块,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存,当然我们后面说到的垃圾回收器的内容的时候,其实Java堆就是垃圾回收器管理的主要区域。

注:堆是线程共享的

异常可能性:如果堆中没有内存完成实例分配,并且堆也无法再拓展时,将会抛出OutOfMemeryError异常

第五、方法区

方法区它用于存储已被虚拟机加载的类信息、常量、静态常量、即时编译器编译后的代码等数据。

注:方法区和堆一样是线程共享的

异常可能性:当方法区无法满足内存分配需求时,将抛出OutOfMemeryError异常

7. 类加载机制

  Java中的所有类,都需要由类加载器装载到JVM中才能运行。类加载器本身也是一个类,而它的工作就是把class文件从硬盘读取到内存中。在写程序的时候,我们几乎不需要关心类的加载,因为这些都是隐式装载的,除非我们有特殊的用法,像是反射,就需要显式的加载所需要的类。

      Java类的加载是动态的,它并不会一次性将所有类全部加载后再运行,而是保证程序运行的基础类(像是基类)完全加载到jvm中,至于其他类,则在需要的时候才加载。这当然就是为了节省内存开销。

     Java的类加载器有三个,对应Java的三种类:

     Bootstrap Loader  // 负责加载系统类 (指的是内置类,像是String)

            | 

          - - ExtClassLoader   // 负责加载扩展类(就是继承类和实现类)

                          | 

                      - - AppClassLoader   // 负责加载应用类(程序员自定义的类)

      三个加载器各自完成自己的工作,但它们是如何协调工作呢?哪一个类该由哪个类加载器完成呢?为了解决这个问题,Java采用了委托模型机制。

      委托模型机制的工作原理很简单:当类加载器需要加载类的时候,先请示其Parent(即上一层加载器)在其搜索路径载入,如果找不到,才在自己的搜索路径搜索该类。这样的顺序其实就是加载器层次上自顶而下的搜索,因为加载器必须保证基础类的加载。之所以是这种机制,还有一个安全上的考虑:如果某人将一个恶意的基础类加载到jvm,委托模型机制会搜索其父类加载器,显然是不可能找到的,自然就不会将该类加载进来。

前面是对类加载器的简单介绍,它的原理机制非常简单,就是下面几个步骤:

1.装载:查找和导入class文件;

2.连接:

      (1)检查:检查载入的class文件数据的正确性;

      (2)准备:为类的静态变量分配存储空间;

      (3)解析:将符号引用转换成直接引用(这一步是可选的)

3.初始化:初始化静态变量,静态代码块。

      这样的过程在程序调用类的静态成员的时候开始执行,所以静态方法main()才会成为一般程序的入口方法。类的构造器也会引发该动作。

 

GC总结

1. 典型的垃圾收集算法

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

  这是最基础的垃圾回收算法,之所以说它是最基础的是因为它最容易实现,思想也是最简单的。标记-清除算法分为两个阶段:标记阶段和清除阶段。标记阶段的任务是标记出所有需要被回收的对象,清除阶段就是回收被标记的对象所占用的空间。具体过程如下图所示:

 

 

  从图中可以很容易看出标记-清除算法实现起来比较容易,但是有一个比较严重的问题就是容易产生内存碎片,碎片太多可能会导致后续过程中需要为大对象分配空间时无法找到足够的空间而提前触发新的一次垃圾收集动作。

  2.Copying(复制)算法

  为了解决Mark-Sweep算法的缺陷,Copying算法就被提了出来。它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用的内存空间一次清理掉,这样一来就不容易出现内存碎片的问题。具体过程如下图所示:

 

 

 

  这种算法虽然实现简单,运行高效且不容易产生内存碎片,但是却对内存空间的使用做出了高昂的代价,因为能够使用的内存缩减到原来的一半。

  很显然,Copying算法的效率跟存活对象的数目多少有很大的关系,如果存活对象很多,那么Copying算法的效率将会大大降低。

  3.Mark-Compact(标记-整理)算法

  为了解决Copying算法的缺陷,充分利用内存空间,提出了Mark-Compact算法。该算法标记阶段和Mark-Sweep一样,但是在完成标记之后,它不是直接清理可回收对象,而是将存活对象都向一端移动,然后清理掉端边界以外的内存。具体过程如下图所示:

 

  

  4.Generational Collection(分代收集)算法

  分代收集算法是目前大部分JVM的垃圾收集器采用的算法。它的核心思想是根据对象存活的生命周期将内存划分为若干个不同的区域。一般情况下将堆区划分为老年代(Tenured Generation)和新生代(Young Generation),老年代的特点是每次垃圾收集时只有少量对象需要被回收,而新生代的特点是每次垃圾回收时都有大量的对象需要被回收,那么就可以根据不同代的特点采取最适合的收集算法。

  目前大部分垃圾收集器对于新生代都采取Copying算法,因为新生代中每次垃圾回收都要回收大部分对象,也就是说需要复制的操作次数较少,但是实际中并不是按照1:1的比例来划分新生代的空间的,一般来说是将新生代划分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden空间和其中的一块Survivor空间,当进行回收时,将Eden和Survivor中还存活的对象复制到另一块Survivor空间中,然后清理掉Eden和刚才使用过的Survivor空间。

  而由于老年代的特点是每次回收都只回收少量对象,一般使用的是Mark-Compact算法。

注意,在堆区之外还有一个代就是永久代(Permanet Generation),它用来存储class类、常量、方法描述等。对永久代的回收主要回收两部分内容:废弃常量和无用的类。

2. 典型的垃圾收集器

  1.Serial/Serial Old

  Serial/Serial Old收集器是最基本最古老的收集器,它是一个单线程收集器,并且在它进行垃圾收集时,必须暂停所有用户线程。Serial收集器是针对新生代的收集器,采用的是Copying算法,Serial Old收集器是针对老年代的收集器,采用的是Mark-Compact算法。它的优点是实现简单高效,但是缺点是会给用户带来停顿。

  2.ParNew

  ParNew收集器是Serial收集器的多线程版本,使用多个线程进行垃圾收集。

  3.Parallel Scavenge

  Parallel Scavenge收集器是一个新生代的多线程收集器(并行收集器),它在回收期间不需要暂停其他用户线程,其采用的是Copying算法,该收集器与前两个收集器有所不同,它主要是为了达到一个可控的吞吐量。

  4.Parallel Old

  Parallel Old是Parallel Scavenge收集器的老年代版本(并行收集器),使用多线程和Mark-Compact算法。

  5.CMS

  CMS(Current Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,它是一种并发收集器,采用的是Mark-Sweep算法。

  6.G1

  G1收集器是当今收集器技术发展最前沿的成果,它是一款面向服务端应用的收集器,它能充分利用多CPU、多核环境。因此它是一款并行与并发收集器,并且它能建立可预测的停顿时间模型。

 

posted @ 2018-02-09 23:25  songyao  阅读(128)  评论(0编辑  收藏  举报