
本文共 2120 字,大约阅读时间需要 7 分钟。
当JVM成功地将一个类加载到方法区后,接下来它会为新创建的对象分配内存。这个内存肯定是位于堆区中的,因为新对象的信息至少需要存储在某个地方。
然后,如果你试图创建一个新对象,例如’ve new Student();’,JVM会立即加载相关的 Student 类。如果之前类加载检查已经完成,那么它会毫不犹豫地为这个对象分配堆区的内存。这个内存管理过程是通过调用系统所支持的内存管理机制完成的,这也意味着如果堆区内存不够用时,JVM会抛出OutOfMemoryError(OOM)异常。
让我们深入理解一下JVM的内存结构和对象管理机制:
JVM的内存结构包括以下几个部分:
1.1 程序计数器
程序计数器主要用于记录当前线程所执行的字节码行号。它是线程私有的,当一个CPU在不同的线程间切换时,程序计数器可以完全独立地工作。你应该知道,程序计数器是依赖于它完成线程切换和执行控制流的每一个跳转操作,所以它在多线程执行中非常关键。
1.2 虚拟机栈
虚拟机栈是为每个线程分配的,每个方法执行时都会创建一个栈帧。栈帧包含调用方法时所需的信息,比如本地方法表、本地变量表等。特别是当递归或深度迭代发生时,虚拟机栈可能会因为栈帧数量过多而导致 StackOverflowError。
1.3 本地方法栈
本地方法栈和虚拟机栈的功能类似,但它是为本地(Native)方法服务的。此外,本地方法栈也会有栈溢出(StackOverflowError)和内存溢出(OutOfMemoryError)的异常。
1.4 Java 堆
Java 堆是所有线程共享的一块内存区域,主要存放对象实例和数组。垃圾回收器的工作重点就在这里。如果你不断地创建对象而不进行有效的内存管理,堆区的内存很快就会被用完。这时候,JVM就会抛出OutOfMemoryError。
1.5 方法区
方法区是一个全共享的内存区域,主要用来存储类的元数据、静态变量以及编译后的常量值。与堆不同,方法区有时也被称为“非堆”(Non-Heap),因为它通常不会参与垃圾回收过程。不过,也有一些实现可能会将方法区纳入堆中的“永久代”。
1.6 运行时常量池
运行时常量池是方法区的一部分,主要存放编译期生成的字面量和符号引用。如果你使用了字符串的 intern() 方法,就会将常量存储在这里。如果常量池内存不够用时,也会抛出OutOfMemoryError。
此外,直接内存也属于内存的一部分,它允许某些本地方法直接分配内存。例如,在Java I/O操作中使用的DirectByteBuffer就是利用了直接内存。
内存溢出的处理
内存溢出(OutOfMemoryError)是Java程序在内存管理中可能遇到的一个常见问题。具体表现形式可以有以下几种:
堆溢出
堆溢出是最常见的内存溢出类型。例如,当你试图在堆中创建无限数量的对象时,堆内存会被逐渐消耗完毕,导致JVM无法分配内存,从而抛出堆溢出异常。你可以通过一个简单的循环测试这一点:
Listlist = new ArrayList<>();while (true) { list.add(new String(new char(100000).intern()));}
栈溢出
栈溢出通常发生在递归或深层次的迭代中。例如,以下代码会导致栈溢出:
private Integer stackLength = 1;public void stackLoop() { stackLength++; stackLoop();}public static void main(String[] args) { OutOfMemoryError a = new OutOfMemoryError(); try { a.stackLoop(); } catch (Throwable e) { System.out.println("stack length: " + a.stackLength); throw e; }}
方法区溢出
方法区溢出相对少见,但它可以通过动态生成大量类来模拟。例如,使用CGLib动态生成类,当方法区内存不够时,就会抛出溢出异常:
public class JavaMethodAreaOOM { public static void main(String[] args) { while (true) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(OOMObject.class); enhancer.create(); } } static class OOMObject { }}
发表评论
最新留言
关于作者
