你还在看《深入理解Java虚拟机》的运行时数据模型吗?

学习JVM必看的书籍无疑是《深入理解Java虚拟机》这本书了,在书中,关于运行时数据区域模型是这样描述的:

在这里我们只针对HotSpot VM来说,它是OracleJDK和OpenJDK中所带的虚拟机,也是目前使用范围最广的Java虚拟机。在JDK7之前,这样的模型是正确的。但是到了JDK8,如图标红的部分,做了一些优化。

什么是方法区,什么是永久代,运行时常量池又是什么

  • “方法区”(Method Area),是线程共享的区域,用于存储已被虚拟机加载的类信息,常量,静态变量等数据。首先我们要知道,方法区是JVM的一种规范,是一个概念,而这个方法区的具体实现由各个虚拟机厂商去实现。
  • “永久代”(Permanent Generation)就是HotSpot虚拟机对于方法区的实现,也仅仅是针HotSpot才有的。
  • “运行时常量池”是方法区的一部分。用于存放编译期生成的各种字面量和符号引用。其特性是具备动态性。

优化一:字符串常量池从永久代划到Java堆

由于常量池具备动态性,在程序运行过程中会有大量的字符串常量在运行时常量池里产生,此时如果放在永久代,则无法恰当的设定永久代的大小,容易出现性能问题和内存溢出。下面一个例子证明在JDK8中,字符串常量池已经放在堆中:

String.intern()方法的作用是返回一个字符串引用,引用的是字符串常量池中的字符串(字面量),我们先来验证一下这个方法:

public class StringConstantsPoolTest {
public static void main(String[] args) {
String str = "abc"; //  str存储在常量池
String str2 = new String("abc");  // str2 存储在堆中
System.out.println(str == str2); //  结果为false ,堆中的引用并不等于常量池中的引用
str2 = str2.intern(); // 获取str2在常量池中的引用
System.out.println(str == str2);
}
}

结果如下:

证明 String.intern()方法返回了一个在常量池中的引用。
下面验证字符串常量池在堆中:
设置JVM参数:

-Xms10m -Xmx10m -XX:-UseGCOverheadLimit

public static void main(String[] args) {
List<String> list = new ArrayList();
int i = 0;
while(true){
list.add(String.valueOf(i++).intern());
}
}

结果如下:

我们看到这时报的是Java堆空间内存溢出,说明字符串常量池是在堆中,注意,此时仅仅是字符串常量池转移到了堆中,但是运行时常量池依旧还是在方法区里

优化二:移除了永久代,引入“元空间”(Metaspace)

为什么移除永久代?

  1. 方法区大小难以设定,容易发生内存溢出 。永久代会存放Class的相关信息,一般这些信息在编译期间就能确定大小。但是如果是在一些需要动态生成大量Class的应用中,如:Spring的动态代理、大量的JSP页面或动态生成JSP页面等,由于方法区的大小在一开始就要分配好,因此就能难确定大小,容易出现内存溢出
  2. GC复杂且效率低 。方法区存储了类的元数据信息和各种常量,它的内存回收目标理应当是对这些类型的卸载和常量的回收。但由于这些数据被类的实例引用,卸载条件变得复杂且严格,回收不当会导致堆中的类实例失去元数据信息和常量信息。因此,回收方法区内存不是一件简单高效的事情。
  3. 促进HotSpot JVM与JRockit VM的融合 。JRockit没有方法区,移除永久代可以促进HotSpot JVM与JRockit VM的融合。

什么是元空间(Metaspace),为什么引入元空间

元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地 内存限制

元空间的特点:

  • 每个加载器有专门的存储空间。
  • 不会单独回收某个类。
  • 元空间里的对象的位置是固定的。
  • 如果发现某个加载器不再存活了,会把相关的空间整个回收。

总结

最终JVM(HotSpot)运行时数据区域模型如下:

原文地址:https://blog.51cto.com/13754022/2372059

时间: 2024-08-15 16:33:58

你还在看《深入理解Java虚拟机》的运行时数据模型吗?的相关文章

深入理解Java虚拟机:运行时数据区域

Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域.这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有些区域则依赖用户线程的启动和结束而建立和销毁.根据<Java虚拟机规范(Java SE 7版)>的规定,Java虚拟机所管理的内存将会包括以下几个运行时数据区域. 程序计数器 程序计数器(Program Counter Register)是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器.在虚拟机的概念模型里

深入理解Java虚拟机笔记---运行时栈帧结构

栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区的虚拟机栈(Virtual Machine Stack)的栈元素.栈帧存储了方法的局部变量表,操作数栈,动态连接和方法返回地址等信息.第一个方法从调用开始到执行完成,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程. 每一个栈帧都包括了局部变量表,操作数栈,动态连接,方法返回地址和一些额外的附加信息.在编译代码的时候,栈帧中需要多大的局部变量表,多深的操作数栈都已经完全确定了,并且写入到了方法表的

java虚拟机3.运行时内存异常

在java虚拟机规范的描述中,除了程序计数器外,虚拟机内存的其他几个运行时区域都有发生OutOfMemoryError异常的可能. java堆溢出 java堆用于存储对象实例,只要不断的创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象,那么在对象数量达到最大堆得容量限制后就会产生内存溢出异常.当出现java堆内存溢出时,异常堆栈信息"java.lang.OutOfMemoryError"会跟着进一步提示"java heap space&qu

java虚拟机2.运行时内存对象

对象的创建 虚拟机遇到一条new指令时,首先去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载.解析和初始化过. 在类加载检查通过后,接下来虚拟机将为新生对象分配内存.对象所需内存的大小在类加载完成后便可完全确定,为对象分配空间的任务等同于把一块确定大小的内存从java堆中划分出来. 假设java堆中内存是绝对规整的,所有用过的内存都放在一边,空闲的内存放在另一边,中间放着一个指针作为分界点的指示器,那分配内存就仅仅是把那个指针向空闲空间那边挪动

详细理解Java虚拟机的运行过程

基本概述: Java虚拟机简称JVM,是JRE中的一部分,也是Java程序运行的最关键的部分.完整的Java运行流程大致包括编译.java文件形成.class文件,然后根据.class文件的内容进行一系列的步骤使整个程序运行起来.其中将.java文件编译为.class文件由Java编译器完成,而由.class文件进而完成整个运行过程则主要是由JVM来完成的.由于所有的编译链接过程都由JRE来完成,所以Java具有良好的平台可移植性,不依赖具体的操作系统. 具体结构及功能: 1.Class Loa

深入JAVA虚拟机之运行时数据区

前言最近在啃一本书<深入JAVA虚拟机>,这本书不是第一次看,可以说是从大学就开始看,这一次应该算第三次啃这本书,也应该说算是第一次真正啃这本书.大学的时候,只是好奇表层的一些神奇现象,随着工作几年后,现在回过头来再次啃这本书,对于表层的那些以前觉得神奇的现在已经感觉乏味,反而对于底层是如何实现.如何运作的越来越着迷.这也是这次看这本书的初衷.通过写博客记录下自己的学习过程,也方便以后回头看看现在的看法想法在将来会变成怎样.如果我在下面的文字表述上或者理解上有误解或者错误,请各位大神能够留言指

《深入理解Java虚拟机》:类加载的过程

<深入理解Java虚拟机>:类加载的过程 类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载.验证.准备.解析.初始化.使用和卸载七个阶段.其中类加载的过程包括了加载.验证.准备.解析.初始化五个阶段. 下面详细讲述类加载过程中每个阶段所做的工作. 加载 加载时类加载过程的第一个阶段,在加载阶段,虚拟机需要完成以下三件事情: 1.通过一个类的全限定名来获取其定义的二进制字节流. 2.将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构. 3.在Java堆中生成一

深入理解java虚拟机-第六章

第6章 类文件 6.3 Class类文件的结构 Class文件是一组以8位字节为基础单位的二进制流. Class文件格式采用一种类似C语言结构伪结构存储数据,这种伪结构中只有两种数据类型:无符号数和表 无符号数属于基本的数据类型以u1,u2,u4,u8来分别代表一个字节.2个字节,4个字节.8个字节的无符号数,无符号数可以用来描述数字.索引引用.数量值或者按照UTF-8编码构成字条串值. 表是由多个无符号数或者其他表作为数据项构成的复合数据类型,所有表习惯性地以 _info 结尾. 表 Clas

深入理解java虚拟机(六)字节码指令简介

Java虚拟机指令是由(占用一个字节长度.代表某种特定操作含义的数字)操作码Opcode,以及跟随在其后的零至多个代表此操作所需参数的称为操作数 Operands 构成的.由于Java虚拟机是面向操作数栈而不是寄存器的架构,所以大多数指令都只有操作码,而没有操作数. 字节码指令集是一种具有鲜明特点.优劣势都很突出的指令集架构: 由于限定了Java虚拟机操作码的长度为1个字节,指令集的操作码不能超过256条.Class文件格式放弃了编译后代码中操作数长度对齐,这就意味者虚拟机处理那些超过一个字节数