java8 新特性整理

本文详细介绍了Java8中的新特性,包括HashMap的优化,从JDK1.7的链表结构升级到JDK1.8的链表-红黑树结构,提高了查询和插入效率。另外,重点讲解了Lambda表达式的概念、语法和应用场景,以及四大核心函数式接口。此外,还提及了Optional类、接口的默认方法、新的日期和时间API、重复注解和类型注解等其他新特性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、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后,对年老代进行压缩

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值