目录
主要内容
- final
- 抽象类和抽象方法
- 接口
- 内部类
- 虚拟机和垃圾回收
学习目标
节数 | 知识点 | 要求 |
第一节(final和抽象类) | final | 掌握 |
抽象类 | 掌握 | |
抽象方法 | 掌握 | |
第二节(接口) | 接口 | 掌握 |
JDK1.8接口新特征 | 掌握 | |
接口应用:内部比较器Comparable | 掌握 | |
第三节(内部类) | 成员内部类 | 了解 |
静态内部类 | 了解 | |
方法内部类 | 了解 | |
第四节(虚拟机和垃圾回收) | 匿名内部类 | 理解 |
虚拟机 | 理解 | |
垃圾回收 | 理解 |
第一节 final和抽象类
1.1. final
final关键字的作用:
- 修饰变量: 被他修饰的变量不可改变。一旦赋了初值,就不能被重新赋值。
final int MAX_SPEED = 120;
- 修饰方法:该方法不可被子类重写。但是可以被重载!
final void study(){}
- 修饰类: 修饰的类不能被继承。比如:Math、String、System等。
final class A {}
【示例1】模拟实现Math类
public final class Maths { private Maths(){ } public static final double PI = 3.14159; public static final double pow(int x,int y){ double result = 1; for(int i=0;i<y;i++){ result *= x; } return result; } public static double abs(double num ){ if(num >=0){ return num; }else{ return -num; } } }
注意:
- 注意:final不能修饰构造方法
- final修饰基本数据类型,值只能赋值一次,后续不能再赋值
- final修饰引用数据类型,final Dog dog = new Dog("亚亚");,不能变化的引用变量的值,可以变化的是对象的属性
【示例2】final关键字修饰引用变量
public class Dog { String name; public Dog(String name){ this.name = name; } } public class Test2 { public static void main(String[] args) { //final int NUM = 5; final int NUM; NUM = 5; // NUM = 6; System.out.println(NUM); //final Dog dog = new Dog(); final Dog dog; dog = new Dog("丫丫"); dog.name = "欧欧"; //dog = new Dog("菲菲"); } }
1.2. 抽象类
- 抽象方法
使用abstract修饰的方法,没有方法体,只有声明。定义的是一种“规范”,就是告诉子类必须要给抽象方法提供具体的实现。
- 抽象类
使用abstract修饰的类。通过abstract方法定义规范,然后要求子类必须定义具体实现。通过抽象类,我们就可以做到严格限制子类的设计,使子类之间更加通用。
问题1:Animal an = new Animal();没有一种动物,名称是Animal,所以Animal不能被实例化
解决:抽象类
问题2:子类必须重写父类的某个方法,否则出现编译错误
解决:抽象方法
【示例3】抽象类和抽象方法
public abstract class Animal { private String color; public Animal() { } public Animal(String color) { this.color = color; } public abstract void shout(); public abstract void eat(); public String toString() { return "Animal{"color='" + color + '\'' + '}'; } } public class Dog extends Animal { private String nickName; public Dog() { } public Dog(String color, String nickName) { super(color); this.nickName = nickName; } public void shout() { System.out.println("旺旺旺"); } public void eat() { } public String toString() { return "Dog{nickName='" + nickName + '\'' +"} " + super.toString(); } }
抽象类的使用要点:
- 有抽象方法的类只能定义成抽象类
- 抽象类不能实例化,即不能用new来实例化抽象类。
- 抽象类必须有构造方法,创建子类对象的时候使用
- 一个抽象类至少0个抽象方法,至多(所有的方法都是抽象方法)个抽象方法
- 子类必须重写父类的抽象方法,不重写就提示编译错误;或者子类也定义为抽象类
- override 重写 implements 实现
父类的方法是抽象的,需要子类实现;父类的方法不是抽象的,子类可以重写
本节作业
- final关键字的作用
- 抽象类的作用
- 抽象类和抽象方法的关键技能点
第二节 接口
接口就是规范,定义的是一组规则,体现了现实世界中“如果你是…则必须能…”的思想。如果你是天使,则必须能飞。如果你是汽车,则必须能跑。接口的本质是契约,就像我们人间的法律一样。制定好后大家都遵守。
2.1. 接口
声明格式:
[访问修饰符] interface 接口名 [extends 父接口1,父接口2…] {
常量定义;
方法定义;
}
定义接口的详细说明:
- 访问修饰符:只能是public或默认。
- 接口名:和类名采用相同命名机制。
- extends:接口可以多继承。
- 常量:接口中的属性只能是常量,总是:public static final 修饰。不写也是。
- 方法:接口中的方法只能是:public abstract。 省略的话,也是public abstract。
要点
- 子类通过implements来实现接口中的规范。
- 接口不能创建实例,但是可用于声明引用变量类型。
- 一个类实现了接口,必须实现接口中所有的方法,并且这些方法只能是public的。
- JDK1.8(不含8)之前,接口中只能包含静态常量、抽象方法,不能有普通属性、构造方法、普通方法。
- JDK1.8(含8)后,接口中包含普通的静态方法、默认方法。
需求:飞机、鸟、超人,导弹参加飞行表演。 思路1:定义一个父类Flyable,让飞机、鸟、超人,导弹都继承该类。不可以,因为继承表达的是is-a的关系,而飞机、鸟、超人不是一种事物, 思路2:使用接口,定义一个接口Flyable,让飞机、鸟、超人,导弹都实现该接口。接口表达是has-a的关系
【示例4】定义Flyable 接口
public interface Flyable { //接口中只有常量,没有变量。常量都是全局静态常量 public static final int NUM = 5; /** * 飞行 * 接口的方法自动采用public abstract修饰。 * 所有方法都是全局抽象方法(<JDK1.8之前) */ public abstract void fly(); }
【示例5】实现Flyable 接口
public class Plane implements Flyable { @Override public void fly() { System.out.println("飞机在平流层平稳的飞行"); } } public class Animal { } public class Bird extends Animal implements Flyable{ @Override public void fly() { System.out.println("鸟儿在空中展翅飞翔"); } } public class SuperMan implements Flyable { @Override public void fly() { System.out.println("超人能飞多高呢?"); } }
【示例6】测试Flyable 接口
public class Test { public static void main(String[] args) { //new Flyable(); //Flyable.num = 6; Flyable plane = new Plane(); showFly(plane); Bird bird = new Bird(); showFly(bird); SuperMan sm = new SuperMan(); showFly(sm); Flyable bird3 = new Plane(); bird3.fly(); Eatable eatable = (Eatable)bird3; eatable.eat(); } public static void showFly(Flyable fly){ fly.fly(); } }
总结1:接口的组成
- 接口和数组、类、抽象类是同一个层次的概念
- 成员变量:接口中所有的变量都使用public static final修饰,都是全局静态常量
- 成员方法: 接口中所有的方法都使用public abstract修饰,都是全局抽象方法
- 构造方法:接口不能new,也没有构造方法
- 接口做方法的形参,实参可以该接口的所有实现类
总结2:接口和多继承
- C++ 多继承
好处 :可以从多个父类继承更多的功能 缺点:不安全 有两个父类Father1,Father2,都有一个方法giveMoney(),子类如果重写了,没有问题,如果子类没有重写,调用giveMoney()是谁的
- Java 单继承
好处:安全 缺点:功能受限 解决方案:既安全,功能又强大,采用接口。接口变相的使Java实现了C++的多继承,又没有C++多继承的不安全性 public class Bird extends Animal implements Flyable,Sleepable 必须先extends 再implements
2.2. 接口应用:内部比较器Comparable
图书类、学生类、新闻类、商品类等是不同的类,可是却都需要有比较的功能,怎么办?共同的父类不可以,可以定义一个比较接口Comparable,其中定义一个实现比较的方法compareTo(Object obj)。让各个类实现该接口即可。Java中就是这么来实现的,下面就来模拟实现一下Comparable接口吧。
【示例7】定义Comparable接口
public interface Comparable { /** * 判断两个对象的大小 * @param obj 另外一个对象 * @return * > 0 正数 大于 * =0 等于 * <0 负数 小于 * */ public int compareTo(Object obj); }
【示例8】实现Comparable接口
public class Book implements Comparable{ private String bookName;//书名 private String author;//作者 private String publisher;//出版社 private double price;// @Override public int compareTo(Object obj) { Book other =(Book)obj; //return this.bookName.compareTo(other.bookName);//"" if(this.price>other.price){ return 1; } else if (this.price < other.price) { return -1; }else{ return 0; } } public Book(String bookName, String author, String publisher, double price){ this.bookName = bookName; this.author = author; this.publisher = publisher; this.price = price; } } public class Person implements Comparable{ private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } @Override public int compareTo(Object obj) { Person other = (Person)obj; return this.age - other.age ; } }
【示例9】测试Comparable接口
public class Test { public static void main(String[] args) { Book book1 = new Book("倚天屠龙记","金庸","清华大学出版社",35); Book book2 = new Book("倚天屠龙记","金庸","清华大学出版社",31); //book1.equals(book2) int result = book1.compareTo(book2); System.out.println(result); Person person1 = new Person("张三",24); Person person2 = new Person("李四",24); result = person1.compareTo(person2); System.out.println(result); } }
2.3. JDK1.8的接口新特征
JDK7及其之前
- 接口的变量都是public final static 全局静态常量,无变化
- 接口中都是抽象abstract方法,不能有static方法(因为abstract和static、final、private不能共存)
JDK1.8及其之后
- 接口中可以添加非抽象方法(static),实现类不能重写,只能通过接口名调用。
- 如果子类中定义了相同名字的静态方法,那就是完全不同的方法了,直接从属于子类。可以通过子类名直接调用
- 接口中可以添加非抽象方法(default),实现类可以重写,只能通过对象名调用
- 实现类可以直接使用default方法,可以重写default方法,但是必须去掉default。(default只能接口中使用)
- 上级接口中default方法的调用:MyInterface.super.method2()
提供非抽象方法的目的
- 为了解决实现该接口的子类代码重复的问题
- 为了既有的成千上万的Java类库的类增加新功能,且不必对这些类重新进行设计。比如只需在Collection接口中增加default Stream<E> stream(),相应的Set和List接口以及它们的子类都包含此的方法,不必为每个子类都重新copy这个方法。
【示例10】JDK8的接口新特征
public interface MyInterface { public static final double PI = 3.14; public abstract void method1(); public static void method2(){ System.out.println("JDK1.8中的非抽象方法有两种,一种是static的"); } public default void method3(){ System.out.println("JDK1.8中的非抽象方法有两种,一种是default的");; } public static void main(String[] args) { MyInterface.method2(); } } public class MyClass implements MyInterface { public void method1() { System.out.println("接口中的抽象方法,子类必须实现"); } public void method3(){ MyInterface.method2(); MyInterface.super.method3(); System.out.println("重写接口的default方法,须将default去掉"); } public static void main(String[] args) { MyInterface mi = new MyClass(); mi.method1(); MyInterface.method2(); mi.method3(); } }
2.4. 面向接口编程
面向接口编程是面向对象编程的一部分。
为什么需要面向接口编程? 软件设计中最难处理的就是需求的复杂变化,需求的变化更多的体现在具体实现上。我们的编程如果围绕具体实现来展开就会陷入”复杂变化”的汪洋大海中,软件也就不能最终实现。我们必须围绕某种稳定的东西开展,才能以静制动,实现规范的高质量的项目。
接口就是规范,就是项目中最稳定的核心! 面向接口编程可以让我们把握住真正核心的东西,使实现复杂多变的需求成为可能。
通过面向接口编程,而不是面向实现类编程,可以大大降低程序模块间的耦合性,提高整个系统的可扩展性和和可维护性。
面向接口编程的概念比接口本身的概念要大得多。设计阶段相对比较困难,在你没有写实现时就要想好接口,接口一变就乱套了,所以设计要比实现难!
老鸟建议
接口语法本身非常简单,但是如何真正使用?这才是大学问。我们需要后面在项目中反复使用,大家才能体会到。 学到此处,能了解基本概念,熟悉基本语法,就是“好学生”了。 请继续努力!再请工作后,闲余时间再看看上面这段话,相信你会有更深的体会。
本节作业
- 接口的语法特点
- 使用接口实现飞行比赛
- 使用Comparable接口让Book类和Person类具备比较的能力
- JDK1.8中接口的新变化
第三节 内部类
内部类是一类特殊的类,指的是定义在一个类的内部的类。实际开发中,为了方便的使用外部类的相关属性和方法,这时候我们通常会定义一个内部类。
一般情况,我们把类定义成独立的单元。有些情况下,我们把一个类放在另一个类的内部定义,称为内部类(innerclasses)。
在Java中内部类主要分为成员非静态成员内部类、静态成员内部类、局部内部类、匿名内部类。
3.1. 非静态成员内部类
作为类的成员存在,和成员变量、成员方法、构造方法、代码块并列。因为是类的成员,所以非静态成员内部类可以使用public、protected 、默认、private修饰,而外部类只能使用public、默认修饰。
【示例11】非静态成员内部类
public class OuterClass { //成员变量 private String name; private int num = 10; //构造方法 public OuterClass() { } public OuterClass(String name, int num) { this.name = name; this.num = num; } //成员方法 public void methodOut(){ System.out.println("methodOut"); } public void methodOut2(){ //外部类不可以直接访问内部的的成员变量和成员方法 //System.out.println(type); //methodInner(); InnerClass ic = new InnerClass(); System.out.println(ic.num);//20 ic.methodInner(); } //内部类 class InnerClass{ //成员变量 private String type; private int num = 20; //构造方法 public InnerClass() { } public InnerClass(String type, int num) { this.type = type; this.num = num; } //成员方法 public void methodInner(){ //内部类可以直接访问外部类的成员变量 System.out.println(name); int num = 30; System.out.println(num); //30 System.out.println(this.num); //20 //内部类如何访问外部类的同名成员变量 System.out.println(OuterClass.this.num);//10 methodOut(); } } } public class Test { public static void main(String[] args) { OuterClass oc = new OuterClass(); oc.methodOut(); oc.methodOut2(); //要创建 非静态成员内部类对象,必须先创建外部类的对象 //OuterClass.InnerClass ic = new OuterClass().new InnerClass(); OuterClass oc2 = new OuterClass(); OuterClass.InnerClass ic =oc2.new InnerClass(); } }
总结1:基本特征
- 内部类可以直接访问外部类的成员
- 外部类不能直接访问内部类的成员,需要先创建对象再通过对象名访问
- 内部类如何访问外部类的同名成员变量:OuterClass.this.num
- 必须先创建外部类的对象,才能创建内部类的对象。非静态成员内部类是属于某个外部类对象的
总结2:更多特征
- 非静态内部类不能有静态方法、静态属性和静态初始化块。
- 外部类的静态方法、静态代码块不能访问非静态内部类,包括不能使用非静态内部类定义变量、创建实例。
注意
内部类只是一个编译时概念,一旦我们编译成功,就会成为完全不同的两个类。对于一个名为Outer的外部类和其内部定义的名为Inner的内部类。编译完成后会出现Outer.class和Outer$Inner.class两个类的字节码文件。所以内部类是相对独立的一种存在,其成员变量/方法名可以和外部类的相同。
3.2. 静态成员内部类
【示例12】静态成员内部类
public class OuterClass { //成员变量 private static String name; private static int num = 10; //构造方法 public OuterClass() { } public OuterClass(String name, int num) { this.name = name; this.num = num; } //成员方法 public static void methodOut(){ System.out.println("methodOut"); InnerClass ic = new InnerClass(); InnerClass.methodInner2(); } public void methodOut2(){ //外部类不可以直接访问内部的的成员变量和成员方法 InnerClass ic = new InnerClass(); System.out.println(ic.num);//20 ic.methodInner(); InnerClass.methodInner2(); } //内部类 static class InnerClass{ //成员变量 private String type; private int num = 20; //构造方法 public InnerClass() { } public InnerClass(String type, int num) { this.type = type; this.num = num; } //成员方法 public void methodInner(){ //静态内部类只能够访问外部类的静态成员 System.out.println(name); int num = 30; System.out.println(num); //30 System.out.println(this.num); //20 //静态内部类如何访问外部类的同名的成员变量 System.out.println(OuterClass.num);//10 methodOut(); } public static void methodInner2(){ } } } public class Test { public static void main(String[] args) { //要创建静态成员内部类对象,不需要先创建外部类的对象 OuterClass.InnerClass ic =new OuterClass.InnerClass(); //需要import com.bjsxt.innerclass2.OuterClass.InnerClass; InnerClass ic2 = new InnerClass(); } }
总结:
- 静态内部类只能够访问外部类的静态成员
- 静态内部类如何访问外部类的同名的成员变量:OuterClass.num
- 静态内部类属于整个外部类的。创建静态内部类的对象,不需先创建外部类的对象
- 外部类可以通过类名直接访问内部类的静态成员,访问非静态成员依旧需要先创建内部类对象。
3.3. 局部内部类
还有一种内部类,它是定义在方法内部的,作用域只限于本方法,称为局部内部类。
局部内部类的的使用主要是用来解决比较复杂的问题,想创建一个类来辅助我们的解决方案,到那时又不希望这个类是公共可用的,所以就产生了局部内部类。局部内部类和成员内部类一样被编译,只是它的作用域发生了改变,它只能在该方法中被使用,出了该方法就会失效。
局部内部类在实际开发中应用很少。
【示例13】局部内部类
public class OuterClass { int num1 = 10; public void method(){ int num2 = 20; class InnerClass{ public void method2(){ num1 = 100; System.out.println(num2); //num2 = 200; } } InnerClass ic = new InnerClass(); ic.method2(); } }
注意:局部内部类访问所在方法的局部变量,要求局部变量必须使用final修饰。JDK1.8中final可以省略,但是编译后仍旧会加final。
本节作业
- 练习非静态成员内部类
- 练习静态成员内部类
- 练习局部内部类
- 扩展:为什么局部内部类访问的所在方法的局部变量要求是final的。参考:https://blog.csdn.net/dazhaoDai/article/details/83097017
第四节 虚拟机和垃圾回收
4.1. 匿名内部类
匿名内部类就是内部类的简化写法,是一种特殊的局部内部类。
前提:存在一个类或者接口,这里的类可以是具体类也可以是抽象类。
本质是什么呢?是一个继承了该类或者实现了该接口的子类匿名对象。
适合那种只需要创建一次对象的类。比如:Java GUI编程、Android编程键盘监听操作等等。比如Java开发中的线程任务Runnble、外部比较器Comparator等。
语法:
new 父类构造器(实参类表) \实现接口 () {
//匿名内部类类体!
}
问题:一个类实现Comparable接口,只能指定一种比较大小的规则,如果需要有更多的比较规则,怎么办?
Comparable:内部比较器 public class Student implements Comparable{} 内部比较器只能有一个,一般采用最经常使用的比较规则
Comparator 外部比较器 可指定多个 不需要Student实现该接口,而是定义专门的类。
【示例14】定义Comparator接口
public interface Comparator{ /** * 比较两个对象的大小 * @param obj1 * @param obj2 * @return * <0 小于 * =0 等于 * >0 大于 */ public int compare(Object obj1, Object obj2); }
【示例15】实现Comparator接口的类
public class BookNameComparator implements Comparator1 { @Override public int compare(Object obj1, Object obj2) { Book book1 = (Book)obj1; Book book2 = (Book)obj2; return book1.getBookName().compareTo(book2.getBookName()); } } public class BookPriceNameComparator implements Comparator1 { @Override public int compare(Object obj1, Object obj2) { Book book1 = (Book)obj1; Book book2 = (Book)obj2; if(book1.getPrice() > book2.getPrice()){ return -1; }else if(book1.getPrice()< book2.getPrice()){ return 1; }else{ return book1.getBookName().compareTo(book2.getBookName()); } } }
如果某个外部比较器只使用一次或者很少的次数,就可以不提供专门的类,而是使用匿名内部类。
【示例16】使用匿名内部类实现外部比较器
public class Test { public static void main(String[] args) { Comparable comp; Comparator comp2; Book book1 = new Book("倚天屠龙记1","金庸1","清华大学出版社",35); Book book2 = new Book("倚天屠龙记5","金庸5","清华大学出版社",35); int result = book1.compareTo(book2); System.out.println(result); Comparator1 cmp1 = new BookNameComparator(); result = cmp1.compare(book1,book2); System.out.println(result); Comparator1 cmp2 = new BookPriceNameComparator(); result = cmp2.compare(book1,book2); System.out.println(result); Comparator1 cmp3 = new Comparator1() { //代码块,每次创建对象的时候执行,并且早于构造方法执行 { System.out.println("--匿名内部类通过代码块完成初始化操作---"); } @Override public int compare(Object obj1, Object obj2) { Book book1 = (Book)obj1; Book book2 = (Book)obj2; return book1.getAuthor().compareTo(book2.getAuthor()); } }; result = cmp3.compare(book1,book2); System.out.println(result); } }
结论:
- 匿名内部类可以实现一个接口,也可以继承一个类(可以是抽象类)。
- 匿名内部类只能实现一个接口,而不是多个
- 必须实现所有的方法,匿名内部类不能是抽象类
- 匿名内部类不可能有构造方法,因为类是匿名的
- 匿名内部类没有访问修饰符
- 如果想实现构造方法的一些初始化功能,可以通过代码块实现
- 如果要访问所在方法的局部变量,该变量需要使用final修饰。(JDK1.8可省略final)
4.2. 内部类的作用和使用场合
内部类的作用:
- 内部类提供了更小的封装。只能让外部类直接访问,不允许同一个包中的其他类直接访问。
- 内部类可以直接访问外部类的私有属性,内部类被当成其外部类的成员。 但外部类不能访问内部类的内部属性。
- 接口只是解决了多重继承的部分问题,而内部类使得多重继承的解决方案变得更加完整。
- 用匿名内部类实现回调功能。我们用通俗讲解就是说在Java中,通常就是编写一个接口,然后你来实现这个接口,然后把这个接口的一个对象作以参数的形式传到另一个程序方法中, 然后通过接口调用你的方法,匿名内部类就可以很好的展现了这一种回调功能
内部类的使用场合:
- 由于内部类提供了更好的封装特性,并且可以很方便的访问外部类的属性。所以,在只为外部类提供服务的情况下可以优先考虑使用内部类。
- 使用内部类间接实现多继承:每个内部类都能独立地继承一个类或者实现某些接口,所以无论外部类是否已经继承了某个类或者实现了某些接口,对于内部类没有任何影响。
4.3. 虚拟机及其构成
虚拟机指通过软件模拟的具有完整硬件系统功能的、运行在一个完全隔离环境中的完整计算机系统。Java虚拟机对字节码进行解释生成对应平台的机器码并执行。Java虚拟机是Java跨平台的重要原因。
Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。主要包括方法区、堆区、虚拟机栈、本地方法栈、程序计数器,其中方法区和堆区为进程的所有子线程共享,其它的为线程独有。
程序计数器
程序计数器是一块较小的内存空间,它的作用可以看作是当前线程所执行的字节码的行号指示器。每个线程都有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,是线程私有内存
Java虚拟机栈和本地方法栈
本地方法栈和虚拟机栈所发挥的作用是非常相似的,区别是虚拟机栈执行java方法,而本地方法栈则为虚拟机使用的Native方法服务,在sun hotspot中已经把两者合二为一了,本地方法栈区也会抛出StackOverFlowError和OutOfMemeoryError异常
Java堆
java堆是垃圾收集器管理的主要区域,因此很多时候也被称为GC堆,Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此区域的唯一目的就是存放对象实例,几乎所有对象的实例都在这分配内存。
方法区
方法区和java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息,常量,静态变量。即时编译器编译后的代码等数据。
堆内存分为三部分
1.年轻代:Young
2.老年代:Tenured
3.永久代 PermGen JDK8中变成元空间MetaSpace
年轻代:分为eden区+两个大小相同的存活期s0、s1;
所有使用关键字new新实例化的对象,一定会在伊甸园区进行保存(除非大对象,伊甸园区容不下);存活区会分为两个相等大小的存活区,存活区保存的一定是在伊甸园区保存好久,并且经过了好几次的小GC还保存下来的活跃对象,那么这个对象将晋升到存活区中。
存活区一定会有两块大小相等的空间。目的是一块存活区未来晋升,另外一块存活区为了对象回收。这两块内存空间一定有一块是空的。
在年轻代中使用的是MinorGC,这种GC采用的是复制算法
老年代
主要接收由年轻代发送过来的对象,一般情况下,经过了数次Minor GC 之后还会保存下来的对象才会进入到老年代。每次进行Minor GC 后存活的对象,年龄都会+1,到了一定年龄后(默认15),进入老年代
如果要保存的对象超过了伊甸园区的大小,此对象也将直接保存在老年代之中;
当老年代内存不足时,将引发 “major GC”,即,“Full GC”。
永久代
方法区:是JVM的一种规范,存放类信息、常量、静态变量、即时编译器编译后的代码等;
永久代:是HotSpot的一种具体实现,实际指的就是方法区,
JDK1.8 之后将最初的永久代内存空间取消了,代之以元空间(metaspace)。
为什么废弃永久代:(This is part of the JRockit and Hotspot convergence effort. JRockit customers do not need to configure the permanent generation (since JRockit does not have a permanent generation) and are accustomed to not configuring the permanent generation. 即:移除永久代是为融合HotSpot JVM与 JRockit VM而做出的努力,因为JRockit没有永久代,不需要配置永久代。) 另外由于永久代内存经常不够用或发生内存泄露,爆出异常java.lang.OutOfMemoryError: PermGen
元空间功能和永久代类似,唯一到的区别是:永久代使用的是JVM的堆内存空间,而元空间使用的是物理内存,直接受到本机的物理内存限制。
注意:JDK6 及以前版本,字符串常量池是放在堆的Perm区的,Perm区是一个类静态的区域,主要存储一些加载类的信息,常量池,方法片段等,默认大小只有4m,一旦常量池中大量使用 intern 是会直接产生java.lang.OutOfMemoryError:PermGen space错误。
JDK7 的版本中,字符串常量池已经从Perm区移到正常的Java Heap区域了。为什么要移动,Perm 区域太小是一个主要原因;
4.4. 垃圾回收
Java引入了垃圾回收机制(Garbage Collection),令C++程序员最头疼的内存管理问题迎刃而解。Java程序员可以将更多的精力放到业务逻辑上而不是内存管理工作上,大大的提高了开发效率。
分代垃圾回收机制,是基于这样一个事实:不同的对象的生命周期是不一样的。因此,不同生命周期的对象可以采取不同的回收算法,以便提高回收效率。我们将对象分为三种状态:年轻代、年老代、持久代。同时,将处于不同状态的对象放到堆中不同的区域。 JVM将堆内存划分为 Eden、Survivor 和 Tenured/Old 空间。
1. 年轻代:所有新生成的对象首先都是放在Eden区。 年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象,对应的是Minor GC,每次 Minor GC 会清理年轻代的内存,算法采用效率较高的复制算法,频繁的操作,但是会浪费内存空间。当“年轻代”区域存放满对象后,就将对象存放到年老代区域。
2. 年老代:在年轻代中经历了N(默认15)次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。年老代对象越来越多,我们就需要启动Major GC和Full GC(全量回收),来一次大扫除,全面清理年轻代区域和年老代区域。
3. 永久代:用于存放静态文件,如Java类、方法等。持久代对垃圾回收没有显著影响。JDK7以前就是“方法区”的一种实现。JDK8以后已经没有“永久代”了,使用metaspace元数据空间和堆替代。
垃圾回收的相关技能点
- 垃圾回收机制主要是回收JVM堆内存里的对象空间。
- 现在的JVM有多种垃圾回收实现算法,表现各异。
- 垃圾回收发生具有不可预知性,程序无法精确控制垃圾回收机制执行。
- 可以将对象的引用变量设置为null,暗示垃圾回收机制可以回收该对象。
- 程序员可以通过System.gc()或者Runtime.getRuntime().gc()来通知系统进行垃圾回收,会有一些效果,但是系统是否进行垃圾回收依然不确定。
- 垃圾回收机制回收任何对象之前,总会先调用它的finalize方法(如果覆盖该方法,让一个新的引用变量重新引用该对象,则会重新激活对象)。
- 永远不要主动调用某个对象的finalize方法,应该交给垃圾回收机制调用。
【示例17】finalize()和gc()
public class Student { @Override protected void finalize() throws Throwable { System.out.println("-----gc-----------"); } public static void main(String[] args) { new Student(); new Student(); new Student(); new Student(); new Student(); //System.gc(); Runtime.getRuntime().gc(); } }
本节作业
- 说明匿名内部类的相关技能点
- 使用匿名内部类实现Comparator比较器
- 内部类的作用和使用场合
- 虚拟机的内存模型
- 堆内存在JDK1.7和JDK1.8的组成结构
- 垃圾回收的主要步骤