共计 10651 个字符,预计需要花费 27 分钟才能阅读完成。
1、运行时数据区
Java 虚拟机定义了若干种程序运行期间会应用到的运行时数据区,其中有一些会随着虚拟机启动而创立,随着虚拟机退出而销毁。另外一些则是与线程一一对应的,这些与线程对应的数据区域会随着线程开始和完结而创立和销毁。
依据《Java 虚拟机标准》的规定,Java 虚拟机所治理的内存将会包含以下几个运行时数据区域:
1.1、程序计数器
程序计数器(Program Counter Register)也被称为 PC 寄存器,是一块较小的内存空间。
它能够看作是以后线程所执行的字节码的行号指示器。在 Java 虚拟机的概念模型里,字节码解释器工作时就是通过扭转这个计数器的值来选取下一条须要执行的字节码指令,它是程序控制流的指示器,分支、循环、跳转、异样解决、线程复原等根底性能都须要依赖这个计数器来实现。
每一条 Java 虚拟机线程都有本人的程序计数器。在任意时刻,一条 Java 虚拟机线程只会执行一个办法的代码,这个正在被线程执行的办法称为该线程的以后办法
如果这个办法不是 native 的,那 PC 寄存器就保留 Java 虚拟机正在执行的字节码指令的地址,如果该办法是 native 的,那 PC 寄存器的值是 undefined。
1.2、Java 虚拟机栈
与程序计数器一样,Java 虚拟机栈(Java Virtual Machine Stack)也是线程公有的,它的生命周期与线程雷同。虚拟机栈形容的是 Java 办法执行的线程内存模型:每个办法被执行的时候,Java 虚拟机都 会同步创立一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动静连贯、办法进口等信息。每一个办法被调用直至执行结束的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
把 Java 内存区域能够粗略地划分为堆内存(Heap)和栈内存(Stack),其中,“栈”通常就是指这里讲的虚拟机栈,或者更多的状况下只是指虚拟机栈中局部变量表局部。
局部变量表 寄存了编译期可知的各种 Java 虚拟机根本数据类型(boolean、byte、char、short、int、float、long、double)、对象援用(reference 类型,它并不等同于对象自身,可能是一个指向对象起始地址的援用指针,也可能是指向一个代表对象的句柄或者其余与此对象相干的地位)和 returnAddress 类型(指向了一条字节码指令的地址)。
这些数据类型在局部变量表中的存储空间以局部变量槽(Slot)来示意,其中 64 位长度的 long 和 double 类型的数据会占用两个变量槽,其余的数据类型只占用一个。局部变量表所需的内存空间在编译期间实现调配,当进入一个办法时,这个办法须要在栈帧中调配多大的局部变量空间是齐全确定 的,在办法运行期间不会扭转局部变量表的大小。
Java 虚拟机栈可能产生如下异常情况:
- 如果线程申请调配的栈容量超过 Java 虚拟机栈容许的最大容量时,Java 虚拟机将会抛出一个 StackOverflowError 异样。
- 如果 Java 虚拟机栈能够动静扩大,并且扩大的动作曾经尝试过,然而目前无奈申请到足够的内存去实现扩大,或者在建设新的线程时没有足够的内存去创立对应的虚拟机栈,那 Java 虚拟机将会抛出一个 OutOfMemoryError 异样。
1.3、本地办法栈
本地办法栈(Native Method Stacks)与虚拟机栈所施展的作用是十分类似的,其区别只是虚拟机栈为虚拟机执行 Java 办法(也就是字节码)服务,而本地办法栈则是为虚拟机应用到的本地(Native)办法服务。
Java 虚拟机标准容许本地办法栈被实现成固定大小的或者是依据计算动静扩大和膨胀的。
本地办法栈可能产生如下异常情况:
- 如果线程申请调配的栈容量超过本地办法栈容许的最大容量时,Java 虚拟机将会抛出一个 StackOverflowError 异样。
- 如果本地办法栈能够动静扩大,并且扩大的动作曾经尝试过,然而目前无奈申请到足够的内存去实现扩大,或者在建设新的线程时没有足够的内存去创立对应的本地办法栈,那 Java 虚拟机将会抛出一个 OutOfMemoryError 异样。
1.4、Java 堆
对于 Java 应用程序来说,Java 堆(Java Heap)是虚拟机所治理的内存中最大的一块。Java 堆是被所有线程共享的一块内存区域,在虚拟机启动时创立。此内存区域的惟一目标就是寄存对象实例,Java 里“简直”所有的对象实例都在这里分配内存。
Java 堆是垃圾收集器治理的内存区域,因而一些材料中它也被称作“GC 堆”(Garbage Collected Heap,)。从回收内存的角度看,因为古代垃圾收集器大部分都是基于分代收集实践设计的,所以 Java 堆中常常会呈现“新生代”“老年代”“永恒代”“Eden 空间”“From Survivor 空间”“To Survivor 空间”等名词,须要留神的是这些区域划分仅仅是一部分垃圾收集器的独特个性或者说设计格调而已,而非某个 Java 虚拟机具体 实现的固有内存布局,更不是《Java 虚拟机标准》里对 Java 堆的进一步粗疏划分。
如果从分配内存的角度看,所有线程共享的 Java 堆中能够划分出多个线程公有的调配缓冲区(Thread Local Allocation Buffer,TLAB),以晋升对象调配时的效率。不过无论从什么角度,无论如何划分,都不会扭转 Java 堆中存储内容的共性,无论是哪个区域,存储的都只能是对象的实例,将 Java 堆细分的目标只是为了更好地回收内存,或者更快地分配内存。
依据《Java 虚拟机标准》的规定,Java 堆能够处于物理上不间断的内存空间中,但在逻辑上它应该 被视为间断的,这点就像咱们用磁盘空间去存储文件一样,并不要求每个文件都间断寄存。但对于大对象(典型的如数组对象),少数虚拟机实现出于实现简略、存储高效的思考,很可能会要求间断的内存空间。
Java 堆既能够被实现成固定大小的,也能够是可扩大的,不过以后支流的 Java 虚拟机都是依照可扩大来实现的(通过参数 -Xmx 和 -Xms 设定)。如果在 Java 堆中没有内存实现实例调配,并且堆也无奈再扩大时,Java 虚拟机将会抛出 OutOfMemoryError 异样。
1.5、办法区
办法区(Method Area)与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类型信息、常量、动态变量、即时编译器编译后的代码缓存等数据。尽管《Java 虚拟机标准》中把办法区形容为堆的一个逻辑局部,然而它却有一个别名叫作“非堆”(Non-Heap),目标是与 Java 堆辨别开来。
《Java 虚拟机标准》对办法区的束缚是十分宽松的,除了和 Java 堆一样不须要间断的内存和能够抉择固定大小或者可扩大外,甚至还能够抉择不实现垃圾收集。相对而言,垃圾收集行为在这个区域确实是比拟少呈现的,但并非数据进入了办法区就如永恒代的名字一样“永恒”存在了。这区域的内存回收指标次要是针对常量池的回收和对类型的卸载,一般来说这个区域的回收成果比拟难令人满意,尤其是类型的卸载,条件相当刻薄,然而这部分区域的回收有时又的确是必要的。以前 Sun 公司的 Bug 列表中,曾呈现过的若干个重大的 Bug 就是因为低版本的 HotSpot 虚拟机对此区域未齐全回收而导致内存透露。
依据《Java 虚拟机标准》的规定,如果办法区无奈满足新的内存调配需要时,将抛出 OutOfMemoryError 异样。
值得一提的是:很多人都更违心把办法区称说为“永恒代”(Permanent Generation),或将两者一概而论。实质上这两者并不是等价的,因为仅仅是过后的 HotSpot 虚拟机设计团队抉择把收集器的分代设计扩大至办法区,或者说应用永恒代来实现办法区而已,这样使得 HotSpot 的垃圾收集器可能像治理 Java 堆一样治理这部分内存,省去专门为办法区编写内存治理代码的工作。然而对于其余虚拟机实现,譬如 BEA JRockit、IBM J9 等来说,是不存在永恒代的概念的。
1.6、运行时常量池
运行时常量池(Runtime Constant Pool)是办法区的一部分。Class 文件中除了有类的版本、字段、办法、接口等形容信息外,还有一项信息是常量池表(Constant Pool Table),用于寄存编译期生成的各种字面量与符号援用,这部分内容将在类加载后寄存到办法区的运行时常量池中。
Java 虚拟机对于 Class 文件每一部分(天然也包含常量池)的格局都有严格规定,如每一个字节用于存储哪种数据都必须符合规范上的要求才会被虚拟机认可、加载和执行,但对于运行时常量池,
《Java 虚拟机标准》并没有做任何细节的要求,不同提供商实现的虚拟机能够依照本人的须要来实现这个内存区域,不过一般来说,除了保留 Class 文件中形容的符号援用外,还会把由符号援用翻译进去的间接援用也存储在运行时常量池中。
运行时常量池绝对于 Class 文件常量池的另外一个重要特色是具备动态性,Java 语言并不要求常量 肯定只有编译期能力产生,也就是说,并非预置入 Class 文件中常量池的内容能力进入办法区运行时常量池,运行期间也能够将新的常量放入池中,这种个性被开发人员利用得比拟多的便是 String 类的 intern()办法。
既然运行时常量池是办法区的一部分,天然受到办法区内存的限度,当常量池无奈再申请到内存时会抛出 OutOfMemoryError 异样。
1.7、间接内存
间接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是《Java 虚拟机标准》中定义的内存区域。
在 JDK 1.4 中新退出了 NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的 I / O 形式,它能够应用 Native 函数库间接调配堆外内存,而后通过一个存储在 Java 堆外面的 DirectByteBuffer 对象作为这块内存的援用进行操作。这样能在一些场景中显著进步性能,因为防止了 在 Java 堆和 Native 堆中来回复制数据。
显然,本机间接内存的调配不会受到 Java 堆大小的限度,然而,既然是内存,则必定还是会受到 本机总内存(包含物理内存、SWAP 分区或者分页文件)大小以及处理器寻址空间的限度,个别服务器管理员配置虚拟机参数时,会依据理论内存去设置 -Xmx 等参数信息,但常常疏忽掉间接内存,使得 各个内存区域总和大于物理内存限度(包含物理的和操作系统级的限度),从而导致动静扩大时呈现 OutOfMemoryError 异样。
2、JDK 的内存区域变迁
2.1、jdk1.6/1.7/1.8 内存区域变动
在上一节提到了,HotSpot 虚拟机是是 Sun/OracleJDK 和 OpenJDK 中的默认 Java 虚拟机,是 JVM 利用最宽泛的一种实现。下面提到,Java 虚拟机标准对办法区的束缚很宽松,而且 HotSpot 虚拟机在这一区域产生过一些 bug,所以 HotSpot 的办法区经验了一些变迁,咱们来看看 HotSpot 虚拟机内存区域的变迁。
- JDK1.6 期间和咱们下面讲的 JVM 内存区域是统一的:
- JDK1.7 时产生了一些变动,将字符串常量池、动态变量,寄存在堆上
- 在 JDK1.8 时彻底干掉了办法区,而在间接内存中划出一块区域作为 元空间,运行时常量池、类常量池都挪动到元空间。
2.2、为什么替换掉办法区
办法区为什么被代替了呢?当然,或者更精确的说法应该是 永恒代 为什么被替换了?——Java 虚拟机标准规定的办法区只是换种形式实现。有主观和主观两个起因。
- 主观上应用永恒代来实现办法区的决定的设计导致了 Java 利用更容易遇到内存溢出的问题(永恒代有 -XX:MaxPermSize 的下限,即便不设置也有默认大小,而 J9 和 JRockit 只有没有触碰到过程可用内存的下限,例如 32 位零碎中的 4GB 限度,就不会出问题),而且有极少数办法(例如 String::intern())会因永恒代的起因而导致不同虚拟机下有不同的体现。
- 主观受骗 Oracle 收买 BEA 取得了 JRockit 的所有权后,筹备把 JRockit 中的优良性能,譬如 Java Mission Control 管理工具,移植到 HotSpot 虚拟机时,但因为两者对办法区实现的差别而面临诸多困难。思考到 HotSpot 将来的倒退,在 JDK 6 的 时候 HotSpot 开发团队就有放弃永恒代,逐渐改为采纳本地内存(Native Memory)来实现办法区的打算了,到了 JDK 7 的 HotSpot,曾经把本来放在永恒代的字符串常量池、动态变量等移出,而到了 JDK 8,终于齐全废除了永恒代的概念,改用与 JRockit、J9 一样在本地内存中实现的元空间(Meta-space)来代替,把 JDK 7 中永恒代还残余的内容(次要是类型信息)全副移到元空间中。
<big>参考:</big>
【1】:周志朋编著《深刻了解 Java 虚拟机:JVM 高级个性与最佳实际》
【2】:周志朋等翻译《Java 虚拟机标准》
【3】:内存篇:JVM 内存构造
【4】:这一次,终于零碎的学习了 JVM 内存构造
【5】:面经手册 · 第 25 篇《JVM 内存模型总结,有各版本 JDK 比照、有元空间 OOM 监控案例、有 Java 版虚拟机,综合学习更容易!》1、运行时数据区
Java 虚拟机定义了若干种程序运行期间会应用到的运行时数据区,其中有一些会随着虚拟机启动而创立,随着虚拟机退出而销毁。另外一些则是与线程一一对应的,这些与线程对应的数据区域会随着线程开始和完结而创立和销毁。
依据《Java 虚拟机标准》的规定,Java 虚拟机所治理的内存将会包含以下几个运行时数据区域:
1.1、程序计数器
程序计数器(Program Counter Register)也被称为 PC 寄存器,是一块较小的内存空间。
它能够看作是以后线程所执行的字节码的行号指示器。在 Java 虚拟机的概念模型里,字节码解释器工作时就是通过扭转这个计数器的值来选取下一条须要执行的字节码指令,它是程序控制流的指示器,分支、循环、跳转、异样解决、线程复原等根底性能都须要依赖这个计数器来实现。
每一条 Java 虚拟机线程都有本人的程序计数器。在任意时刻,一条 Java 虚拟机线程只会执行一个办法的代码,这个正在被线程执行的办法称为该线程的以后办法
如果这个办法不是 native 的,那 PC 寄存器就保留 Java 虚拟机正在执行的字节码指令的地址,如果该办法是 native 的,那 PC 寄存器的值是 undefined。
1.2、Java 虚拟机栈
与程序计数器一样,Java 虚拟机栈(Java Virtual Machine Stack)也是线程公有的,它的生命周期与线程雷同。虚拟机栈形容的是 Java 办法执行的线程内存模型:每个办法被执行的时候,Java 虚拟机都 会同步创立一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动静连贯、办法进口等信息。每一个办法被调用直至执行结束的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
把 Java 内存区域能够粗略地划分为堆内存(Heap)和栈内存(Stack),其中,“栈”通常就是指这里讲的虚拟机栈,或者更多的状况下只是指虚拟机栈中局部变量表局部。
局部变量表 寄存了编译期可知的各种 Java 虚拟机根本数据类型(boolean、byte、char、short、int、float、long、double)、对象援用(reference 类型,它并不等同于对象自身,可能是一个指向对象起始地址的援用指针,也可能是指向一个代表对象的句柄或者其余与此对象相干的地位)和 returnAddress 类型(指向了一条字节码指令的地址)。
这些数据类型在局部变量表中的存储空间以局部变量槽(Slot)来示意,其中 64 位长度的 long 和 double 类型的数据会占用两个变量槽,其余的数据类型只占用一个。局部变量表所需的内存空间在编译期间实现调配,当进入一个办法时,这个办法须要在栈帧中调配多大的局部变量空间是齐全确定 的,在办法运行期间不会扭转局部变量表的大小。
Java 虚拟机栈可能产生如下异常情况:
- 如果线程申请调配的栈容量超过 Java 虚拟机栈容许的最大容量时,Java 虚拟机将会抛出一个 StackOverflowError 异样。
- 如果 Java 虚拟机栈能够动静扩大,并且扩大的动作曾经尝试过,然而目前无奈申请到足够的内存去实现扩大,或者在建设新的线程时没有足够的内存去创立对应的虚拟机栈,那 Java 虚拟机将会抛出一个 OutOfMemoryError 异样。
1.3、本地办法栈
本地办法栈(Native Method Stacks)与虚拟机栈所施展的作用是十分类似的,其区别只是虚拟机栈为虚拟机执行 Java 办法(也就是字节码)服务,而本地办法栈则是为虚拟机应用到的本地(Native)办法服务。
Java 虚拟机标准容许本地办法栈被实现成固定大小的或者是依据计算动静扩大和膨胀的。
本地办法栈可能产生如下异常情况:
- 如果线程申请调配的栈容量超过本地办法栈容许的最大容量时,Java 虚拟机将会抛出一个 StackOverflowError 异样。
- 如果本地办法栈能够动静扩大,并且扩大的动作曾经尝试过,然而目前无奈申请到足够的内存去实现扩大,或者在建设新的线程时没有足够的内存去创立对应的本地办法栈,那 Java 虚拟机将会抛出一个 OutOfMemoryError 异样。
1.4、Java 堆
对于 Java 应用程序来说,Java 堆(Java Heap)是虚拟机所治理的内存中最大的一块。Java 堆是被所有线程共享的一块内存区域,在虚拟机启动时创立。此内存区域的惟一目标就是寄存对象实例,Java 里“简直”所有的对象实例都在这里分配内存。
Java 堆是垃圾收集器治理的内存区域,因而一些材料中它也被称作“GC 堆”(Garbage Collected Heap,)。从回收内存的角度看,因为古代垃圾收集器大部分都是基于分代收集实践设计的,所以 Java 堆中常常会呈现“新生代”“老年代”“永恒代”“Eden 空间”“From Survivor 空间”“To Survivor 空间”等名词,须要留神的是这些区域划分仅仅是一部分垃圾收集器的独特个性或者说设计格调而已,而非某个 Java 虚拟机具体 实现的固有内存布局,更不是《Java 虚拟机标准》里对 Java 堆的进一步粗疏划分。
如果从分配内存的角度看,所有线程共享的 Java 堆中能够划分出多个线程公有的调配缓冲区(Thread Local Allocation Buffer,TLAB),以晋升对象调配时的效率。不过无论从什么角度,无论如何划分,都不会扭转 Java 堆中存储内容的共性,无论是哪个区域,存储的都只能是对象的实例,将 Java 堆细分的目标只是为了更好地回收内存,或者更快地分配内存。
依据《Java 虚拟机标准》的规定,Java 堆能够处于物理上不间断的内存空间中,但在逻辑上它应该 被视为间断的,这点就像咱们用磁盘空间去存储文件一样,并不要求每个文件都间断寄存。但对于大对象(典型的如数组对象),少数虚拟机实现出于实现简略、存储高效的思考,很可能会要求间断的内存空间。
Java 堆既能够被实现成固定大小的,也能够是可扩大的,不过以后支流的 Java 虚拟机都是依照可扩大来实现的(通过参数 -Xmx 和 -Xms 设定)。如果在 Java 堆中没有内存实现实例调配,并且堆也无奈再扩大时,Java 虚拟机将会抛出 OutOfMemoryError 异样。
1.5、办法区
办法区(Method Area)与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类型信息、常量、动态变量、即时编译器编译后的代码缓存等数据。尽管《Java 虚拟机标准》中把办法区形容为堆的一个逻辑局部,然而它却有一个别名叫作“非堆”(Non-Heap),目标是与 Java 堆辨别开来。
《Java 虚拟机标准》对办法区的束缚是十分宽松的,除了和 Java 堆一样不须要间断的内存和能够抉择固定大小或者可扩大外,甚至还能够抉择不实现垃圾收集。相对而言,垃圾收集行为在这个区域确实是比拟少呈现的,但并非数据进入了办法区就如永恒代的名字一样“永恒”存在了。这区域的内存回收指标次要是针对常量池的回收和对类型的卸载,一般来说这个区域的回收成果比拟难令人满意,尤其是类型的卸载,条件相当刻薄,然而这部分区域的回收有时又的确是必要的。以前 Sun 公司的 Bug 列表中,曾呈现过的若干个重大的 Bug 就是因为低版本的 HotSpot 虚拟机对此区域未齐全回收而导致内存透露。
依据《Java 虚拟机标准》的规定,如果办法区无奈满足新的内存调配需要时,将抛出 OutOfMemoryError 异样。
值得一提的是:很多人都更违心把办法区称说为“永恒代”(Permanent Generation),或将两者一概而论。实质上这两者并不是等价的,因为仅仅是过后的 HotSpot 虚拟机设计团队抉择把收集器的分代设计扩大至办法区,或者说应用永恒代来实现办法区而已,这样使得 HotSpot 的垃圾收集器可能像治理 Java 堆一样治理这部分内存,省去专门为办法区编写内存治理代码的工作。然而对于其余虚拟机实现,譬如 BEA JRockit、IBM J9 等来说,是不存在永恒代的概念的。
1.6、运行时常量池
运行时常量池(Runtime Constant Pool)是办法区的一部分。Class 文件中除了有类的版本、字段、办法、接口等形容信息外,还有一项信息是常量池表(Constant Pool Table),用于寄存编译期生成的各种字面量与符号援用,这部分内容将在类加载后寄存到办法区的运行时常量池中。
Java 虚拟机对于 Class 文件每一部分(天然也包含常量池)的格局都有严格规定,如每一个字节用于存储哪种数据都必须符合规范上的要求才会被虚拟机认可、加载和执行,但对于运行时常量池,
《Java 虚拟机标准》并没有做任何细节的要求,不同提供商实现的虚拟机能够依照本人的须要来实现这个内存区域,不过一般来说,除了保留 Class 文件中形容的符号援用外,还会把由符号援用翻译进去的间接援用也存储在运行时常量池中。
运行时常量池绝对于 Class 文件常量池的另外一个重要特色是具备动态性,Java 语言并不要求常量 肯定只有编译期能力产生,也就是说,并非预置入 Class 文件中常量池的内容能力进入办法区运行时常量池,运行期间也能够将新的常量放入池中,这种个性被开发人员利用得比拟多的便是 String 类的 intern()办法。
既然运行时常量池是办法区的一部分,天然受到办法区内存的限度,当常量池无奈再申请到内存时会抛出 OutOfMemoryError 异样。
1.7、间接内存
间接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是《Java 虚拟机标准》中定义的内存区域。
在 JDK 1.4 中新退出了 NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的 I / O 形式,它能够应用 Native 函数库间接调配堆外内存,而后通过一个存储在 Java 堆外面的 DirectByteBuffer 对象作为这块内存的援用进行操作。这样能在一些场景中显著进步性能,因为防止了 在 Java 堆和 Native 堆中来回复制数据。
显然,本机间接内存的调配不会受到 Java 堆大小的限度,然而,既然是内存,则必定还是会受到 本机总内存(包含物理内存、SWAP 分区或者分页文件)大小以及处理器寻址空间的限度,个别服务器管理员配置虚拟机参数时,会依据理论内存去设置 -Xmx 等参数信息,但常常疏忽掉间接内存,使得 各个内存区域总和大于物理内存限度(包含物理的和操作系统级的限度),从而导致动静扩大时呈现 OutOfMemoryError 异样。
2、JDK 的内存区域变迁
2.1、jdk1.6/1.7/1.8 内存区域变动
在上一节提到了,HotSpot 虚拟机是是 Sun/OracleJDK 和 OpenJDK 中的默认 Java 虚拟机,是 JVM 利用最宽泛的一种实现。下面提到,Java 虚拟机标准对办法区的束缚很宽松,而且 HotSpot 虚拟机在这一区域产生过一些 bug,所以 HotSpot 的办法区经验了一些变迁,咱们来看看 HotSpot 虚拟机内存区域的变迁。
- JDK1.6 期间和咱们下面讲的 JVM 内存区域是统一的:
- JDK1.7 时产生了一些变动,将字符串常量池、动态变量,寄存在堆上
- 在 JDK1.8 时彻底干掉了办法区,而在间接内存中划出一块区域作为 元空间,运行时常量池、类常量池都挪动到元空间。
2.2、为什么替换掉办法区
办法区为什么被代替了呢?当然,或者更精确的说法应该是 永恒代 为什么被替换了?——Java 虚拟机标准规定的办法区只是换种形式实现。有主观和主观两个起因。
- 主观上应用永恒代来实现办法区的决定的设计导致了 Java 利用更容易遇到内存溢出的问题(永恒代有 -XX:MaxPermSize 的下限,即便不设置也有默认大小,而 J9 和 JRockit 只有没有触碰到过程可用内存的下限,例如 32 位零碎中的 4GB 限度,就不会出问题),而且有极少数办法(例如 String::intern())会因永恒代的起因而导致不同虚拟机下有不同的体现。
- 主观受骗 Oracle 收买 BEA 取得了 JRockit 的所有权后,筹备把 JRockit 中的优良性能,譬如 Java Mission Control 管理工具,移植到 HotSpot 虚拟机时,但因为两者对办法区实现的差别而面临诸多困难。思考到 HotSpot 将来的倒退,在 JDK 6 的 时候 HotSpot 开发团队就有放弃永恒代,逐渐改为采纳本地内存(Native Memory)来实现办法区的打算了,到了 JDK 7 的 HotSpot,曾经把本来放在永恒代的字符串常量池、动态变量等移出,而到了 JDK 8,终于齐全废除了永恒代的概念,改用与 JRockit、J9 一样在本地内存中实现的元空间(Meta-space)来代替,把 JDK 7 中永恒代还残余的内容(次要是类型信息)全副移到元空间中。
参考:
【1】:周志朋编著《深刻了解 Java 虚拟机:JVM 高级个性与最佳实际》
【2】:周志朋等翻译《Java 虚拟机标准》
【3】:内存篇:JVM 内存构造
【4】:这一次,终于零碎的学习了 JVM 内存构造
【5】:面经手册 · 第 25 篇《JVM 内存模型总结,有各版本 JDK 比照、有元空间 OOM 监控案例、有 Java 版虚拟机,综合学习更容易!》