Java - 程序员面试笔记记录 & 实现 - Part4

时间:2024-07-10 08:50:34

5.1 JVM 内存划分

Class 文件:Java 程序编译后生成的中间代码。

类装载子系统:负责把 class 文件装载到内存中,供虚拟机执行。

方法区:存储被虚拟机加载的类信息、常量、静态变量、编译器编译后的代码。在类加载器加载 class 文件时,这些信息会被提取出来,此区域线程共享。1.6 之前字符串常量也存放在这个区域,1.6 之后移到堆区了。

堆:虚拟机启动时创建的被线程共享的区域。主要用于存放对象的实力。

虚拟机栈:栈是线程私有的空间,线程结束后栈空间就被回收。主要用来实现方法的调用和执行,方法被执行时,创建一个栈帧来存储这个方法的局部变量、操作栈、动态链接、出口等信息。

程序计数器:线程私有,可以看作当前线程执行的字节码的行号指示器,解释器的工作原理就是通过改变这个计数器的值来确定下一条需要被执行的字节码指令。

本地方法栈:本地方法栈和虚拟机栈作用类似,为虚拟机使用到的 Native 方法服务。

执行引擎:负责执行字节码。

垃圾回收器:回收不再使用的内存。

5.2 运行时内存

根据对象的生命周期把对象划分为不同的种类(年轻代、老年代和永久代)并分别进行内存回收。即把堆分为多个子堆,每个子堆视为一代,如果一个对象经过多次收集仍然存活,可以放到高一级的堆里。

1. 年轻代:分为3个部分,一个 Eden 和两个相同的 Survivor 区。Eden 存放新建的对象。Survivor 始终有一个为空。

2. 老年代:存储生命周期较长的对象,超大的对象。

3. 永久代:存放代码,字符串常量池,静态变量等。

5.2 补充 - fullGC

fullGC 清理整个堆空间。会造成很大的系统资源开销。常见的几种触发场景:

1. 调用 System.gc

2. 老年代的空间不足

3. 永久代满

注意 Java8 已经移除了永久代,新加了一个元数据区(native),大部分元数据都是在本地内存中分配。

5.2 补充 - 元空间

JDK 1.8 开始彻底把永久代从 JVM 移除了,把类的元数据放到本地化的元数据堆中(Metaspace)。其中,类与类加载器有相同的生命周期。

5.3 垃圾回收

常见的垃圾回收算法

1. 引用计数算法:堆中每个对象都有一个引用计数器。但无法解决互相引用的问题。

2. 追踪回收算法:利用JVM维护的对象引用图,从根节点开始遍历对象的应用图,遍历结束时未被标记的对象就是已不再使用的对象、

3. 压缩回收:把堆中活动的对象移动到堆中一段。

4. 拷贝回收:将堆分成两个大小相同的区域,任何时刻只有其中一个区域被使用。

5. 按代回收算法

回收器

1. 串行垃圾回收

主要为单线程环境设计,运行时暂停应用线程。在年轻代和老年代中都使用了一个单独的线程来实现垃圾的回收。年轻代使用拷贝回收,老年代或永久代使用标记、清扫、压缩算法。

2. 并行垃圾回收

使用多线程进行回收,仍会暂停所有的应用程序。

3. 并行标记清理回收 CMS

年轻代中使用拷贝算法。老年代中使用最大并发量的标记清除算法。

4. G1

面向服务器的垃圾回收器。提供了可以预测暂停时间的功能。

5.4 类加载器

系统类、扩展类、应用类各有各的加载器。

加载步骤:装载 -> 链接(检查、准备、解析)-> 初始化

5.4 补充 - 内存泄漏

内存泄漏主要存在两种情况:一个是在堆中申请的空间没有释放;一个是对象已经不再使用,但还在内存中白村。JVM 会解决第一种,所以 Java 中的泄漏一般是指第二种。

1. 静态集合类,如 HashMap 和 Vector,如果为静态容器,则容器中的对象在程序结束之前不能释放。

2. 链接,未 close 的链接会导致对象无法回收。

3. 监听器,释放对象时没有相应的删除监听器。

4. 变量不合理的作用域

5. 单例模式可能会造成内存泄漏                                            

5.4 补充 - 单例模式

单例模式(Singleton Pattern)是一种常用的软件设计模式,用于限制一个类只有一个实例,并提供一个全局访问点来获取这个实例。

单例模式的特点:

  1. 唯一性:单例类只允许创建一个实例对象。

  2. 全局访问点:提供了一个全局的访问点来获取这个唯一的实例。

  3. 线程安全:在多线程环境中,单例模式需要确保只有一个线程能够创建实例。

  4. 延迟初始化:单例实例通常在第一次被使用时才创建,这样可以提高程序的启动速度。

单例模式的作用:

  1. 控制资源:当某个类的对象需要严格控制数量,或者对象的创建和销毁需要额外的资源管理时,使用单例模式可以确保资源的有效利用。

  2. 节省内存:通过确保只存在一个实例,可以减少内存占用,特别是当对象较大或创建成本较高时。

  3. 统一管理:单例模式提供了一个集中管理和配置类实例的地方,便于维护和扩展。

  4. 避免不一致:在多线程环境中,如果多个线程尝试创建同一个类的实例,可能会导致数据不一致。单例模式通过确保只有一个实例来避免这种情况。

public class Singleton {
    // volatile 确保多线程下可见性和禁止指令重排
    private static volatile Singleton instance;

    // 私有构造函数,防止外部通过new创建实例
    private Singleton() {
    }

    // 公有的静态方法,提供全局访问点
    public static Singleton getInstance() {
        if (instance == null) { // 首检
            synchronized (Singleton.class) { // 锁住类对象
                if (instance == null) { // 二检
                    instance = new Singleton(); // 创建实例
                }
            }
        }
        return instance;
    }
}

 6.1 设计原则

单一职责:每个类应该只负责一项工作。

开放封闭:对扩展开放,对修改封闭。

里氏替换原则:对父类的调用同样适用于子类。

依赖倒置:高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象。

接口隔离:不应该强迫客户端实现一个它用不上的接口。应该使用多个接口而不是一个单一接口。

合成复用:尽量使用对象的组合而非继承来达到复用的目的。

迪米特法则:最少知识原则,一个模块或对象应该尽可能少的和其他模块发生相互作用。

6.3 工厂模式

专门负责实例化有大量公共接口的类,可以动态决定将哪一个类实例化。

简单工厂:根据提供给它的参数,返回一个实例。

// 产品接口
interface Product {
    void use();
}

// 实现产品接口的具体产品
class ConcreteProductA implements Product {
    public void use() {
        System.out.println("Using ConcreteProductA");
    }
}

class ConcreteProductB implements Product {
    public void use() {
        System.out.println("Using ConcreteProductB");
    }
}

// 简单工厂类
class SimpleFactory {
    public static Product createProduct(String type) {
        if (type == null) {
            return null;
        }
        if ("A".equalsIgnoreCase(type)) {
            return new ConcreteProductA();
        } else if ("B".equalsIgnoreCase(type)) {
            return new ConcreteProductB();
        }
        return null;
    }
}

// 客户端代码
class Client {
    public static void main(String[] args) {
        Product product = SimpleFactory.createProduct("A");
        product.use();
    }
}

工厂方法模式:定义一个用于创建产品对象的工厂的接口,实际创建工作推迟到工厂接口的子类中。

// 产品接口
interface Product {
    void use();
}

// 具体产品
class ConcreteProductA implements Product {
    public void use() {
        System.out.println("Using ConcreteProductA");
    }
}

class ConcreteProductB implements Product {
    public void use() {
        System.out.println("Using ConcreteProductB");
    }
}

// 工厂接口
interface Factory {
    Product createProduct();
}

// 具体工厂
class ConcreteFactoryA implements Factory {
    public Product createProduct() {
        return new ConcreteProductA();
    }
}

class ConcreteFactoryB implements Factory {
    public Product createProduct() {
        return new ConcreteProductB();
    }
}

// 客户端代码
class Client {
    public static void main(String[] args) {
        Factory factory = new ConcreteFactoryA();
        Product product = factory.createProduct();
        product.use();
    }
}

抽象工厂:向客户端提供一个接口,使客户端在不必指定产品的具体的情况下,创建多个产品族中的产品对象。

// 产品接口
interface ProductA {
    void use();
}

interface ProductB {
    void use();
}

// 具体产品
class ConcreteProductA1 implements ProductA {
    public void use() {
        System.out.println("Using ConcreteProductA1");
    }
}

class ConcreteProductB1 implements ProductB {
    public void use() {
        System.out.println("Using ConcreteProductB1");
    }
}

// 抽象工厂接口
interface AbstractFactory {
    ProductA createProductA();
    ProductB createProductB();
}

// 具体工厂
class ConcreteFactory1 implements AbstractFactory {
    public ProductA createProductA() {
        return new ConcreteProductA1();
    }

    public ProductB createProductB() {
        return new ConcreteProductB1();
    }
}

// 客户端代码
class Client {
    public static void main(String[] args) {
        AbstractFactory factory = new ConcreteFactory1();
        ProductA productA = factory.createProductA();
        ProductB productB = factory.createProductB();
        productA.use();
        productB.use();
    }
}

6.4 适配器模式

把一个类的接口转换成客户端所希望的另一种接口。常用于适配第三方库、迁移等场景。

// 目标接口,定义了客户端使用的接口
interface Target {
    void request();
}

// 适配者类,即第三方库的类
class ThirdPartyLibrary {
    public void specificRequest() {
        System.out.println("ThirdPartyLibrary specific request");
    }
}

// 适配器类,实现目标接口并包装了适配者对象
class Adapter implements Target {
    private ThirdPartyLibrary adaptee;

    public Adapter(ThirdPartyLibrary adaptee) {
        this.adaptee = adaptee;
    }

    @Override
    public void request() {
        // 调用适配者的方法,将其转换为目标接口期望的形式
        adaptee.specificRequest();
    }
}

// 客户端代码
class Client {
    public static void main(String[] args) {
        ThirdPartyLibrary library = new ThirdPartyLibrary();
        Target adapter = new Adapter(library);

        // 客户端通过目标接口与适配器交互
        adapter.request();
    }
}

6.5 观察者模式

即发布 / 订阅模式,对象通过一个方法使得本身变得可观察,当可观察的对象修改时,会将消息发送给已注册的观察者。

// 观察者接口
interface Observer {
    void update();
}

// 主题接口
interface Subject {
    void registerObserver(Observer o);
    void removeObserver(Observer o);
    void notifyObservers();
}

// 具体主题
class ConcreteSubject implements Subject {
    private List<Observer> observers = new ArrayList<>();
    private int state;

    @Override
    public void registerObserver(Observer o) {
        observers.add(o);
    }

    @Override
    public void removeObserver(Observer o) {
        int index = observers.indexOf(o);
        if (index >= 0) {
            observers.remove(index);
        }
    }

    @Override
    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update();
        }
    }

    public void setState(int state) {
        this.state = state;
        notifyObservers();
    }

    public int getState() {
        return state;
    }
}

// 具体观察者
class ConcreteObserver implements Observer {
    private ConcreteSubject subject;

    public ConcreteObserver(ConcreteSubject subject) {
        this.subject = subject;
        subject.registerObserver(this);
    }

    @Override
    public void update() {
        System.out.println("Observer: " + subject.getState());
    }
}

// 客户端代码
class Client {
    public static void main(String[] args) {
        ConcreteSubject subject = new ConcreteSubject();
        new ConcreteObserver(subject); // 注册观察者

        // 改变主题状态,观察者将收到通知
        subject.setState(123);
    }
}