学习日志01 java

java要多多练习!:

1 JVM是什么

JVM 内存即 Java 虚拟机内存,是 Java 程序运行时用于存储数据的区域 。Java 程序在运行时,JVM 会在计算机物理内存上划分出不同的内存区域来管理数据,主要分为以下几部分:

堆内存(Heap Memory)

  • 作用:是线程共享区域 ,用于存放对象实例(使用new关键字创建的对象)和数组,几乎所有对象实例都在此分配内存。类加载器读取类文件后,类的元数据(如类、方法和常量等信息 )也会存放在堆内存。
  • 结构:可划分为新生代和老年代。新生代又可细分为 Eden 区、From Survivor 区和 To Survivor 区 。不同区域承担不同的对象存储和垃圾回收任务,比如新生代存放新创建对象,经过多次垃圾回收仍存活的对象会进入老年代。
  • 特点:是 JVM 中最大的内存区域,大小可调节,存储特点为先进先出、后进后出 ,能动态分配内存大小,但存取速度相对较慢,也是垃圾收集器(GC)主要的回收区域。

栈内存(Stack Memory)

  • Java 虚拟机栈线程私有的区域 。创建线程时会为该线程创建一个 Java 虚拟机栈,用于存放方法调用的栈帧。每个方法对应一个栈帧,栈帧包含局部变量表(存储方法执行过程中的局部变量,包括基本数据类型和对象引用 ,大小在编译期确定且在方法生命周期内不变 )、操作数栈(执行方法时进行计算的临时存储区域 ,通过压栈和出栈操作进行数据运算 )、动态连接(每个栈帧包含指向运行时常量池中该栈帧所属方法的引用 ,用于支持方法调用过程中的动态连接 )、方法出口(存储方法调用结束后的返回地址 ,方法执行完毕后程序依此返回到正确调用点继续执行 )。
  • 本地方法栈:也是线程私有的区域,作用与 Java 虚拟机栈类似,不过是为支持 Java 程序调用本地方法(通常由其他语言如 C 或 C++ 编写 )服务 ,负责处理本地方法的调用和执行,遵循后进先出(LIFO)原则。

方法区(Method Area)

  • 作用:各个线程共享的内存区域 ,用于存储虚拟机加载的类信息、常量、静态变量等 。非线程隔离!在 JDK 8 之前,该区域被称为永久代(PermGen 区) ,是一片连续的堆空间;JDK 8 之后,永久代被移除,取而代之的是元空间(Metaspace) ,元空间位于本地内存中,其大小不再受MaxPermSize限制,而是由系统实际可用空间决定,这使得类元数据的存储更灵活。

此外,还有程序计数器(记录当前线程执行程序的位置 ,是线程私有内存区域 )直接内存(不是 JVM 规范定义的内存区域,NIO 可调用 Native 方法直接分配堆外内存,即本机内存 ,不影响堆内存大小 ) 。合理管理和配置 JVM 内存,对 Java 程序的性能和稳定性至关重要 。

2 Java ThreadLocal

ThreadLocal 是 Java 中位于 java.lang 包下的一个类 ,用于提供线程局部变量。以下是其相关介绍:

作用

为每个使用该变量的线程提供独立的变量副本,各线程可独立修改自己的副本,不影响其他线程副本。在多线程环境下,能避免线程安全问题,简化多线程编程。比如在多线程 Web 应用里,可给每个线程创建独立数据库连接,防止多个线程共享连接引发并发问题 ;也可用于事务管理,存储每个线程的事务状态等信息。

原理

  • 每个线程都有一个 ThreadLocalMapThreadLocal 类的内部静态类 )。当线程调用 ThreadLocal 的 set(T value) 方法设置变量时,以 ThreadLocal 实例为键、变量值为值,存于该线程的 ThreadLocalMap 中;调用 get() 方法时,从自身的 ThreadLocalMap 中取值。
  • ThreadLocalMap 中 Entry 继承自 WeakReference>,即 Entry 的键(ThreadLocal 实例 )是弱引用。若 ThreadLocal 实例无强引用指向,垃圾回收时会被回收,但 Entry 中的值不会,可能导致内存泄漏。所以使用完要及时调用 remove() 方法清理。

接口方法

  • T get() :返回当前线程中该 ThreadLocal 变量副本的值。若当前线程无对应值,先调用 initialValue() 方法初始化并返回。
  • void set(T value) :将当前线程中该 ThreadLocal 变量副本的值设为指定值 。
  • void remove() :移除当前线程中该 ThreadLocal 变量副本的值。后续该线程读取时,若未重新设置值,会重新调用 initialValue() 方法初始化。
  • protected T initialValue() :返回当前线程中该 ThreadLocal 变量的初始值。线程首次调用 get() 方法(且之前未调用 set(T value) 方法 )时执行,默认返回 null ,可被子类重写以指定初始值。还可通过静态方法 withInitial(Supplier supplier) 创建 ThreadLocal 变量并指定初始值。

注意事项

  • 内存泄漏ThreadLocal 使用不当可能引发内存泄漏,及时调用 remove() 方法很重要。也可将 ThreadLocal 设为全局变量(如 public static 修饰 ),避免被回收从而防止内存泄漏。
  • 线程池场景:线程池中的线程会复用,使用 ThreadLocal 时若前一个任务设置的值未清理,后续任务可能使用到脏数据。所以在线程池场景,任务开始时初始化 ThreadLocal ,结束时调用 remove() 方法清理。 此外,InheritableThreadLocal 是 ThreadLocal 的子类,可实现子线程继承父线程的 ThreadLocal 变量副本;还有阿里开源的 TransmittableThreadLocal ,可解决线程池等场景下 InheritableThreadLocal 传递值不符合预期的问题。

3 final

在Java中,final变量(成员变量)必须在以下位置之一进行初始化
1. 声明时直接初始化
2. 在构造方法中初始化
3. 在初始化块中初始化

如果没有初始化会造成编译错误。非final的成员变量j会被自动初始化为0。

4 object类的基本方法

在 Java 里,Object 类是所有类的根类,这意味着每个类都会继承 Object 类的方法。下面是 Object 类的一些基本方法:

1. toString() 方法

此方法会返回对象的字符串表示。默认情况下,它返回的是类名加上 @ 符号以及对象的哈希码。通常会重写这个方法,从而返回更具意义的字符串。

class MyClass {
    private int value;

    public MyClass(int value) {
        this.value = value;
    }

    @Override
    public String toString() {
        return "MyClass{value=" + value + "}";
    }
}

public class Main {
    public static void main(String[] args) {
        MyClass obj = new MyClass(42);
        System.out.println(obj.toString());
    }
}

2. equals() 方法

该方法用于比较两个对象是否相等。默认情况下,它比较的是对象的引用,也就是判断两个对象是否为同一个实例。不过,许多类都会重写这个方法,以便根据对象的内容进行比较。

class MyClass {
    private int value;

    public MyClass(int value) {
        this.value = value;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        MyClass myClass = (MyClass) o;
        return value == myClass.value;
    }
}

public class Main {
    public static void main(String[] args) {
        MyClass obj1 = new MyClass(42);
        MyClass obj2 = new MyClass(42);
        System.out.println(obj1.equals(obj2));
    }
}

3. hashCode() 方法

此方法返回对象的哈希码值。在将对象存储到哈希表(像 HashMap 或 HashSet)时,这个方法会被使用。当重写 equals() 方法时,通常也需要重写 hashCode() 方法,以保证相等的对象具有相同的哈希码。

import java.util.Objects;

class MyClass {
    private int value;

    public MyClass(int value) {
        this.value = value;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        MyClass myClass = (MyClass) o;
        return value == myClass.value;
    }

    @Override
    public int hashCode() {
        return Objects.hash(value);
    }
}

public class Main {
    public static void main(String[] args) {
        MyClass obj = new MyClass(42);
        System.out.println(obj.hashCode());
    }
}

4. getClass() 方法

该方法返回对象的运行时类。它返回的是一个 Class 对象,这个对象包含了类的各种信息。

class MyClass {}

public class Main {
    public static void main(String[] args) {
        MyClass obj = new MyClass();
        Class clazz = obj.getClass();
        System.out.println(clazz.getName());
    }
}

5. clone() 方法

此方法用于创建并返回对象的一个副本。不过,要使用这个方法,类必须实现 Cloneable 接口,否则会抛出 CloneNotSupportedException 异常。

class MyClass implements Cloneable {
    private int value;

    public MyClass(int value) {
        this.value = value;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

public class Main {
    public static void main(String[] args) {
        try {
            MyClass obj1 = new MyClass(42);
            MyClass obj2 = (MyClass) obj1.clone();
            System.out.println(obj2.value);
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }
}

6. finalize() 方法

这个方法在对象被垃圾回收之前会被调用。不过,不建议依赖这个方法来进行资源清理,因为它的调用时间是不确定的,而且从 Java 9 开始,该方法已被弃用。

class MyClass {
    @Override
    protected void finalize() throws Throwable {
        System.out.println("Object is being garbage collected");
        super.finalize();
    }
}

public class Main {
    public static void main(String[] args) {
        MyClass obj = new MyClass();
        obj = null;
        System.gc();
    }
}

7. notify()notifyAll() 和 wait() 方法

这些方法用于线程间的通信。notify() 会唤醒在此对象监视器上等待的单个线程,notifyAll() 会唤醒在此对象监视器上等待的所有线程,而 wait() 会让当前线程等待,直到其他线程调用该对象的 notify() 或 notifyAll() 方法。

class SharedResource {
    private boolean flag = false;

    public synchronized void waitForFlag() throws InterruptedException {
        while (!flag) {
            wait();
        }
        System.out.println("Flag is set, continuing...");
    }

    public synchronized void setFlag() {
        flag = true;
        notifyAll();
    }
}

public class Main {
    public static void main(String[] args) {
        SharedResource resource = new SharedResource();

        Thread t1 = new Thread(() -> {
            try {
                resource.waitForFlag();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        Thread t2 = new Thread(() -> {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            resource.setFlag();
        });

        t1.start();
        t2.start();
    }
}

这些就是 Object 类的基本方法,在 Java 编程中经常会用到。

5 哈希码是什么?

在 Java 里,哈希码(Hash Code)是一个由对象的 hashCode() 方法返回的整数值。下面从基本概念、生成方式、使用规则、作用、潜在问题几个方面详细介绍 Java 中的哈希码。

基本概念

Java 中每个对象都继承自 Object 类,而 Object 类有一个 hashCode() 方法,其用途是为对象生成一个唯一标识整数。默认情况下,这个整数基于对象在内存中的地址生成。不过,很多类会重写 hashCode() 方法,以便根据对象的内容来生成哈希码。

生成方式

  • 默认生成:若类没有重写 hashCode() 方法,那么对象的哈希码就由 Object 类的 hashCode() 方法生成,通常和对象的内存地址有关。
class DefaultHashCodeExample {
    public static void main(String[] args) {
        Object obj = new Object();
        System.out.println(obj.hashCode());
    }
}
  • 重写生成:为了让相等的对象具有相同的哈希码,许多类会重写 hashCode() 方法。一般会结合对象的属性来计算哈希码。
import java.util.Objects;

class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

使用规则

在 Java 里使用哈希码时,要遵循下面的规则:

  • 一致性:在程序执行期间,如果对象用于 equals() 方法的比较信息没有改变,那么多次调用 hashCode() 方法必须返回相同的整数。
  • 相等性:如果两个对象通过 equals() 方法比较是相等的,那么它们调用 hashCode() 方法必须返回相同的整数结果。
  • 非相等性:如果两个对象通过 equals() 方法比较是不相等的,那么调用 hashCode() 方法不一定要求返回不同的整数。不过,为不同对象生成不同的哈希码可以提高哈希表的性能。

作用

  • 哈希表数据结构:哈希码在哈希表(像 HashMapHashSet 等)里起着关键作用。哈希表借助哈希码来确定元素在内部数组中的存储位置,以此实现快速的插入、查找和删除操作。
import java.util.HashMap;
import java.util.Map;

public class HashCodeInHashMap {
    public static void main(String[] args) {
        Map personMap = new HashMap<>();
        Person person = new Person("Alice", 25);
        personMap.put(person, "Some value");
        String value = personMap.get(person);
        System.out.println(value);
    }
}

  • 数据分组与分布:在分布式系统或者多线程环境中,哈希码可用于数据的分组和分布,保证数据均匀地分布到不同的节点或者线程中。

潜在问题

  • 哈希冲突:由于哈希码的取值范围是有限的,不同的对象可能会生成相同的哈希码,这种情况被称作哈希冲突。在哈希表中,哈希冲突会影响性能,需要采用合适的解决方法(如链地址法、开放地址法等)来处理。
  • 性能影响:计算哈希码的过程可能会影响性能,特别是在对象属性较多或者计算复杂的情况下。所以,在重写 hashCode() 方法时,要尽量保证计算过程高效。

你可能感兴趣的:(学习,java,开发语言)