Java对象类型

在 Java 中,引用类型决定了对象与垃圾回收器(GC)的交互方式。Java 提供了四种引用类型:强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)和虚引用(Phantom Reference)。它们的主要区别在于垃圾回收器对待它们的方式不同。

1. 强引用(Strong Reference)

强引用是最常见的引用类型。如果一个对象具有强引用,垃圾回收器不会回收该对象,即使内存不足时也不会回收。只有当强引用被显式地设置为 null 时,对象才会被垃圾回收器回收。

Object obj = new Object(); // 强引用
obj = null; // 强引用被置为 null,对象可以被回收

2. 软引用(Soft Reference)

软引用用于描述一些还有用但非必需的对象。当内存不足时,垃圾回收器会回收软引用指向的对象。软引用通常用于实现内存敏感的缓存。

  SoftReference<Object> softRef = new SoftReference<>(new Object());
  Object obj = softRef.get(); // 获取软引用指向的对象
  if (obj == null) {
      // 对象已被回收
  }

3. 弱引用(Weak Reference)

弱引用比软引用更弱一些。垃圾回收器一旦发现弱引用指向的对象,无论内存是否充足,都会回收该对象。弱引用通常用于实现缓存或映射表,例如 WeakHashMap

WeakReference<Object> weakRef = new WeakReference<>(new Object());
Object obj = weakRef.get(); // 获取弱引用指向的对象
if (obj == null) {
    // 对象已被回收
}

4. 虚引用(Phantom Reference)

虚引用是最弱的一种引用类型。虚引用无法通过 get() 方法获取到对象,它的唯一作用是在对象被垃圾回收时收到一个系统通知。虚引用通常与引用队列(ReferenceQueue)一起使用,用于跟踪对象被回收的状态。

ReferenceQueue<Object> queue = new ReferenceQueue<>();
PhantomReference<Object> phantomRef = new PhantomReference<>(new Object(), queue);
// 虚引用的 get() 方法总是返回 null
Object obj = phantomRef.get(); // 返回 null

引用队列(ReferenceQueue)

引用队列可以与软引用、弱引用和虚引用一起使用。当引用的对象被垃圾回收时,引用本身会被加入到引用队列中,程序可以通过检查引用队列来了解哪些对象被回收。

ReferenceQueue<Object> queue = new ReferenceQueue<>();
WeakReference<Object> weakRef = new WeakReference<>(new Object(), queue);
// 当对象被回收时,weakRef 会被加入到 queue 中

总结

  1. 强引用:对象不会被垃圾回收,除非引用被显式置为 null
  2. 软引用:内存不足时,垃圾回收器会回收软引用指向的对象。
  3. 弱引用:垃圾回收器一旦发现弱引用指向的对象,就会回收它。
  4. 虚引用:无法通过 get() 获取对象,主要用于跟踪对象被回收的状态。

JVM 内存结构

1. JVM 的内存结构

  • 堆内存(Heap):用于存储对象实例,是垃圾回收的主要区域。
  • 方法区(Metaspace):存储类元数据、常量池等。
  • 栈内存(Stack):用于存储线程的局部变量和方法调用。
  • 本地方法栈(Native Method Stack):用于支持本地方法调用。

其中,堆内存是判断剩余内存是否不足的主要区域。


2. JVM 如何判断剩余内存不足

JVM 通过以下机制判断剩余内存是否不足:

(1)垃圾回收器的行为
  • 当堆内存的使用量接近最大值时,JVM 会触发垃圾回收(GC)。
  • 如果 GC 后仍然无法释放足够的内存,JVM 会认为剩余内存不足。
(2)内存分配失败
  • 当尝试分配内存时(例如创建新对象),如果堆内存中没有足够的连续空间,JVM 会触发 GC。
  • 如果 GC 后仍然无法分配足够的内存,JVM 会抛出 OutOfMemoryError
(3)堆内存的使用阈值
  • JVM 会根据堆内存的使用情况动态调整 GC 的触发时机。
  • 例如,如果老年代(Old Generation)的内存使用量超过某个阈值,JVM 会触发 Full GC。

3. JVM 参数与内存管理

JVM 提供了一些参数来控制内存分配和垃圾回收的行为,这些参数也会影响 JVM 判断剩余内存是否不足:

(1)堆内存大小参数
  • -Xmx:设置堆内存的最大值。
  • -Xms:设置堆内存的初始值。

例如:

java -Xms512m -Xmx1024m MyApp
  • 初始堆内存为 512 MB,最大堆内存为 1024 MB。
  • 当堆内存使用量接近 1024 MB 时,JVM 会触发 GC。
(2)GC 相关参数
  • -XX:+UseG1GC:启用 G1 垃圾回收器。
  • -XX:MaxGCPauseMillis:设置最大 GC 停顿时间。
  • -XX:InitiatingHeapOccupancyPercent:设置触发并发 GC 的堆内存使用百分比。
(3)内存不足时的行为
  • 如果 GC 后仍然无法释放足够的内存,JVM 会抛出 OutOfMemoryError

4. 如何监控剩余内存

可以通过以下方式监控 JVM 的剩余内存:

(1)使用 JVM 内置工具
  • Runtime:通过 Runtime.getRuntime() 获取 JVM 的内存信息。

    Runtime runtime = Runtime.getRuntime();
    long freeMemory = runtime.freeMemory(); // 当前空闲内存
    long totalMemory = runtime.totalMemory(); // 当前总内存
    long maxMemory = runtime.maxMemory(); // 最大内存
    System.out.println("Free Memory: " + freeMemory);
    System.out.println("Total Memory: " + totalMemory);
    System.out.println("Max Memory: " + maxMemory);
(2)使用 JMX(Java Management Extensions)
  • 通过 JMX 可以监控 JVM 的内存使用情况。
  • 例如,使用

    java.lang.management.MemoryMXBean`:
    import java.lang.management.ManagementFactory;
    import java.lang.management.MemoryMXBean;
    import java.lang.management.MemoryUsage;
    
    public class MemoryMonitor {
       public static void main(String[] args) {
           MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();
           MemoryUsage heapUsage = memoryBean.getHeapMemoryUsage();
           MemoryUsage nonHeapUsage = memoryBean.getNonHeapMemoryUsage();
    
           System.out.println("Heap Memory Usage: " + heapUsage);
           System.out.println("Non-Heap Memory Usage: " + nonHeapUsage);
       }
    }
(3)使用外部工具
  • JVisualVM:图形化工具,可以监控 JVM 的内存使用情况。
  • JConsole:Java 自带的监控工具。
  • Prometheus + Grafana:用于分布式系统的监控和可视化。

5. 模拟内存不足的场景

可以通过以下代码模拟内存不足的场景:

public class OutOfMemoryExample {
    public static void main(String[] args) {
        try {
            // 不断分配内存,直到抛出 OutOfMemoryError
            List<byte[]> list = new ArrayList<>();
            while (true) {
                list.add(new byte[1024 * 1024]); // 每次分配 1 MB
            }
        } catch (OutOfMemoryError e) {
            System.out.println("Out of memory!");
        }
    }
}

6. 总结

  • JVM 通过垃圾回收机制和内存分配失败来判断剩余内存是否不足。
  • 可以通过 Runtime 类、JMX 或外部工具监控 JVM 的内存使用情况。
  • 合理设置 JVM 参数(如 -Xmx-Xms)可以优化内存管理,避免 OutOfMemoryError
  • 内存不足时,JVM 会触发 GC,如果 GC 后仍然无法释放足够的内存,会抛出 OutOfMemoryError

JVM内存监控

在 Java 中,Runtime 类提供了几个方法来获取 JVM 的内存使用情况,主要包括以下三个方法:

  1. freeMemory()
  2. totalMemory()
  3. maxMemory()

这些方法返回的值代表了 JVM 内存管理的不同方面。以下是它们的详细区别:


1. freeMemory()

  • 含义:当前 JVM 堆内存中空闲的内存大小
  • 解释

    • 这部分内存是已经被 JVM 分配但尚未被使用的内存。
    • 它可以立即用于分配新对象。
    • 单位:字节(bytes)。
  • 示例
long freeMemory = Runtime.getRuntime().freeMemory();
System.out.println("Free Memory: " + freeMemory + " bytes");

2. totalMemory()

  • 含义:当前 JVM 堆内存的总大小
  • 解释

    • 这部分内存是 JVM 已经从操作系统分配的堆内存大小。
    • 它包括已使用的内存和空闲的内存(即 freeMemory())。
    • 如果堆内存不足,JVM 会尝试向操作系统申请更多内存,直到达到 maxMemory() 的限制。
  • 单位:字节(bytes)。
  • 示例
long totalMemory = Runtime.getRuntime().totalMemory();
System.out.println("Total Memory: " + totalMemory + " bytes");

3. maxMemory()

  • 含义:JVM 堆内存的最大大小
  • 解释

    • 这部分内存是 JVM 堆内存可以扩展到的最大大小。
    • 它由 JVM 参数 -Xmx 设置。
    • 如果堆内存使用量接近 maxMemory(),JVM 会触发垃圾回收(GC)。如果 GC 后仍然无法释放足够的内存,JVM 会抛出 OutOfMemoryError
  • 单位:字节(bytes)。
  • 示例
long maxMemory = Runtime.getRuntime().maxMemory();
System.out.println("Max Memory: " + maxMemory + " bytes");

4. 三者之间的关系

  • freeMemory()totalMemory() 的一部分,表示当前空闲的内存。
  • totalMemory() 是 JVM 当前从操作系统分配的堆内存大小,可以动态扩展,但不会超过 maxMemory()。初始值由 -Xms 设置,可以动态扩展
  • maxMemory() 是 JVM 堆内存的最大限制,由 -Xmx 参数设置。

用公式表示它们的关系:

freeMemory() <= totalMemory() <= maxMemory()

5. 示例代码

以下代码展示了如何获取和打印这些内存信息:

public class MemoryInfo {
    public static void main(String[] args) {
        Runtime runtime = Runtime.getRuntime();

        long freeMemory = runtime.freeMemory(); // 空闲内存
        long totalMemory = runtime.totalMemory(); // 当前总内存
        long maxMemory = runtime.maxMemory(); // 最大内存

        System.out.println("Free Memory: " + freeMemory + " bytes");
        System.out.println("Total Memory: " + totalMemory + " bytes");
        System.out.println("Max Memory: " + maxMemory + " bytes");

        // 计算已使用内存
        long usedMemory = totalMemory - freeMemory;
        System.out.println("Used Memory: " + usedMemory + " bytes");
    }
}

输出示例

Free Memory: 12345678 bytes
Total Memory: 56789012 bytes
Max Memory: 1234567890 bytes
Used Memory: 44443334 bytes
Toal Memory = Free Memory + Used Memory

6. 总结

  • freeMemory():当前空闲的内存,可以立即用于分配新对象。
  • totalMemory():JVM 当前从操作系统分配的堆内存大小。
  • maxMemory():JVM 堆内存的最大限制,由 -Xmx 参数设置。
  • 通过这三个值,可以监控 JVM 的内存使用情况,判断是否存在内存不足的风险。

philadelphia
17 声望4 粉丝

雪山千古冷,独照峨眉峰