一、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空间被移除了,取而代之的是Metaspace(JEP 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的条件
-
GC在优先级低的线程中运行,空闲时调用
-
java内存不足,GC会被调用
8.9.4、垃圾收集器
垃圾收集器 | 描述 |
---|---|
1.串行收集器 | 暂停所有应用的线程来工作,单线程 |
2.并行收集器 | 默认垃圾收集器,暂停所有应用,多线程 |
3.G1收集器 | 用于大堆区域,堆内存分隔,并发回收 |
4.CMS收集器 | 多线程扫描,标记需要回收的实例,清除 |
8.9.5、垃圾回收算法
算法 | 概述 | 影响 |
---|---|---|
标记清除法 | 分为两个阶段,标记和清除。标记出需要回收的对象,清除被标记的对象所占用的空间 | 碎片化严重 |
复制算法 | 将内存分为大小相等的两块,每次只是用其中的一块,当一块内存满后,将尚存活的对象移到另一半内存,把使用内存清掉 | 内存压缩减半 |
标记整理法 | 标记后不清除对象,将存活的对象移到内存一端,清除边界外的对象 | |
分代收集算法 | 根据对象存活的不同生命周期,将内存划分为不同的区域(Eden和Survivor区),回收时,将存活对象复制到另一块Survivor区 |
总结:
-
内存空间包含:方法区、堆、栈、本地方法栈
-
方法区:各个线程共享的区域,存放类信息,常量,静态变量
-
堆:线程共享区域,实例存放,堆空间大
-
栈:线程私有区域,生命周期与线程相同,递归可能造成栈溢出
-
本地方法栈:与栈类似,执行本地方法
-
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、年老代大小选择
-
响应时间优先的应用:年老代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和会话持续时间等一些参数。如果堆设置小了,可以会造成内存碎片、高回收频率以及应用暂停而使用传统的标记清除方式;如果堆大了,则需要较长的收集时间。最优化的方案,一般需要参考以下数据获得:
-
并发垃圾收集信息
-
持久代并发收集次数
-
传统GC信息
-
花在年轻代和年老代回收上的时间比例
减少年轻代和年老代花费的时间,一般会提高应用的效率
-
吞吐量优先的应用:一般吞吐量优先的应用都有一个很大的年轻代和一个较小的年老代。原因是,这样可以尽可能回收掉大部分短期对象,减少中期的对象,而年老代尽存放长期存活对象。
较小堆引起的碎片问题
-
-XX:+UseCMSCompactAtFullCollection:使用并发收集器时,开启对年老代的压缩。
-
-XX:CMSFullGCsBeforeCompaction=0:上面配置开启的情况下,这里设置多少次Full GC后,对年老代进行压缩