一、 内部类

1. 分类

05_面向对象E_内部类和垃圾回收机制 - 图1

2. 普通内部类

  • 可以使用四个访问修饰符
  • 内部类中可以直接使用外部类的成员变量
  • 内部类可以直接调用外部类的成员方法
  • 在内部类方法中 通过外部类类名.this.成员变量名区分内部类和外部类的同名成员变量
  • 外部类是不能直接使用内部类的成员变量和成员方法的
  • 内部类可以实现很好的隐藏,当一个类仅仅为另一个类服务,就可以将类作为内部类
  • 内部类可以帮我我们区分因继承和实现接口所造成的方法不可区分的问题

总结1:基本特征

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

总结2:更多特征

  • 非静态内部类不能有静态方法、静态属性和静态初始化块。
  • 外部类的静态方法、静态代码块不能访问非静态内部类,包括不能使用非静态内部类定义变量、创建实例。 ```java import javax.print.attribute.standard.MediaSize;

public class OuterClass implements InterA{

  1. // 成员变量
  2. private String name ="OuterClass name";
  3. // 成员方法
  4. public void showName(){
  5. System.out.println(name);
  6. }
  7. public void testA(){
  8. InnerClass ic =new InnerClass();
  9. System.out.println(ic.username);
  10. ic.showUsername();
  11. eat();
  12. ic.eat();
  13. }
  14. // 构造方法
  15. public OuterClass(){
  16. }
  17. @Override
  18. public void eat() {
  19. }
  20. // 成员内部类
  21. private class InnerClass extends Person{
  22. //成员变量
  23. String username="innerclass username";
  24. String name ="InnerClass name";
  25. //成员方法
  26. public void showUsername(){
  27. System.out.println(username);
  28. /* System.out.println(name);
  29. System.out.println(OuterClass.this.name);
  30. showName();*/
  31. }
  32. // 构造方法
  33. public InnerClass(){
  34. }
  35. }

}

interface InterA{ void eat(); }

class Person { public void eat(){ System.out.println(“吃饭”); } }

<a name="19Zcr"></a>
### 3.  静态内部类

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

    // 成员变量
    private static String name ="OuterClass name";
    // 成员方法
    public static void showName(){
        System.out.println(name);

    }
    // 构造方法
    public OuterClass(){

    }

    public void testa() {
        InnerClass ic =new InnerClass();
        System.out.println(ic.username);
        ic.showUsername();
    }

    // 成员内部类
    static class InnerClass {
        //成员变量
        String username="innerclass username";

        //成员方法
        public void showUsername(){
            System.out.println(username);
            System.out.println(name);
            showName();
        }
        // 构造方法
        public InnerClass(){
        }
    }


    abstract class InnterClassA{

    }

    interface  InterA{

    }
}

4. 局部内部类

还有一种内部类,它是定义在方法内部的,作用域只限于本方法,称为局部内部类。
局部内部类的的使用主要是用来解决比较复杂的问题,想创建一个类来辅助我们的解决方案,到那时又不希望这个类是公共可用的,所以就产生了局部内部类。局部内部类和成员内部类一样被编译,只是它的作用域发生了改变,它只能在该方法中被使用,出了该方法就会失效。
注意:局部内部类访问所在方法的局部变量,要求局部变量必须使用final修饰。JDK1.8中final可以省略,但是编译后仍旧会加final。

public class Test1 {
    public static void main(String[] args) {
        final  int i=10;



        class A{
            String name;
            public void showName(){
                System.out.println(i);

            }
            public A(){

            }
        }

        A a =new A();
    }
    public static void method(){

    }

5. 匿名内部类

语法:

new 父类构造器(实参类表) \实现接口 () {
//匿名内部类类体!
}

匿名类是一个继承了该类或者实现了该接口的子类匿名对象。适合那种只需要创建一次对象的类。比如:Java GUI编程、Android编程键盘监听操作等等。比如Java开发中的线程任务Runnble、外部比较器Comparator等。

  • 匿名内部类可以帮助我们快速的为接口或者抽象类产生一个实现对象/子类对象
  • 匿名内部类没有构造方法
  • 匿名内部类自定义的方法和属性的getset方法往往是无法调用的
  • 只能使用代码块对成员变量进行初始化
  • 匿名内部类作为局部内部类的一种,是方法内的内部类

注意

  • 匿名内部类可以实现一个接口,也可以继承一个类(可以是抽象类)。
  • 匿名内部类只能实现一个接口,而不是多个
  • 必须实现所有的方法,匿名内部类不能是抽象类
  • 匿名内部类不可能有构造方法,因为类是匿名的 ```java public class Test1 { public static void main(String[] args) {

     int value=10;
    
     A a=new A(){
         private String name;
         {
             String[] names={"Tom","Jhon","Mark","King","Allen","Scott"};
             int index =(int)(Math.random()*names.length);
             name=names[index];
         }
         public void methodA(){
    
         }
         public void setName(String name){
             this.name=name;
         }
    
         @Override
         public int getNum(int i) {
             System.out.println(name);
             System.out.println(value);
    
             methodA();
             return (int)(Math.random()*i);
         }
    
     };
    
    System.out.println(a.getNum(10));


    B b =new B(){
        @Override
        public int getNum(int i) {
            return 10;
        }
    };
}

}

interface A{ /**

 * 根据范围返回随机正整数
 * @param i 范围
 * @return 随机整数,不包含给定的范围值
 */
int getNum(int i);

} abstract class B{ public abstract int getNum(int i); } ```

二、 垃圾回收机制

1. JVM虚拟机构成

计数器区、本地方法区、栈内存、堆内存、方法区(永久代,元空间)

2. 堆内存结构

新生代+年老代+永久代(方法区,非堆内存)

2.1 年轻代:分为eden区+两个大小相同的存活期s0、s1;

所有使用关键字new新实例化的对象,一定会在伊甸园区进行保存(除非大对象,伊甸园区容不下);存活区会分为两个相等大小的存活区,存活区保存的一定是在伊甸园区保存好久,并且经过了好几次的小GC还保存下来的活跃对象,那么这个对象将晋升到存活区中。
存活区一定会有两块大小相等的空间。目的是一块存活区未来晋升,另外一块存活区为了对象回收。这两块内存空间一定有一块是空的。
在年轻代中使用的是MinorGC,这种GC采用的是复制算法

2.2 老年代

主要接收由年轻代发送过来的对象,一般情况下,经过了数次Minor GC 之后还会保存下来的对象才会进入到老年代。每次进行Minor GC 后存活的对象,年龄都会+1,到了一定年龄后(默认15),进入老年代。如果要保存的对象超过了伊甸园区的大小,此对象也将直接保存在老年代之中;当老年代内存不足时,将引发 “major GC”,即,“Full GC”。

2.3 永久代

永久代:是HotSpot的一种具体实现,实际指的就是方法区,JDK1.8 之后将最初的永久代内存空间取消了,代之以元空间(metaspace)。
为什么废弃永久代:除永久代是为融合HotSpot JVM与 JRockit VM而做出的努力,因为JRockit没有永久代,不需要配置永久代。另外由于永久代内存经常不够用或发生内存泄露,爆出异常java.lang.OutOfMemoryError: PermGen。
元空间功能和永久代类似,唯一到的区别是:永久代使用的是JVM的堆内存空间,而元空间使用的是物理内存,直接受到本机的物理内存限制。

3. 垃圾回收判别方式

3.1 引用计数法

每个对象都有一个计数器,当这个对象被一个变量或者另一个对象引用一次,该计数器加一;若该引用失效,则计数器减一。当计数器为0时,就认为该对象是无效对象。

3.2 可达性分析法(根搜索法)

所有和GC Roots直接或间接关联的对象都是有效对象,和GC Roots没有关联的对象就是无效对象。算法的基本思路就是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为“引用链(Reference Chain)”。当一个对象到GC Roots没有任何引用链相连(用图论的话来说,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的,可以被回收的。
即使在可达性分析算法中不可达的对象,也并非是“非死不可”的,这时候它们暂时处于“缓刑”阶段,要真正宣告一个对象死亡,至少要经历两次标记过程。
如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,那它将会被第一次标记并且进行一次筛选,筛选的条件是次对象是否有必要执行finalize()方法。当对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过时,虚拟机将这两种情况都视为“没有必要执行”。
如果这个对象被判定为有必要执行finalize()方法,那么这个对象将会被放置在一个叫做F-Queue的队列之中,并在稍后由一个虚拟机自动建立的、低优先级的Finalizer线程去执行它。
inalize()方法是对象逃脱死亡名义的最后一次机会,稍候GC将对F-Queue中的对象进行第二次小规模标记。如果对象要在finalize()中成功拯救自己–只要重新与引用链上的任何一个对象建立关联即可。譬如把自己(this关键字)赋值给某个类变量或者对象的成员变量,那在第二次标记时它将被移除出“即将回收”的集合。如果对象这时候还没有逃脱,那基本上它就真的被回收了。

4. 垃圾回收算法

4.1 标记-清除

是最基础的一种收集算法。分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。标记过程就是上面可达性分析算法中所讲的二次标记过程。
缺点:
(1) 效率问题:标记和清除的两个过程效率都不高;
(2) 空间问题:标记清除后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后需要分配较大对象时,无法找到足够的连续内存而不得不提前出发另一次垃圾收集动作;

4.2 复制算法

为了解决上面算法的效率问题,复制算法出现。它将可用内存按容量划分为 大小相等的两块,每次只使用其中的一块。当这一块的内存使用完了,就将还存活的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。
复制算法的 优点:
(1)每次都是对 整个半区进行内存回收,实现简单、运行也高效;
(2)在那块使用内存上进行内存分配时,不用考虑内存碎片的问题,只要移动堆顶指针,按顺序分配内存即可;
缺点:
将内存缩小为原来的一半,代价较高。

4.3 标记-整理算法

复制算法如果在对象存活率较高时,就需要进行较多次的复制操作,效率也会变低。而对于老年代中的对象,一般存活率都较高,因此需要选用其他收集算法:标记 - 整理算法。标记过程仍然与“标记-清除”算法中一样,但是在标记完成后并不直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。

4.4 分代收集算法

当前商业虚拟机都采用这个“分代收集”算法(Generation Collection),它根据对象存活周期的不同将内存划分为几块,一般是把java堆分为新生代和老年代,根据各个年代的特点选用不同的收集算法。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,因此可以选用“复制算法”,此时只需要付出少量存活对象的复制成本即可;对于老年代,因为对象存活率较高、也没有额外空间为期分配担保,就必须使用“标记-清除”或“标记-整理”算法来进行回收。

5. 分代垃圾回收机制

不同的对象的生命周期是不一样的。因此,不同生命周期的对象可以采取不同的回收算法,以便提高回收效率。我们将对象分为三种状态:年轻代、年老代、持久代。同时,将处于不同状态的对象放到堆中不同的区域。 JVM将堆内存划分为 Eden、Survivor 和 Tenured/Old 空间。

1. 年轻代

所有新生成的对象首先都是放在Eden区。 年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象,对应的是Minor GC,每次 Minor GC 会清理年轻代的内存,算法采用效率较高的复制算法,频繁的操作,但是会浪费内存空间。当“年轻代”区域存放满对象后,就将对象存放到年老代区域。

2. 年老代

在年轻代中经历了N(默认15)次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。年老代对象越来越多,我们就需要启动Major GC和Full GC(全量回收),来一次大扫除,全面清理年轻代区域和年老代区域。

3. 永久代

用于存放静态文件,如Java类、方法等。持久代对垃圾回收没有显著影响。JDK7以前就是“方法区”的一种实现。JDK8以后已经没有“永久代”了,使用metaspace元数据空间和堆替代。
**