Java_面向对象编程-4

目录

主要内容

学习目标

第一节 final和抽象类

1.1. final

1.2. 抽象类

第二节 接口

2.1. 接口

2.2. 接口应用:内部比较器Comparable

2.3. JDK1.8的接口新特征

2.4. 面向接口编程

第三节 内部类

3.1. 非静态成员内部类

3.2. 静态成员内部类

3.3. 局部内部类

第四节 虚拟机和垃圾回收

4.1. 匿名内部类

4.2. 内部类的作用和使用场合

4.3. 虚拟机及其构成

4.4. 垃圾回收


主要内容

  1. final
  2. 抽象类和抽象方法
  3. 接口
  4. 内部类
  5. 虚拟机和垃圾回收

学习目标

节数

知识点

要求

第一节(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;
        }
    }
}

注意:

  1. 注意:final不能修饰构造方法
  2. final修饰基本数据类型,值只能赋值一次,后续不能再赋值
  3. 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();
    }
}

抽象类的使用要点:

  1. 有抽象方法的类只能定义成抽象类
  2. 抽象类不能实例化,即不能用new来实例化抽象类。
  3. 抽象类必须有构造方法,创建子类对象的时候使用
  4. 一个抽象类至少0个抽象方法,至多(所有的方法都是抽象方法)个抽象方法
  5. 子类必须重写父类的抽象方法,不重写就提示编译错误;或者子类也定义为抽象类
  6. override 重写   implements 实现
      父类的方法是抽象的,需要子类实现;父类的方法不是抽象的,子类可以重写

本节作业

  1. final关键字的作用
  2. 抽象类的作用
  3. 抽象类和抽象方法的关键技能点

第二节 接口

        接口就是规范,定义的是一组规则,体现了现实世界中“如果你是…则必须能…”的思想。如果你是天使,则必须能飞。如果你是汽车,则必须能跑。接口的本质是契约,就像我们人间的法律一样。制定好后大家都遵守。

2.1. 接口

声明格式:

[访问修饰符]  interface 接口名   [extends  父接口1,父接口2…]  {

常量定义;                        

方法定义;

}

   定义接口的详细说明:

  1. 访问修饰符:只能是public或默认。
  2. 接口名:和类名采用相同命名机制。
  3. extends接口可以多继承。
  4. 常量:接口中的属性只能是常量,总是:public static final 修饰。不写也是。
  5. 方法:接口中的方法只能是:public abstract。 省略的话,也是public abstract。

要点

  1. 子类通过implements来实现接口中的规范。
  2. 接口不能创建实例,但是可用于声明引用变量类型。
  3. 一个类实现了接口,必须实现接口中所有的方法,并且这些方法只能是public的。
  4. JDK1.8(不含8)之前,接口中只能包含静态常量、抽象方法,不能有普通属性、构造方法、普通方法。
  5. 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. 面向接口编程

        面向接口编程是面向对象编程的一部分。

        为什么需要面向接口编程? 软件设计中最难处理的就是需求的复杂变化,需求的变化更多的体现在具体实现上。我们的编程如果围绕具体实现来展开就会陷入”复杂变化”的汪洋大海中,软件也就不能最终实现。我们必须围绕某种稳定的东西开展,才能以静制动,实现规范的高质量的项目。

        接口就是规范,就是项目中最稳定的核心! 面向接口编程可以让我们把握住真正核心的东西,使实现复杂多变的需求成为可能。

        通过面向接口编程,而不是面向实现类编程,可以大大降低程序模块间的耦合性,提高整个系统的可扩展性和和可维护性。

        面向接口编程的概念比接口本身的概念要大得多。设计阶段相对比较困难,在你没有写实现时就要想好接口,接口一变就乱套了,所以设计要比实现难!

老鸟建议

        接口语法本身非常简单,但是如何真正使用?这才是大学问。我们需要后面在项目中反复使用,大家才能体会到。 学到此处,能了解基本概念,熟悉基本语法,就是“好学生”了。 请继续努力!再请工作后,闲余时间再看看上面这段话,相信你会有更深的体会。 

本节作业

  1. 接口的语法特点
  2. 使用接口实现飞行比赛
  3. 使用Comparable接口让Book类和Person类具备比较的能力
  4. 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:基本特征

  1. 内部类可以直接访问外部类的成员
  2. 外部类不能直接访问内部类的成员,需要先创建对象再通过对象名访问
  3. 内部类如何访问外部类的同名成员变量:OuterClass.this.num
  4. 必须先创建外部类的对象,才能创建内部类的对象。非静态成员内部类是属于某个外部类对象的

总结2:更多特征

  1. 非静态内部类不能有静态方法、静态属性和静态初始化块。
  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();
    }
}

总结:

  1. 静态内部类只能够访问外部类的静态成员
  2. 静态内部类如何访问外部类的同名的成员变量:OuterClass.num
  3. 静态内部类属于整个外部类的。创建静态内部类的对象,不需先创建外部类的对象
  4. 外部类可以通过类名直接访问内部类的静态成员,访问非静态成员依旧需要先创建内部类对象。

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。

本节作业

  1. 练习非静态成员内部类
  2. 练习静态成员内部类
  3. 练习局部内部类
  4. 扩展:为什么局部内部类访问的所在方法的局部变量要求是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);
    }
}

结论:

  1. 匿名内部类可以实现一个接口,也可以继承一个类(可以是抽象类)。
  2. 匿名内部类只能实现一个接口,而不是多个
  3. 必须实现所有的方法,匿名内部类不能是抽象类
  4. 匿名内部类不可能有构造方法,因为类是匿名的
  5. 匿名内部类没有访问修饰符
  6. 如果想实现构造方法的一些初始化功能,可以通过代码块实现
  7. 如果要访问所在方法的局部变量,该变量需要使用final修饰。(JDK1.8可省略final)

4.2. 内部类的作用和使用场合

内部类的作用:

  1. 内部类提供了更小的封装。只能让外部类直接访问,不允许同一个包中的其他类直接访问。
  2. 内部类可以直接访问外部类的私有属性,内部类被当成其外部类的成员。 但外部类不能访问内部类的内部属性。
  3. 接口只是解决了多重继承的部分问题,而内部类使得多重继承的解决方案变得更加完整。
  4. 用匿名内部类实现回调功能。我们用通俗讲解就是说在Java中,通常就是编写一个接口,然后你来实现这个接口,然后把这个接口的一个对象作以参数的形式传到另一个程序方法中, 然后通过接口调用你的方法,匿名内部类就可以很好的展现了这一种回调功能

内部类的使用场合:

  1. 由于内部类提供了更好的封装特性,并且可以很方便的访问外部类的属性。所以,在只为外部类提供服务的情况下可以优先考虑使用内部类。
  2. 使用内部类间接实现多继承:每个内部类都能独立地继承一个类或者实现某些接口,所以无论外部类是否已经继承了某个类或者实现了某些接口,对于内部类没有任何影响。

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元数据空间和堆替代。

垃圾回收的相关技能点

  1. 垃圾回收机制主要是回收JVM堆内存里的对象空间。
  2. 现在的JVM有多种垃圾回收实现算法,表现各异。
  3. 垃圾回收发生具有不可预知性,程序无法精确控制垃圾回收机制执行。
  4. 可以将对象的引用变量设置为null,暗示垃圾回收机制可以回收该对象。
  5. 程序员可以通过System.gc()或者Runtime.getRuntime().gc()来通知系统进行垃圾回收,会有一些效果,但是系统是否进行垃圾回收依然不确定。
  6. 垃圾回收机制回收任何对象之前,总会先调用它的finalize方法(如果覆盖该方法,让一个新的引用变量重新引用该对象,则会重新激活对象)。
  7. 永远不要主动调用某个对象的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();
    }
}

本节作业

  1. 说明匿名内部类的相关技能点
  2. 使用匿名内部类实现Comparator比较器
  3. 内部类的作用和使用场合
  4. 虚拟机的内存模型
  5. 堆内存在JDK1.7和JDK1.8的组成结构
  6. 垃圾回收的主要步骤

猜你喜欢

转载自blog.csdn.net/weixin_45859844/article/details/120516241