JDK新特性
1.lambda表达式
1.1 lambda表达式的概念
Lambda表达式——特殊的匿名内部类,语法更简洁。Lambda表达式允许把函数作为一个方法的参数(函数作为方法参数传递),将代码像数据一样传递。
1.2 lambda表达式语法
#参数为一个时,()可加可不加
#形参列表的数据类型会自动推断
(parameters) -> {执行语句;}
或者
(parameters) -> 执行语句
示例
(a) ->System.out.println(a)
a -> System.out.println(a)
(a,b) -> System.out.println(a+b)
(int a,String b) -> System.out.println(a+b)
1.3 无参无返回值的lambda
public class TestLambdaDemo1 {
/**
* lambda
* 1 方法有参数
* 2 参数是接口
* 3 接口只有一个方法
*/
public static void main(String[] args) {
// 用匿名内部类来一遍
m1(new M1( ) {
@Override
public void test1() {
System.out.println("test1执行-无参无返回值" );
}});
// 改造成lambda
m1(() -> System.out.println("lambda执行-无参无返回值" ));
}public static void m1(M1 m1) {
m1.test1();
}
}// 定义一个接口
// 像这种只能有一个抽象方法的接口,用于lambda的接口
// 称之为函数式接口
interface M1{
void test1();
// void test11();
}
1.4 有参数无返回值的lambda
public class TestLambdaDemo2 {
/**
* 1 方法有参数
* 2 参数是接口
* 3 接口只有一个抽象方法
*/public static void main(String[] args) {
// 匿名内部类实现
m2(10, new M2( ) {
@Override
public void test2(int a) {
System.out.println(a*10);
}
});m22(8, "8", new M22( ) {
@Override
public void test22(int a, String b) {
System.out.println(a+b);
}
});// lambda改造
// 一个参数时,圆括号可以加
m2(5,(a) -> System.out.println(a*10));
// 一个参数时,圆括号也可以不加
m2(6,a -> System.out.println(a*10));
// 数据类型可加可不加
m2(7,(int a) -> System.out.println(a*10));// 多个参数,圆括号必须加
m22(9,"9",(a,b) -> System.out.println(a+b ));
// 数据类型可加可不加
m22(10,"10",(int a,String b) -> System.out.println(a+b ));
}public static void m2(int x,M2 m2) {
m2.test2(x);
}public static void m22(int x,String y,M22 m22) {
m22.test22(x,y);
}}
interface M2{
void test2(int a);
}interface M22{
void test22(int a,String b);
}
1.5 无参有返回值的lambda
public class TestLambdaDemo3 {
/**
* 1 方法得有参数
* 2 参数得是接口
* 3 接口中只能有一个抽象方法
*/
public static void main(String[] args) {
// 匿名内部类
m3(new M3( ) {
@Override
public int test3() {
int a = 1;
a++;
return 200;
}
});// lambda
// lambda 中有且只有一行语句时,return可以省略
m3(() -> 302);
m3(() -> {return 302;});
// lambda中有多行语句,return不能省略
m3(() -> {
int a = 1;
a++;
return 302;
});}
public static void m3(M3 m3){
int i = m3.test3( );
System.out.println(i );
}
}interface M3 {
int test3();
}
1.6 有参有返回值的lambda
public class TestLambdaDemo4 {
/**
* 设计一个方法m4,方法的参数列表是接口
* 该接口中有1个抽象方法,能接收两个int类型参数,返回值是String
* 要求,给m4方法传入lambda表达式,功能是将传入的两个参数拼接为String后返回
*/
public static void main(String[] args) {m4(10,20,(x,y) ->x+""+y);
}
public static void m4(int a,int b,M4 m4) {
String str = m4.test4(a,b);
System.out.println(str );
}
}
interface M4{
String test4(int a,int b);
}
2.函数式接口
2.1 函数式接口的概念
如果一个接口有且仅有一个抽象方法,但是可以有多个非抽象方法,则该接口称之为函数式接口。使用@Functionallnterface注解可以检测接口是否符合函数式接口。函数式接口可以使用Lambda表达式,Lambda表达式会被匹配到这个抽象方法上。
2.2 常见函数式接口
函数式接口 | 参数类型 | 返回类型 | 说明 |
Consumer<T> 消费型接口 | T | void | void accept(T t);对类型为T的对象应用操作。 |
Supplier<T> 供给型接口 | 无 | T | T get();返回类型为T的对象。 |
Function<T,R> 函数型接口 | T | R | R apply(T t);对类型为T的对象应用操作, 并返回类型为R类型的对象。 |
Predicate<T> 断言型接口 | T | 断言型接口 | boolean test(T t);确定类型为T的对象是否满足条件, 并返回boolean类型。 |
2.3 Comsumer<T>
public class TestConsumer { public static void main(String[] args) { Consumer<Double> c= t->{ System.out.println("吃饭消费了:"+t); }; fun01(c,100); } //调用某个方法时,该方法需要的参数为接口类型,这时就应该能想到使用lambda public static void fun01(Consumer<Double> consumer,double money){ consumer.accept(money); } }
2.4 Supplier<T>
//供给型接口:只有返回值没有参数列表 public class TestSupplier { public static void main(String[] args) { Supplier<String> supplier = new Supplier<String>() { @Override public String get() { return "你好"; } }; //简化 Supplier<String> s=()-> "你好"; System.out.println(supplier.get()); System.out.println(s.get()); } }
2.5 Function<T,R>
//Function<T,R>函数型接口 //T: 参数类型的泛型 //R: 函数返回结果的泛型 public class TestFunction { public static void main(String[] args) { //使用匿名内部类表示 Function<Integer, String> function = new Function<Integer, String>() { @Override public String apply(Integer o) { if (o!=null){ return String.valueOf(o); }else { return null; } } }; //使用lambda表达式优化 Function<Integer,String> lambda=o->String.valueOf(o); System.out.println(function.apply(16)); System.out.println(lambda.apply(16)); } }
2.6 Predicated<T>
/** * 断定型接口:有一个输入参数返回值只能是Boolean值 */ public class TestPredicate { public static void main(String[] args) { Predicate<String> predicate = new Predicate<String>() { @Override public boolean test(String o) { return o.isEmpty(); } }; //简化 Predicate<String> predicate1=(o)->{return o.isEmpty();}; System.out.println(predicate.test("")); System.out.println(predicate1.test("aa")); } }
3. 方法引用
3.1 方法引用的概念
方法引用是Lambda表达式的一种简写形式。如果Lambda表达式方法体中只是调用一个特定的已经存在的方法,则可以使用方法引用。
3.2 方法引用的分类
类型 | 语法 | 对应的Lambda表达式 |
静态方法引用 | 类名::staticMethod | (args)->类名.staticMethod(args) |
实例方法引用 | inst::instMethod | (args)->inst.instMethod(args) |
对象方法引用 | 类名::instMethod | (inst,args) -> inst.instMethod(args) |
构建方法引用 | 类名::new | (args)-> new类名(args) |
3.3 方法引用的示例
public class TestMethodReferences { public static void main(String[] args) { //构造方法引用: 类名::new (参数)->new 类名(参数) // Function<String,People> function1=(n)->new People(n); Function<String, People> function1 = People::new; People s = function1.apply("张三"); System.out.println(s); //实例方法引用: inst::instMethod (args)->inst.instMethod(args) Consumer<String> consumer = (name) -> s.setName(name); consumer.accept("李四"); //对象方法引用: 类名::实例方法. (参数1,参数2)->参数1.实例方法(参数2) // Function<People,String> function2=(p)->p.getName(); Function<People, String> function2 = People::getName; String name = function2.apply(s); System.out.println(name); People.setAge(12); //静态方法引用 类名::staticMethod (args)->类名.staticMethod(args) // Consumer<Integer> function3 = (age)->People.setAge(age); Consumer<Integer> function3 = People::setAge; function3.accept(20); System.out.println(People.getAge()); } } class People{ private String name; private static int age; public People() { } public People(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } public static void setAge(int age) { People.age = age; } public static int getAge() { return age; } @Override public String toString() { return "People{" + "name='" + name + '\'' + '}'; } }
4. Stream流
4.1 什么是 Stream?
Stream(流)是一个来自数据源的元素队列并支持聚合操作。
- 元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。
- 数据源 流的来源。 可以是集合,数组,I/O channel, 产生器generator 等。
- 聚合操作 类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等。
Stream操作还有两个基础的特征:
- Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
- 内部迭代: 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现。
4.2 如何获取Stream流对象
- 通过Collection对象的stream()或parallelStream()方法。
- 通过Arrays类的stream()方法。
- 通过Stream接口的of()、iterate()、generate()方法。
- 通过IntStream、LongStream、DoubleStream接口中的of、rangerangeClosed方法。
4.3 Stream流中常见的api
- 中间操作api: 一个操作的中间链,对数据源的数据进行操作。而这种操作的返回类型还是一个Stream对象。
- 终止操作api: 一个终止操作,执行中间操作链,并产生结果,返回类型不再是Stream流对象。
终止操作:
遍历: foreach
匹配: find、match
规约: reduce
聚合: max、min、count
中间操作:
筛选:filter
映射:map
收集:collect
排序: sorted
提取与组合
4.4 Stream流操作示例
中间操作
public class TestStreamDemo1 {
public static void main(String[] args) {
// 1 获得流
Stream<String> stream = Stream.of("11", "11","22","22", "33");
Stream<String> stream2 = Stream.of("aa", "bb", "cc");
// 2 limit(long n) 保留流里面的前几个
// stream.limit(2).forEach(s -> System.out.println(s ));// 3 skip(long n) 跳过前几个
// stream.skip(2).forEach(s -> System.out.println(s ));// 4 concat 拼接两个流为新的流
// 该方法是Stream接口中的静态方法,直接通过接口名调用
// Stream.concat(stream,stream2).forEach(s -> System.out.println(s ));// 5 distinct 将流中的数据去重
// stream.distinct().forEach(s -> System.out.println(s ));// 6 sorted 排序,默认是升序
Stream<Integer> stream3 = Stream.of(5,3,2,1,4);
// stream3.sorted().forEach(s -> System.out.println(s ));stream3.sorted((o1,o2) -> o2 - o1).forEach(s -> System.out.println(s ));
}
}
终止操作
public class TestStreamDemo2 {
public static void main(String[] args) {
Stream<Integer> stream = Stream.of(5, 3, 2, 1, 4);// 1 终止操作min,返回的是Optional类
// Optional类中有个方法,get() ,可以获得其中的数据
// min方法返回的是,排序后的第一个
// Optional<Integer> optional = stream.min((o1, o2) -> o1 - o2);
// Integer min = optional.get( );
// System.out.println(min );// Integer min = stream.min((o1, o2) -> o1 - o2).get( );
// 2 count() 计数
// System.out.println(stream.count( ));
// long count = stream.filter(e -> e > 2).count( );
// System.out.println(count );// 3 findFirst() 获得流里面第一个,返回Optional
// System.out.println(stream.findFirst( ).get( ));// 4 anyMatch(Predicate) 任意元素匹配时返回true
// 判断流里面的元素,任意一个都>3
// 任意一个是,只要有一个就可以
// System.out.println(stream.anyMatch(i -> i > 3));// 5 allMatch(Predicate) 全部元素匹配时返回true
// System.out.println(stream.allMatch(i -> i > 3));// 6 reduce() 将元素归纳
// 假设我们对一个集合中的值进行求和
// System.out.println(stream.reduce(0, (sum, e) -> {
// System.out.println("sum = " + sum);
// System.out.println("e = " + e);
// return sum + e;
// }));// 7 iterator() 迭代器迭代元素
// Iterator<Integer> iterator = stream.iterator( );
// while (iterator.hasNext()) {
// Integer next = iterator.next( );
// System.out.println(next );
// }
}
}
收集流
public class TestStreamDemo3 {
public static void main(String[] args) {
Stream<Integer> stream = Stream.of(5, 5,3, 2, 1, 4,4);// 流中数据转成list集合
// List<Integer> list = stream.filter(i -> i > 3).collect(Collectors.toList( ));
// System.out.println(list );// 流中数据转成set集合
// Set<Integer> set = stream.filter(i -> i > 3).collect(Collectors.toSet( ));
// System.out.println(set );// 流可以转成数组
// Object[] array = stream.toArray( );// 也可转成指定类型的数组
Integer[] array = stream.toArray((length) -> new Integer[length]);
System.out.println(Arrays.toString(array));
}
}
5. 新日期时间API
旧的日期时间的缺点:
- 设计比较乱: Date日期在java.util和java.sql也有,而且它的时间格式转换类在java.text包。
- 线程不安全。
新增加了哪些类?
- LocalDate: 表示日期类。yyyy-MM-dd
- LocalTime: 表示时间类。 HH:mm:ss
- LocalDateTime: 表示日期时间类 yyyy-MM-dd t HH:mm:ss sss
- DatetimeFormatter:日期时间格式转换类。
- Instant: 时间戳类。
- Duration: 用于计算两个日期类
代码示例:
public class Test { public static void main(String[] args) { LocalDate now = LocalDate.now(); //获取当前日期 LocalDate date = LocalDate.of(2022, 8, 23);//指定日期 System.out.println(now); LocalTime now1 = LocalTime.now();//当前时间 LocalTime of = LocalTime.of(17, 30, 20, 600); System.out.println(now1); LocalDateTime now2 = LocalDateTime.now();//获取当前日期时间 LocalDateTime now3 = LocalDateTime.of(2023,8,6,12,00,00); Duration between = Duration.between(now2, now3); System.out.println(between.toHours()); System.out.println(now2); DateTimeFormatter dateTimeFormatter=DateTimeFormatter.ofPattern("yyyy-MM-dd"); LocalDate parse = LocalDate.parse("1999-12-12", dateTimeFormatter);//把字符串转换为日期格式 String format = parse.format(dateTimeFormatter); System.out.println(parse); System.out.println(format); } }
6. Optional 类
Optional 类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。
常用方法:
方法 | 描述 |
static <T> Optional<T> empty() | 返回空的 Optional 实例。 |
boolean equals(Object obj) | 判断其他对象是否等于 Optional。 |
T get() | 如果在这个Optional中包含这个值,返回值, 否则抛出异常:NoSuchElementException |
static <T> Optional<T> of(T value) | 返回一个指定非null值的Optional。 |
static <T> Optional<T> ofNullable(T value) | 如果为非空,返回 Optional 描述的指定值,否则返回空的 Optional。 |
boolean isPresent() | 如果值存在则方法会返回true,否则返回 false。 |
T orElse(T other) | 如果存在该值,返回值, 否则返回 other。 |
void ifPresent(Consumer<? super T> consumer) | 如果值存在则使用该值调用 consumer , 否则不做任何事情。 |
T orElseGet(Supplier<? extends T> other) | 如果存在该值,返回值, 否则触发 other,并返回 other 调用的结果。 |
实例代码:
public class TestOptionalDemo { public static void main(String[] args) { //创建空的Optional Optional<Object> emptyOptional = Optional.empty(); //使用of创建 Optional<String> hello = Optional.of("hello");//正常 //Optional<Object> objectOptional = Optional.of(null);//运行时抛出异常 //使用ofNullable创建 Optional<String> hello2 = Optional.ofNullable("hello");//正常 Optional<Object> objectOptional2 = Optional.ofNullable(null);//正常 Optional<String> helloOptional = Optional.ofNullable("hello"); Optional<String> nullOptional = Optional.ofNullable(null); //使用get获取Optional中的对象 System.out.println(helloOptional.get()); //抛出运行时异常 java.util.NoSuchElementException //System.out.println(nullOptional.get()); //执行orElse String result1 = helloOptional.orElse("val is null"); System.out.println(result1); String result2 = nullOptional.orElse("val is null"); System.out.println(result2); //执行orElseGet String result3 = helloOptional.orElseGet(() -> { return "val is null"; }); System.out.println(result3); String result4 = nullOptional.orElseGet(()->{ return "val is null"; }); System.out.println(result4); //使用isPresent判断Optional中的值是否为null System.out.println(helloOptional.isPresent()); System.out.println(nullOptional.isPresent()); } }
JVM
1.什么是JVM?
JVM是Java Virtual Machine(Java虚拟机)的缩写。它是java运行环境的一部分,是一个虚构出来的计算机,它是通过在实际的计算机上仿真模拟各种计算机功能来实现的。JVM是用来解析和运行Java程序的。Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。
2.JVM内存结构
-
类装载器(
ClassLoader
)(用来装.class
文件) -
执行引擎(执行字节码,或者执行本地方法)
-
运行时数据区(方法区、堆、java栈又称为Java 虚拟机栈、PC寄存器又称为程序计数器、本地方法栈)
程序计数器(线程私有)
一块较小的内存空间, 是当前线程所执行的字节码的行号指示器,每条线程都要有一个独立的程序计数器,这类内存也称为“线程私有”的内存。这个内存区域是唯一一个在虚拟机中没有规定任何 OutOfMemoryError 情况的区域。
虚拟机栈(线程私有)
是描述java方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
本地方法栈(线程私有)
本地方法栈和虚拟机栈作用类似, 区别是虚拟机栈为执行 Java 方法服务, 而本地方法栈则为Native 方法服务, 如果一个 VM 实现使用 C-linkage 模型来支持 Native 调用, 那么该栈将会是一个C 栈,但 HotSpot VM 直接就把本地方法栈和虚拟机栈合二为一。
堆(Heap-线程共享)
堆是被线程共享的一块内存区域,创建的对象和数组都保存在 Java 堆内存中,也是垃圾收集器进行垃圾收集的最重要的内存区域。由于现代 VM 采用分代收集算法, 因此 Java 堆从 GC 的角度还可以细分为: 新生代(Eden 区、From Survivor 区和 To Survivor 区)和老年代。
方法区(线程共享)
方法区即我们常说的永久代(Permanent Generation), 用于存储被 JVM 加载的类信息、常量、静态变量、即时编译器编译后的代码等数据. HotSpot VM把GC分代收集扩展至方法区, 使用Java堆的永久代来实现方法区, 这样 HotSpot 的垃圾收集器就可以像管理 Java 堆一样管理这部分内存。
运行时常量池(Runtime Constant Pool)是方法区的一部分。Class 文件中除了有类的版本、字段、方法、接口等描述等信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。
3.JVM 运行时内存
Java 堆从 GC 的角度还可以细分为: 新生代(Eden 区、From Survivor 区和 To Survivor 区)和老年代。
新生代
是用来存放新生的对象。一般占据堆的 1/3 空间。由于频繁创建对象,所以新生代会频繁触发MinorGC 进行垃圾回收。新生代又细分为三个区:Eden区、SurvivorFrom、ServivorTo区,三个区的默认比例为:8:1:1。
Eden区:Java新创建的对象绝大部分会分配在Eden区(如果对象太大,则直接分配到老年代)。当Eden区内存不够的时候,就会触发MinorGC(新生代采用的是复制算法),对新生代进行一次垃圾回收。
SurvivorFrom区和To区:在GC开始的时候,对象只会存在于Eden区和名为From的Survivor区,To区是空的,一次MinorGc过后,Eden区和SurvivorFrom区存活的对象会移动到SurvivorTo区中,然后会清空Eden区和SurvivorFrom区,并对存活的对象的年龄+1,如果对象的年龄达到15,则直接分配到老年代。MinorGC完成后,SurvivorFrom区和SurvivorTo区的功能进行互换。下一次MinorGC时,会把SurvivorTo区和Eden区存活的对象放入SurvivorFrom区中,并计算对象存活的年龄。
老年代
老年代主要存放应用中生命周期长的内存对象。老年代比较稳定,不会频繁的进行MajorGC。而在MaiorGC之前才会先进行一次MinorGc,使得新生的对象进入老年代而导致空间不够才会触发。当无法找到足够大的连续空间分配给新创建的较大对象也会提前触发一次MajorGC进行垃圾回收腾出空间。
在老年代中,MajorGC采用了标记—清除算法:首先扫描一次所有老年代里的对象,标记出存活的对象,然后回收没有标记的对象。MajorGC的耗时比较长。因为要扫描再回收。MajorGC会产生内存碎片,当老年代也没有内存分配给新来的对象的时候,就会抛出OOM(Out of Memory)异常。
永久代
永久代指的是永久保存区域。主要存放Class和Meta(元数据)的信息。Classic在被加载的时候被放入永久区域,它和存放的实例的区域不同,在Java8中,永久代已经被移除,取而代之的是一个称之为“元数据区”(元空间)的区域。元空间和永久代类似,都是对JVM中规范中方法的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存的限制。
4.JVM垃圾回收
4.1 如何确定垃圾
引用计数法:在 Java 中,引用和对象是有关联的。通过引用计数来判断一个对象是否可以回收,一个对象如果没有任何与之关联的引用,即引用计数为 0,则说明对象不太可能再被用到,那么这个对象就是可回收对象。存在循环引用问题(循环引用会导致内存泄漏,内存泄漏的堆积会导致内存溢出)。
可达性分析:为了解决引用计数法的循环引用问题,Java 使用了可达性分析的方法。通过一系列的“GC roots”对象作为起点搜索。如果在“GC roots”和一个对象之间没有可达路径,则称该对象是不可达的。要注意的是,不可达对象不等价于可回收对象,不可达对象变为可回收对象至少要经过两次标记过程。两次标记后仍然是可回收对象,则将面临回收。
4.2 JAVA 四种引用类型
强引用:在程序代码中普遍存在的,类似 Object obj = new Object()
这类引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。
软引用:用来描述一些还有用但并非必须的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。如果这次回收后还没有足够的内存,才会抛出内存溢出异常。
弱引用:也是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。在JDK 1.2之后,提供了WeakReference类来实现弱引用。比如 threadlocal。
虚引用:是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。它的作用是能在这个对象被收集器回收时收到一个系统通知。在JDK 1.2之后,提供了PhantomReference类来实现虚引用。
无论引用计数算法还是可达性分析算法都是基于强引用而言的。
4.3 垃圾回收算法
标记清除算法(Mark-Sweep):最基础的垃圾回收算法,分为两个阶段,标注和清除。标记阶段标记出所有需要回收的对象,清除阶段回收被标记的对象所占用的空间。该算法最大的问题是内存碎片化严重,后续可能发生大对象不能找到可利用空间的问题。
复制算法(copying):为了解决 Mark-Sweep 算法内存碎片化的缺陷而被提出的算法。按内存容量将内存划分为等大小的两块。每次只使用其中一块,当这一块内存满后将尚存活的对象复制到另一块上去,把已使用的内存清掉。这种算法实现简单,内存效率高,不易产生碎片。但是可用内存被压缩到了原本的一半,且存活对象增多的话,Copying 算法的效率会大大降低。
标记整理算法(Mark-Compact):结合了以上两个算法,为了避免缺陷而提出。标记阶段和 Mark-Sweep 算法相同,标记后不是清理对象,而是将存活对象移向内存的一端。然后清除端边界外的对象。
分代收集算法:分代收集法是目前大部分 JVM 所采用的方法,其核心思想是根据对象存活的不同生命周期将内存划分为不同的域,一般情况下将 GC 堆划分为老生代(Tenured/Old Generation)和新生代(YoungGeneration)。老生代的特点是每次垃圾回收时只有少量对象需要被回收,新生代的特点是每次垃圾回收时都有大量垃圾需要被回收,因此可以根据不同区域选择不同的算法。
4.4 垃圾收集器
Serial 垃圾收集器(单线程、复制算法):Serial 是最基本垃圾收集器,使用的是复制算法。Serial 是一个单线程的收集器,只会使用一个 CPU 或一条线程去完成垃圾收集工作,并且在进行垃圾收集的同时,必须暂停其他所有的工作线程,直到垃圾收集结束。
ParNew 垃圾收集器(Serial+多线程):ParNew 垃圾收集器其实是 Serial 收集器的多线程版本,也使用复制算法,除了使用多线程进行垃圾收集之外,其余的行为和 Serial 收集器完全一样,ParNew 垃圾收集器在垃圾收集过程中同样也要暂停所有其他的工作线程。
Serial Old 收集器(单线程标记整理算法 ):Serial Old 是 Serial 垃圾收集器年老代版本,它同样是个单线程的收集器,使用标记-整理算法,这个收集器也主要是运行在 Client 默认的 java 虚拟机默认的年老代垃圾收集器。
Parallel Old 收集器(多线程标记整理算法):Parallel Old 收集器是Parallel Scavenge的年老代版本,使用多线程的标记-整理算法,在 JDK1.6才开始提供。
CMS 收集器(多线程标记清除算法):Concurrent mark sweep(CMS)收集器是一种年老代垃圾收集器,其最主要目标是获取最短垃圾回收停顿时间,和其他年老代使用标记-整理算法不同,它使用多线程的标记-清除算法。
G1 收集器:G1 (Garbage-First)垃圾回收器将堆内存分割成不同的区域,然后并发的对其进行垃圾回收。G1收集器的设计目标是取代CMS收集器,它同CMS相比,不会产生大量内存碎片,并可以添加预测机制,用户可以指定期望停顿时间。