java8 新特性整理

一、JDK8新特性

1、hashMap优化

1.1、Jdk1.7中:hashmap(数组(16)-链表)

​ 使用一个Entry数组存储数据,用key的hashcode取模来决定key会被分配到数组的位置,如果hashcode相同,或者hashcode取模后的结果相同(hash collison),那么这些key会被定位到Entry数组的同一个格子里,这些key会形成一个链表。如果在同一个格子里,会调用equals()方法,比较对象是否是同一个,若是同一个,则将值替换成最新的,若不是,将刚插入的对象放在最前面,其他依次后移,最终形成一个链表。

​ 在hashcode特别差的情况下,比如说所有的key的hashcode都相同,这个链表可能很长,那么put/get操作都可能需要遍历这个链表。HashMap分配的空间有限,需要一个加载因子如果你hashmap的空间有100,那么当你插入了75个元素的时候,hashmap就需要扩容了,不然的话会形成很长散列桶,对于查询和插入都会增加时间,因为他要一个一个的equals。但是你又不能让加载因子很小,0.01这样是不合适的,因为他会大大消耗你的 内存, 你一加入一个对象hashmap就扩容。这时就存在着一个平衡,jdk中默认是0.75,可以根据自己的实际情况进行调整。

1.2、Jdk1.8 中:hashMap(数组-链表-红黑树)

​ 使用一个Node数组来存储数据,但这个Node可能是链表结构,也可能是红黑树结构,如果插入的key的hashcode相同,那么这些key也会被定位到Node数组的同一个格子里。如果同一个格子的key不超过8个,使用链表结构存储。如果超过了8个,那么会调用treeifyBin()函数,将链表转换为红黑树。除添加之外其他效率都高。

注意:但是真正想要利用JDK1.8的好处,有一个限制:key的对象,必须正确的实现了Compare接口如果没有实现Compare接口,或者实现得不正确(比方说所有Compare方法都返回0)那JDK1.8的HashMap其实还是慢于JDK1.7的。

1.3、ConcurrentHashMap

在JDK1.7版本中,ConcurrentHashMap的数据结构是由一个Segment数组和多个HashEntry组成,如下图所示:

​ Segment数组的意义就是将一个大的table分割成多个小的table来进行加锁,也就是上面的提到的锁分离技术,而每一个Segment元素存储的是HashEntry数组+链表,这个和HashMap的数据存储结构一样。

​ JDK1.8的实现已经摒弃了Segment的概念,而是直接用Node数组+链表+红黑树的数据结构来实现,并发控制使用Synchronized和CAS来操作,整个看起来就像是优化过且线程安全的HashMap,虽然在JDK1.8中还能看到Segment的数据结构,但是已经简化了属性,只是为了兼容旧版本。

2、Lambda表达式

2.1、 Lambda表达式:

一个lambda可以由用逗号分隔的参数列表、–>符号与函数体三部分表示.引入特殊的操作符”->”

​ 左侧:lambda 表达式参数列表。

​ 右侧:lambda 表达式体,即要执行的功能。

语法一:无参数,无返回值,匿名内部类变量被final修饰

语法二:有一个参数,无返回值

Consumer consumer = (x)-> System.out.println(x);
consumer.accept("我真帅");

语法三:有两个以上参数,有返回值,lambda有多条语句。(如果只有一条语句,大括号和return都可以不写)

Comparator<Integer> com = (x,y)->{
    System.out.println(".....");
    return Integer.compare(x,y);
};
​

语法四:Lambda表达式的参数列表的数据类型可以不写,因为JVM的编译器可以通过上下文推断出。

Lambda表达式需要”函数式接口”的支持

函数是接口:接口中只有一个抽象方法的接口,称为函数式接口,可以使用注解@FunctionalInterface修饰,可以检查是否是函数式接口。

2.2、 Java8 内置的四大核心函数式接口

(1) Consumer<T>:消费型接口

​ void accept(T t);

(2) Supplier<T>: 供给型接口

​ T get();

(3) Function<T,R>:函数型接口

​ R apply(T t);

(4) Predicate<T> : 断言型接口

​ boolean test(T t);

2.3 、方法的引用:

​ 若Lambda体中的内容方法已经实现了,我们可以使用”方法引用”---Lambda的另一种表达形式。

主要有三种语法格式:

(1) 对象::实现方法名

Employee emp = new Employee(); 
Supplier<String> supplier = ()->emp.getName();
String name = supplier.get();
   ------>>>
​
 Supplier<String> sup = Emp::getName;
 String name = sup.get();
​

(2) 类::静态方法名

Comparator<Integer> com = (x,y)->Integer.compare(x,y); 
Comparator<Integer> com1 = Integer::compare;

(3) 类::实例方法名

BiPredicate<String,String> bp = (x,y)-> x.equals(y); // x:实例方法的调用者
​
BiPredicate<String,String> bp2 = String::equals;

注意:①Lambda 体中调用方法的参数列表与返回值类型,要与函数式接口中的抽象方法的函数列表和返回值类型保持一致。

​ ②若 Lambda参数列表中的第一参数是实例方法的调用者,而第二个参数是实例方法的参数时,可以使用CalssName::method。eg:类::实例方法名的实例

2.4、 构造器引用

2.4.1、 格式:ClassName::new

@Test
public void test7() {
    Supplier<Employee> sup = () -> new Employee();
    Employee employee = sup.get();
    /*构造器引用/,无参构造*/  
    Supplier<Employee> supm = Employee::new;
    System.out.println(supm.get()); ---get()无参
    //有参构造
    EmployeeInterFace<Integer,String,Integer,Double,Employee> supplier =(a,s,d,f)->new Employee(a,s,d,f);
    EmployeeInterFace<Integer,String,Integer,Double,Employee> supplier1=Employee::new;
    System.out.println(supplier1.grenter(102,"张三",23,4444.44));
}
//有参构造函数式接口
@FunctionalInterface
public interface EmployeeInterFace<T,R,E,F,G> {
    public G grenter(T t,R r,E e,F f);
}

注意:构造器参数列表要与接口参数列表保持一致。

2.4.2、Stream:是数据渠道,用于操作数据源(集合、数组等)所产生的元素序列。”集合讲的是数据,流讲的是计算”

注意:① Stream自己不会存储元素。

​ ② Stream不会改变源对象。相反,他们会返回一个持有结果的新的Stream。

​ ③ Stream操作是延迟执行的。这意味着他们会等到需要结构的时候才去执行。

2.4.3、创建Stream的方式:

  /*1.可以通过Collection系列集合提供的Stream()或者parallelStream()创建Stream*/
List<String> list = new ArrayList<String>();
​
Stream<String> stringStream = list.stream();
​
/*2.通过Arrays中的静态方法stream()获取数组流 */
String[] strings = {"a", "b", "c"};
Stream<String> stream = Arrays.stream(strings);
/*3.通过Stream类中的静态方法*/
Stream<String> a = Stream.of("a", "b", "c");
/*4.创建无限流*,参数1:起始位置,参数2:一元操作//*迭代/*/
             Stream<Integer> iterate = Stream.iterate(0, x -> x + 2);
               iterate.limit(10).forEach(System.out::println);
   /*生成*/
           Stream<Double> generate = Stream.generate(() -> Math.random*());
           generate.limit(5).forEach(System.out::println);

2.5 、中间操作:

2.5.1、 筛选与切片:

​ Filter --接收Lambda,从流中排除某些元素。

​ Limit --截断流,使其元素不超过给定数量。

​ Skip(n) --跳过元素,返回一个扔掉了前n个元素的流。若流中元素不足n个,返回一个空流。与limit(n)互补。

​ Distinct --刷选,通过流所生成元素的hashCode()和equals()去重。对象需重写hashCode和equals方法。

2.5.2、映射:

​ Map --接收Lambda,将元素转换成其他形式或提取信息。接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。

​ FlatMap --接收一个函数作为参数,将流中的每个值都转换成另一个流,然后把所有流连接成一个流。

public void test3(){
    List<String> strings = Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee");
    strings.stream()
            .map(TestStreamAPI::filterCharcter)
            .forEach((sm)->{
              sm.forEach(System.out::println);
            });
}
​
@Test
public void test4(){
    List<String> strings = Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee");
   strings.stream()
         .flatMap(TestStreamAPI::filterCharcter)
         .forEach(System.out::println);
}
/*将字符串转成流*/
public static Stream<Character> filterCharcter(String str){
    List<Character> list = new ArrayList<>();
    for (Character c:str.toCharArray()){
        list.add(c);
    }
    return list.stream();
​
}
​

2.5.3 、排序:

Sorted() --自然排序(Comparable)

Sorted(Comparator) --定制排序(Comparator)

@Test
public void test5(){
    employees.stream()
            .sorted((e1,e2)->{
             if (e1.getAge()==e2.getAge()){
                 return e1.getName().compareTo(e2.getName());
             }else {
                 return -e1.getName().compareTo(e2.getName());
             }//降序只需要加-
            })
            .forEach(System.out::println);
}

2.5.4、 查找与匹配

AllMatch --检查是否匹配所有元素

anyMatch --检查是否至少匹配一个元素

noneMatch --检查是否没有匹配所有元素

findFirst --返回第一个元素

findAny --返回当前流中的任意元素count --返回流中的元素总个数

Max --返回流中最大的元素

Min --返回流中最小的元素

@Test
/*获取工资的最大值*/
public void test7() {
    Optional<Double> max = employees.stream()
            .map(Employee::getSalary)
            .max(Double::compare);
    System.out.println(max.get());
}

2.5.6、 规约与收集

reduce: 规约

   List.Stream().reduce((x,y)->x+y);

收集:

collector接口中方法的实现决定了如何对流执行收集操作(如收集到List、set、map)。但是collectors实用类提供很多静态方法,可以方便地创建常见收集器实例,具体方法:

collect --将流转换成其他形式。接收一个Collector接口的实现,用于给Stream中元素做汇总的方法

方法:

   @Test
public void test() {
    List<String> collect = employees.stream()
            .map(Employee::getName)
            .collect(Collectors.toList());
    collect.forEach(System.out::println);
    System.out.println("---------------------------");
    HashSet<String> collect1 = employees.stream()
            .map(Employee::getName)
            .collect(Collectors.toCollection(HashSet::new));
    collect1.forEach(System.out::println);
}

并行

OptionalLong reduce = LongStream.rangeClosed(0, 1000000L)
        .parallel()
        .reduce((x, y) -> x + y);
System.out.println(reduce.getAsLong());
package com.javacodegeeks.java8.parallel.arrays;
 
import java.util.Arrays;
import java.util.concurrent.ThreadLocalRandom;
/**
*使用了parallelSetAll()方法来对一个有20000个元素的数组进行随机赋值。然后,调用parallelSort方法。这个程序首先打印出前10个元素的值,之后对整个数组排序
**/ 
public class ParallelArrays {
    public static void main( String[] args ) {
        long[] arrayOfLong = new long [ 20000 ];        
         
        Arrays.parallelSetAll( arrayOfLong, 
            index -> ThreadLocalRandom.current().nextInt( 1000000 ) );
        Arrays.stream( arrayOfLong ).limit( 10 ).forEach( 
            i -> System.out.print( i + " " ) );
        System.out.println();
         
        Arrays.parallelSort( arrayOfLong );     
        Arrays.stream( arrayOfLong ).limit( 10 ).forEach( 
            i -> System.out.print( i + " " ) );
        System.out.println();
    }
}

这个程序在控制台上的输出如下(请注意数组元素是随机生产的):

Unsorted: 591217 891976 443951 424479 766825 351964 242997 642839 119108 552378 
Sorted: 39 220 263 268 325 607 655 678 723 793

3、Optional:避免空指针操作的类

常用方法:

/*option类:空值判断容器*/
public void test2(){
    Optional<Employee> employee = Optional.ofNullable(new Employee(111, "张三", 18, 5555, Employee.Status.BUSY));
    if (employee.isPresent()){ //有值
        System.out.println(employee.get());
    }
    Employee emp= employee.orElse(new Employee(111, "张三", 18, 5555, Employee.Status.BUSY));
    Optional<String> s = employee.map((e) -> e.getName());
    Optional<String> s1 = employee.flatMap((e) -> Optional.of(e.getName()));
    System.out.println(s1.get());
}

4、接口中有默认方法

Java8 中接口有抽象方法,默认实现方法和静态方法。

接口中使用default修饰符修饰的实现方法。

5、新的日期和时间API

5.1 、LocalDate 、LocalTime、LocalDateTime类

​ LocalDate 、LocalTime、LocalDateTime类的实例是不可变的对象,分别表示使用ISO-8601(国际标准化组织指定的现代公民的日期和时间的表示法)日历系统的日期、时间、日期和时间。他们提供了简单的日期或时间,并不包含当前的时间信息。也不包含与时区相关的信息。

/*获取日期时间实例*/
LocalDateTime ldt = LocalDateTime.now();
​
LocalDateTime localDateTime = LocalDateTime.of(2018, 05, 06, 10, 30, 0);
​

5.2 Instant: 时间戳

​ (unix元年:1970年1月1日00:00:00到某个时间之间的毫秒值)

/*时间戳(unix 元年:1970年1月1日00:00:00 到某个时间之间的毫秒值)*/
Instant instant = Instant.now();//默认获取的是UTC时区
System.out.println(instant);
​
/*偏移量*/
OffsetDateTime time = instant.atOffset(ZoneOffset.ofHours(8));
System.out.println(time);
​
/*毫秒*/
System.out.println(instant.toEpochMilli());
​
/*通过毫秒获取实例*/
Instant milli = Instant.ofEpochMilli(1);
System.out.println(milli);
​

6.3 、 Duration:

​ 计算两个时间之间的间隔 Period:计算两个”日期”之间的间隔。

Instant instant2 =  Instant.now();
Duration between = Duration.between(instant, instant2);

6.4 DateTimeFormatter :格式化时间/日期

/*日期格式转换*/
DateTimeFormatter dtf = DateTimeFormatter.ISO_DATE;
LocalDateTime now = LocalDateTime.now();
String strDate = dtf.format(now);
System.out.println(strDate);
DateTimeFormatter dtf2 = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss");
String strDate2 = dtf2.format(now);
System.out.println(strDate2);
LocalDateTime dateTime = now.parse(strDate2, dtf2);     
​

6.5 时区:ZonedDate、ZonedTime、ZonedDateTime

​ java8中加入了对时区的支持,带时区的时间分别为: ZonedDate、ZonedTime、ZonedDateTime中每个时区都对应着ID,地区ID都为”{区域}/{城市}”的格式 eg:Asia/Shanghai等。

​ Zoneld:该类中包含了所有的时区信息

​ getAvailableZonelds():可以获取所有时区信息

​ Of(id):用指定的时区信息获取Zoneld对象。

Set<String> availableZoneIds = ZoneId.getAvailableZoneIds();
​
availableZoneIds.forEach(System.out::println);
​
LocalDateTime dateTime = LocalDateTime.now(ZoneId.of("Asia/Rangoon"));
​
System.out.println(dateTime);
​

6、重复注解和类型注解

6.1、重复注解

自从Java 5引入了注解机制,这一特性就变得非常流行并且广为使用。然而,使用注解的一个限制是相同的注解在同一位置只能声明一次,不能声明多次。Java 8打破了这条规则,引入了重复注解机制,这样相同的注解可以在同一地方声明多次。

重复注解机制本身必须用@Repeatable注解。事实上,这并不是语言层面上的改变,更多的是编译器的技巧,底层的原理保持不变。

package com.javacodegeeks.java8.repeatable.annotations;
 
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
 
public class RepeatingAnnotations {
    @Target( ElementType.TYPE )
    @Retention( RetentionPolicy.RUNTIME )
    public @interface Filters {
        Filter[] value();
    }
     
    @Target( ElementType.TYPE )
    @Retention( RetentionPolicy.RUNTIME )
    @Repeatable( Filters.class )
    public @interface Filter {
        String value();
    };
     
    @Filter( "filter1" )
    @Filter( "filter2" )
    public interface Filterable {        
    }
     
    public static void main(String[] args) {
        for( Filter filter: Filterable.class.getAnnotationsByType( Filter.class ) ) {
            System.out.println( filter.value() );
        }
    }
}

​ 正如我们看到的,这里有个使用@Repeatable( Filters.class )注解的注解类Filter,Filters仅仅是Filter注解的数组,但Java编译器并不想让程序员意识到Filters的存在。这样,接口Filterable就拥有了两次Filter(并没有提到Filter)注解。

​ 同时,反射相关的API提供了新的函数getAnnotationsByType()来返回重复注解的类型(请注意Filterable.class.getAnnotation( Filters.class )经编译器处理后将会返回Filters的实例)。

程序输出结果如下:

filter1
filter2

6.2、扩展注解的支持

Java 8扩展了注解的上下文。现在几乎可以为任何东西添加注解:局部变量、泛型类、父类与接口的实现,就连方法的异常也能添加注解。下面演示几个例子:

package com.javacodegeeks.java8.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Collection;
public class Annotations {
    @Retention( RetentionPolicy.RUNTIME )
    @Target( { ElementType.TYPE_USE, ElementType.TYPE_PARAMETER } )
    public @interface NonEmpty {        
    }        
    public static class Holder< @NonEmpty T > extends @NonEmpty Object {
        public void method() throws @NonEmpty Exception {           
        }
    }     
    @SuppressWarnings( "unused" )
    public static void main(String[] args) {
        final Holder< String > holder = new @NonEmpty Holder< String >();       
        @NonEmpty Collection< @NonEmpty String > strings = new ArrayList<>();       
    }
}

​ ElementType.TYPE_USE和ElementType.TYPE_PARAMETER是两个新添加的用于描述适当的注解上下文的元素类型。在Java语言中,注解处理API也有小的改动来识别新增的类型注解。

7、Base64

在Java 8中,Base64已经成为Java类库的标准。它的使用十分简单,下面让我们看一个例子:

package com.javacodegeeks.java8.base64;
 
import java.nio.charset.StandardCharsets;
import java.util.Base64;
 
public class Base64s {
    public static void main(String[] args) {
        final String text = "Base64 finally in Java 8!";
         
        final String encoded = Base64
            .getEncoder()
            .encodeToString( text.getBytes( StandardCharsets.UTF_8 ) );
        System.out.println( encoded );
         
        final String decoded = new String( 
            Base64.getDecoder().decode( encoded ),
            StandardCharsets.UTF_8 );
        System.out.println( decoded );
    }
}

程序在控制台上输出了编码后的字符与解码后的字符:

QmFzZTY0IGZpbmFsbHkgaW4gSmF2YSA4IQ==
Base64 finally in Java 8!

Base64类同时还提供了对URL、MIME友好的编码器与解码器(Base64.getUrlEncoder() / Base64.getUrlDecoder(), Base64.getMimeEncoder() / Base64.getMimeDecoder())。

8、Java虚拟机(JVM)的

8.1、jvm介绍

jvm是运行在操作系统之上的,与硬件系统没有直接的交互。

总体位置

8.1、类装载器:

​ 负责加载class文件,class文件在文件开头有特定的文件标示,并且ClassLoader只负责class文件的加载,至于它是否可以运行,则由Execution Engine决定。

  • 虚拟机自带的加载器启动类加载器(Bootstrap)C++;

  • 扩展类加载器(Extension)Java;

  • 应用程序类加载器(App)Java,也叫系统类加载器,加载当前应用的classpath的所有类;

  • 用户自定义加载器 Java.lang.ClassLoader的子类,用户可以定制类的加载方式

加载流程:某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。

8.2、Execution Engine :执行引擎负责解释命令,提交操作系统执行

8.3、Native Interface:

​ 本地接口Java语言本身不能对操作系统底层进行访问和操作,但是可以通过JNI接口调用其他语言来实现对底层的访问。

8.4、本地接口:

​ 是融合不同的编程语言为Java所用,它的初衷是融合 C/C++程序,Java诞生的时候是C/C++

横行的时候,要想立足,必须有调用C/C++程序,于是就在内存中专门开辟了一块区域处理标记为Native的代码,它的具体做法是Native Method Stack中登记Native方法,在Execution Engine 执行时加载Native libraries。

8.5、PC寄存器

​ 每个线程都有一个程序计数器,是线程私有的,就是就是一个指针,指向方法区中的方法字节码(用来存储指向下一条指令的地址,也即将要执行的指令代码),由执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不记。

8.6 、栈

​ 栈也叫栈内存,主管Java程序的运行,是在线程创建时创建,它的生命 期是跟随 线程的生命期,线程 结束 栈内存也就释放 ,对于栈来说不存在 垃圾 回收问题,只要线程一结束 该栈就Over,生命周期和线程一致,是线程私有的。基本类型的变量、 实例方法、引用类型变量 都是在函数的栈内存中分配 。

Exception in thread "main" java.lang.StackOverflowError

8.7 方法区

1:方法区是线程共享的,通常用来保存装载的类的元结构信息。比如:运行时常量池+静态变量+常量+字段+方法字节码+在类/实例/接口初始化用到的特殊方法等。

2:通常和永久区关联在一起(Java7之前),但具体的跟JVM的实现和版本有关。

8.8、堆

java7 之前

一个JVM实例只存在一个堆内存, 堆内存的大小是可以调节的。类加载器读取了类文件后,需要把类、方法、常变量放到堆内存中,保存所有引用类型的真实信息,以方便执行器执行。

新生区新生区是类的诞生、成长、消亡的区域,一个类在这里 产生,应用,最后被垃圾回收器收集,结束生命。 新生区又分为两部分 : 伊甸区(Eden space)和幸存者区(Survivor pace) ,所有的类都是在伊甸区被new出

来的。 幸存区有 两个: 0区(Survivor 0 space)和1区(Survivor 1 space)。当 伊甸园 的空间用完时,程序又需要创建对象,JVM的垃圾回收器将对伊甸园 区进行 垃圾 回收(Minor GC),将伊甸园区中的不再被其他对 象所引用的对象进行销毁 。然后将伊甸园 中的 剩余对象移动到幸存0区.若幸存0区也满了, 再对该区进行垃圾 收,然后移动到1区。 那如果1区也 满了呢?再移动到养老 区。 若养老 区也满了,那么这 个时候将产生MajorGC(FullGC),进行养老 区的内存清理。若养老 区执行了Full GC之后 发现依 然无法进行对象的保存,就 会产生OOM异常“

如果 出现java.lang.OutOfMemoryError: Java heap space异常, 说明Java虚拟机的 堆内存不够。原因有二:
(1)Java虚拟机的堆内存设 置不够,可以通过参数-Xms、-Xmx来调整。
(2)代码中创建了大量大 对象,并且 长时间不能被垃圾收集器收集(存在 被引用)。

PermGen空间被移除了,取而代之的是MetaspaceJEP 122)。JVM选项**-XX:PermSize与-XX:MaxPermSize分别被-XX:MetaSpaceSize与-XX:MaxMetaspaceSize所代替。**

堆内存调优01:

-Xms 设置初始分配大小,默认为物理内存的1/64
-Xmx 最大分配内存,默认为物理内存的1/4
-XX:+PrintGCDetails 输出详细的GC处理日志
public static void main(String[] args){
long maxMemory = Runtime.getRuntime().maxMemory() ;//返回 java 虚拟机试图使用的最大内存量。
long totalMemory = Runtime.getRuntime().totalMemory() ;//返回 Java 虚拟机中的内存总量。
System.out.println("MAX_MEMORY = " + maxMemory + "(字节)、" + (maxMemory / (double)1024 /1024) + "MB");
    System.out.println("TOTAL_MEMORY = " + totalMemory + "(字节)、" + (totalMemory /       (double)1024 / 1024) + "MB");
}

8.9、垃圾回收机制

8.9.1、根据生命周期不同,放在不同代上

年轻代 存放新生对象
年老代 经理N次垃圾回收,仍存活对象,生命周期较长,GC强制垃圾回收
持久代 存放静态文件,java类,方法等(方法区)

8.9.2、回收判断

垃圾回收判断方法 原理
1.引用计数 对象引用计数为0时,垃圾收集
2.对象遍历 对对象进行遍历,删除不可到达的对象

8.9.3、触发gc的条件

  1. GC在优先级低的线程中运行,空闲时调用

  2. java内存不足,GC会被调用

8.9.4、垃圾收集器

垃圾收集器 描述
1.串行收集器 暂停所有应用的线程来工作,单线程
2.并行收集器 默认垃圾收集器,暂停所有应用,多线程
3.G1收集器 用于大堆区域,堆内存分隔,并发回收
4.CMS收集器 多线程扫描,标记需要回收的实例,清除

8.9.5、垃圾回收算法

算法 概述 影响
标记清除法 分为两个阶段,标记和清除。标记出需要回收的对象,清除被标记的对象所占用的空间 碎片化严重
复制算法 将内存分为大小相等的两块,每次只是用其中的一块,当一块内存满后,将尚存活的对象移到另一半内存,把使用内存清掉 内存压缩减半
标记整理法 标记后不清除对象,将存活的对象移到内存一端,清除边界外的对象  
分代收集算法 根据对象存活的不同生命周期,将内存划分为不同的区域(Eden和Survivor区),回收时,将存活对象复制到另一块Survivor区  

总结:

  • 内存空间包含:方法区、堆、栈、本地方法栈

    1. 方法区:各个线程共享的区域,存放类信息,常量,静态变量

    2. 堆:线程共享区域,实例存放,堆空间大

    3. 栈:线程私有区域,生命周期与线程相同,递归可能造成栈溢出

    4. 本地方法栈:与栈类似,执行本地方法

    5. pc寄存器:控制程序指令执行顺序

8.10、常见配置参数

​ JVM 中最大堆大小有三方面限制:相关操作系统的数据模型(32-bt还是64-bit)限制;系统的可用虚拟内存限制;系统的可用物理内存限制。32位系统下,一般限制在1.5G~2G;64为操作系统对内存无限制 。

8.10.1、常见配置汇总:

堆参数 参数描述
-Xms 初始堆大小
-Xmx 最大堆大小
-XX:NewSize=n 设置年轻代大小
-XX:newRatio=n 设置年轻代和老年代的比值。如:为3,表示年轻代与老年代比值为1:3
-XX:SurivorRation=n 年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。 如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5
-XX:MaxPermSize=n 设置持久代大小
收集器参数 描述
-XX:+UseSerialGC 设置串行收集器
-XX:UseParallelGC 设置并行收集器
-XX:+UseParalledIOldGC 设置并行老年代收集器
-XX:+UseConcMarkSweepGC 设置并发收集器

8.10.2、调优总结

8.10.2.1、年轻代大小选择

   - **响应时间优先的应用**:**尽可能设大,直到接近系统的最低响应时间限制**(根据实际情况选择)。在此种情况下,年轻代收集发生的频率也是最小的。同时,减少到达年老代的对象。  


  • 吞吐量优先的应用:尽可能的设置大,可能到达Gbit的程度。因为对响应时间没有要求,垃圾收集可以并行进行,一般适合8CPU以上的应用。

8.10.2.2、年老代大小选择

  1. 响应时间优先的应用:年老代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率会话持续时间等一些参数。如果堆设置小了,可以会造成内存碎片、高回收频率以及应用暂停而使用传统的标记清除方式;如果堆大了,则需要较长的收集时间。最优化的方案,一般需要参考以下数据获得:

  • 并发垃圾收集信息

  • 持久代并发收集次数

  • 传统GC信息

  • 花在年轻代和年老代回收上的时间比例

减少年轻代和年老代花费的时间,一般会提高应用的效率

  1. 吞吐量优先的应用:一般吞吐量优先的应用都有一个很大的年轻代和一个较小的年老代。原因是,这样可以尽可能回收掉大部分短期对象,减少中期的对象,而年老代尽存放长期存活对象。

较小堆引起的碎片问题

  • -XX:+UseCMSCompactAtFullCollection:使用并发收集器时,开启对年老代的压缩。

  • -XX:CMSFullGCsBeforeCompaction=0:上面配置开启的情况下,这里设置多少次Full GC后,对年老代进行压缩

猜你喜欢

转载自blog.csdn.net/qq_31108731/article/details/81158676
今日推荐