JVM-方法区

方法区

JAVA技术交流群:737698533

方法区是运行时数据区的最后一个内容,Method Area

JVM-方法区

栈,堆,方法区中的交互关系

JVM-方法区
JVM-方法区

方法区简述

方法区(Method Area),与java堆一样,是各个线程共享的内存区域,它用于存储已经被虚拟机加载的类型信息,常量,静态变量,及时编译后的代码缓存等数据

java虚拟机规范中明确说明:”尽管所有的方法区在逻辑上是属于堆的一部分,但是一些简单的实现可能不会选择去进行垃圾回收或进行压缩”,但是对于HotSpot JVM 而言,方法区还有一个别名叫做Non-Heap(非堆),目的就是要和堆区分开来

所以,方法区看作是一块独立于堆的内存空间

方法区在JVM启动时候就被创建,并且它的实际物理内存空间和java堆区一样都是可以不连续的,方法区的大小可以选择固定大小,也可以选择可扩展,方法区的大小决定了可以保持多少个类,如果系统定义了太多的类,导致方法区溢出,虚拟机同样会抛出内存溢出异常:java.lang.OutOfMemoryError:PermGen Space或java.lang.OutOfMemoryError,为什么是两种异常呢?因为在JDK8之前,方法区又称为”永久代”,而JDK8以及8之后改为”元空间”

方法区,元空间,永久代三者关系是什么呢?方法区是java虚拟机规范的一部分,而元空间和永久代是一个具体的实现,元空间的本质和永久代类似,都是对JVM规范方法区的实现,不过两者最大的区别就是: 元空间不在虚拟机中设置内存,而是直接使用本地内存

JVM-方法区

设置方法区大小

JDK1.7及以前

  • -XX:PermSize来设置永久代初始分配空间,默认为20.75m
  • -XX:MaxPermSize来设定永久代最大可分配空间,32位机器默认64m,64位机器默认82m
  • 当jvm加载类信息超过最大容量,会报OutOfMemoryError:PermGen Space

JDK1.8以及后

  • 元数据区大小使用参数-XX:MetaSpaceSize和-XX:MaxMetaspaceSize指定,来替代jdk7原有的两个参数
  • 默认值依赖于平台,windows下默认初始化大小为21M,最大值为-1,及没有限制
  • 于永久代不同,如果不指定大小,默认情况下,虚拟机会耗尽所有的可用系统内存,如果元数据区发生移除,虚拟机一样也会抛出异常OutOfMemoryError:Metaspace
  • -XX:MetaspaceSize:设置初始的元空间大小,对于一个64位的服务器端JVM来说,其默认的内存大小为21MB,这就是初始的高水位线,一旦触及这个水位线,Full GC将会触发并卸载没有用的类(即这些类对应的类加载器不在存活),然后这个高水位线会重置,新的高水位线取决于GC后释放了多少元空间,如果释放的空间不足,那么在不超过MaxMetaspaceSize的情况下适当提高该值,如果释放空间过多,则适当降低该值
  • 如果初始化高水位线设置过低,上述高水位线调整情况会发生很多次,通过垃圾回收日志可以观察到Full GC多次调用,为了避免频繁GC,建议将-XX:MetaspaceSize设置为一个相对较高的值

方法区内部结构

类型信息

对每个加载的类型(类Class,接口interface,枚举enum,注解annotation),jvm必须在方法区存储以下类型信息

  1. 类型的完整有效名称,全限定名
  2. 类型直接父类的全限定名
  3. 类型的修饰符(public,abstract,final的某个子集)
  4. 类型直接接口的一个有序列表

域(Field)信息 (属性,字段)

  • JVM必须在方法区中保存类型的所有域的相关信息以及域的声明顺序
  • 域的相关信息包括:域名称,域类型,域修饰符(public,private,protected,static,final,volatile,transient的某个子集)

方法(Method)信息

JVM必须保存所有方法的一下信息,同域信息一样包括声明顺序

  • 方法名称
  • 方法的返回类型(或void)
  • 方法参数的属性和类型,按照顺序
  • 方法的修饰符(public,private,protected,static,final,synchronized,native,abstract的子集)

non-final的类变量

静态类变量和类关联在一起,随着类的加载而加载,它们成为类数据在逻辑上的一部分,类变量被类所有实例共享,即使没有类实例时也可以访问

全局常量(static final)

被声明为final的类变量处理方式则不同,每个全局变量在编译的时候就会被分配了

运行时常量池

  • 运行时常量池(Runtime Constant Pool) 是方法区的一部分
  • 常量池表(Constant Pool Table)是Class文件的一部分, 用于存放编译期间生成的各种字面量与符号引用, 这部分内容将在类加载后存放到方法区的运行时常量池中
  • 当类和接口加载到虚拟机后,就会创建对应的运行时常量池
  • JVM为每个已加载的类型(类或接口)都维护一个常量池,池中的数据项像数组项一样,是通过索引访问的
  • 运行时常量池中包含多种不同的常量,包括编译期间就已经明确的数值字面量,也包括到运行期解析后才能获得的方法或字段引用,此时不再是常量池中的符号地址,这里转换为真实地址
  • 运行时常量池对比Class文件的常量池的另一重要特性是具备动态性
  • 运行时常量池类似于传统编程语言的符号表,但是它所包含的数据却比符号表要更加丰富一些
  • 当创建接口或类的运行时常量池时,如果构造运行时常量池所需的内存空间超过了方法区能提供的最大空间,则JVM会抛出OutOfMemoryError异常

方法区的演进细节

只有HotSpot才有永久代,HotSpot中方法区的变化:

jdk1.6即之前:有永久代(permanent generation),静态变量存放在永久代上

jdk1.7: 有永久代,但是已经逐步”去永久代”,字符创常量池,静态变量移除,存放在堆空间中

jdk1.8及以后:无永久代,类型信息,字段,方法,常量保存在本地内存的元空间,但字符创常量池,静态变量仍在堆空间

永久代为什么要替换为元空间

  1. 永久代设置空间大小难以确定,如果设置比较小容易发生FullGC影响程序性能,而且容易出现OOM,如果过大又占用内存
  2. 对永久代的调优是很困难的

判断一个常量是否”废弃”是相对简单的,而要判断一个类型是否属于”不再被使用的类”的条件就比较苛刻了,需要同时满足3个条件

  1. 该类所有实例都已经被回收,也就是java堆中不存在该类以及任何派生子类的实例
  2. 加载该类的类加载器已经被回收,这个条件除非精心设计的可替换类加载器的场景,如OSGi,JSP的重加载等,否则通常很难达成
  3. 对应该类的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法

java虚拟机被允许对满足上述3个条件的无用类进行回收,这里说仅仅是”被允许”,而不是和对象一样,没有引用了就必然进行回收,关于是否要对类型进行回收,HotSpot虚拟机提供了-Xonclassgc参数进行控制,还可以使用-verbose:class以及-XX:+TraceClass-Loading,-XX:+TraceClassUnLoading查看类加载和卸载信息

在大量使用反射动态代理CGLIb等字节码框架,动态生成JSP以及OSGI这类频繁自定义类加载器场景中,通常都需要java虚拟机具备类型卸载的能了,以保证不会对方法区造成过大的内存压力

StringTable为什么调整

jdk7中将StringTbale放到了堆空间,因为永久代中回收频率很低,在Full GC 时候才会回收,而Full GC是老年代的空间不足,永久代空间不足时才会触发,这就导致StringTable回收频率不高,而我们开发中会有大量字符串创建,回收效率低导致永久代内存不足,放到堆里能及时回收内存

对象实例化内存布局和访问定位

对象的实例化步骤

  1. 判断对象对应的类是否加载,链接,初始化
  2. 虚拟机遇到一条new指令,首先去检查这个指令的参数能否在MetaSpace的常量池中定位到一个类的符号引用,并检查这个符号引用代表的类是否已经加载,解析,初始化(即判断这个类元信息是否存在),如果没有,那么在双亲委派机制模式下,使用当前类加载器ClassLoader+包名+类名为key进行查找对应的class文件,如果没有找到文件,则抛出ClassNotFontException异常.如果找到,则进行类加载,并生成对应的Class类对象
  3. 为对象分配内存,首先计算对象占用空间的大小,接着在堆空间划分一块内存给新对象,如果实例成员变量是引用变量,仅分配引用变量即可,即4字节大小
  4. 如果内存规整-指针碰撞:如果内存是规整的,那么虚拟机将采用指针碰撞来为对象分配内存,意思是所有用过的内存放在一边,空闲内存在另一边,中间放着一个指针作为分界点的指示器,分配内存就是将指针向空闲内存方向移动与对象大小相同的距离,如果垃圾收集器选择的是Serial,ParNew这种基于压缩算法的,虚拟机采用这种分配方式,一般带有compact(整理)过程的收集器,使用指针碰撞
  5. 如果内存不规则-空闲列表分配:如果内存不是规整的,已经使用和未使用的内存相互交错,那么虚拟机采用的是空闲列表法来为对象分配内存,虚拟机维护一个列表,记录上那些内存块是可用的,在分配时从表中找到一块足够大的空间来分配对象实例,并更新表上的内容,这种分配方式称为”空闲列表(Free List)”
  6. 至于选择哪种分配方式由java堆是否规整决定,而java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定
  7. 处理并发安全问题
  8. 采用CAS失败重试,区域加锁保证更新的原子性
  9. 每个线程设预先分配一块TLAB
  10. 初始化分配到的空间
  11. 所有属性设置默认值,保证对象实例字段在不赋值时可以直接使用
  12. 设置对象头
  13. 将对象的所属类(即类的元数据信息),对象的HashCode和对象的GC信息,锁信息等数据存储在对象头中,这个过程具体设置方式取决于JVM实现
  14. 执行init方法进行初始化
  15. 在java程序视角来看,初始化才正式开始,初始化成员变量,执行实例化代码块,调用类的构造方法,并把堆内对象的首地址赋值给引用变量,因此一般来说(由字节码是否跟随有invokespecial指令所决定),new指令之后会紧接着就是执行方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全创建出来

对象内存布局

对象头

  1. 运行时元数据(Mark Word)
  2. 哈希值(HashCode)
  3. GC分代年龄
  4. 锁状态标志
  5. 线程持有的锁
  6. 偏向线程ID
  7. 偏向时间戳
  8. 类型指针
  9. 指向类元数据InstanceKlass,确定该对象所属类型
  10. 如果是数组,还需要记录数组长度

实例数据(InstanceData)

它是对象真正存储的有效信息,包括程序代码中定义的各种类型的字段(包括从父类继承下来的和本身拥有的字段)

  1. 相同宽度的字段被分配在一起
  2. 父类中定义的变量会出现在子类之前
  3. 如果CompactFields参数为true(默认为true),子类的窄变量可以插入到父类变量的空隙

对齐填充(Padding)

不是必须,也没有特别含义,仅仅起到占位符的作用

public class Customer {
    int id = 100;
    String name;
    Account account;

    {
        name = "小明";
    }

    public Customer() {
        account = new Account();
    }

}

class Account {

}
//=========
public class Demo {

    public static void main(String[] args) {
        Customer c = new Customer();
    }
}

JVM-方法区

对象访问方式

由于reference类型在java虚拟机规范中并没有定义这个引用应该通过什么方式去定位,所以对象访问方式也是由虚拟机自己决定的,主流的访问方式主要有两种:直接指针和句柄访问

句柄访问

JVM-方法区

直接指针

JVM-方法区

这两种方式各有优势,使用句柄访问的最大好处就是reference中存储的是稳定的句柄地址,在对象被移动(垃圾回收时移动对象是非常普遍的行为)时只需要修改句柄的实例指针,而reference本身不需要修改,而使用直接指针访问的最大好处就是快,因为少一次指针定位的时间开销, 在HotSpot虚拟机中采用的是直接指针

直接内存

直接内存并不是虚拟机运行时数据区的一部分,也不是java虚拟机规范中的内存区域,在JDK1.4中新加入了NIO类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作,这样能在一些场景中显著提升性能,因为避免了在java堆和native堆中来回复制数据,直接内存分配不会受到java堆大小的印象,但是既然是内存,则肯定会受到本机内存大小的限制,如果内存区域大于物理内存限制,则会抛出OOM异常

直接内存大小可以通过MaxDirectMemorySize设置,如果不指定,默认大小与堆的最大值-Xmx参数值一致

Original: https://www.cnblogs.com/sunankang/p/14396244.html
Author: Jame!
Title: JVM-方法区

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/620602/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • SQL Server 2022来了

    SQL Server 2022来了 微软SQL Server依然保持着3年内发布一个大版本的传统,最新版本已经来到SQL Server2022 相关特性双向HA/DR 到Azure…

    数据库 2023年6月9日
    0129
  • Java学习-第一部分-第二阶段-项目实战:坦克大战【3】

    坦克大战【3】 笔记目录:(https://www.cnblogs.com/wenjie2000/p/16378441.html) 坦克大战0.6版 √增加功能 2. 记录玩家的成…

    数据库 2023年6月11日
    0134
  • [LeetCode]9. 回文数

    判断一个整数是否是回文数。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。 示例 1: 输入: 121输出: true示例 2: 输入: -121输出: false解…

    数据库 2023年6月9日
    0192
  • 草图?不管黑猫白猫,能把你的设计理念讲清楚才行

    我在日常工作中,经常要参加一些技术活动,或被拉去参加一些需求会或运营会,时间比较分散。 上周在参加一个代码评审时,发现程序上该复用的没有复用,却写了两份逻辑几乎相同的代码。另外,还…

    数据库 2023年6月9日
    0112
  • Redis-缓存和数据库一致性问题

    三种策略 Cache Aside 只读缓存模式,即读操作命中缓存直接返回,未命中从后端数据库加载到缓存再返回。写操作直接更新数据库,并删除缓存。👍一切以后端数据库为准,最常用的方式…

    数据库 2023年6月11日
    0135
  • Our Feeling

    走过春夏秋冬走过五湖四海就是没有走过你 看过日出日落看过潮起潮落就是看不到你 本文来自博客园,作者:ukyo–BlackJesus,转载请注明原文链接:https://…

    数据库 2023年6月11日
    0134
  • 23种设计模式之迭代器模式

    文章目录 概述 迭代器模式的优缺点 迭代器模式的结构和实现 * 模式结构 模式实现 总结 ; 概述 迭代器模式就是顺序访问聚集中的对象,一般来说,集合中非常常见,如果对集合类比较熟…

    数据库 2023年6月6日
    088
  • Javaweb-文件上传和邮件发送

    1.文件上传 新建空项目 准备工作 在maven仓库里下载commons io 和 commons fileupload两个jar包 实用类介绍 文件上传注意事项 为保证服务器安全…

    数据库 2023年6月16日
    0103
  • day44-反射03

    Java反射03 3.通过反射获取类的结构信息 3.1java.lang.Class类 getName:获取全类名 getSimpleName:获取简单类名 getFields:获…

    数据库 2023年6月11日
    099
  • macOS快捷键

    1. 最小化所有应用程序 command+option+h+m 2. 同应用窗口切换 command ~ 3. 截图 "全&a…

    数据库 2023年6月14日
    094
  • day03-2无异常退出

    多用户即时通讯系统03 4.编码实现02 4.3功能实现-无异常退出系统 4.3.1思路分析 上述代码运行时,在客户端选择退出系统的时候,可以发现程序并没有停止运行,原因是: 退出…

    数据库 2023年6月11日
    0111
  • 浅谈DDD中的聚合

    DDD分为战略部分跟战术部分,相信大家都认同DDD的核心在战略而非战术。而战略方面的核心我认为在业务建模,领域划分、统一语言等都在为业务建模服务。 为什么业务建模重要? 以前的开发…

    数据库 2023年6月14日
    0100
  • String字符串用逗号拼接,防止最后一位是逗号

    StringBuilder sb = new StringBuilder(); for(String s strArr) { if (sb.length() > 0) {//…

    数据库 2023年6月16日
    0135
  • Vue 和 Django 实现 Token 身份验证

    使用 Django 编写的 B/S 应用通常会使用 Cookie + Session 的方式来做身份验证,用户登录信息存储在后台数据库中,前端 Cookie 也会存储少量用于身份核…

    数据库 2023年6月14日
    0102
  • macbook air 2019 安装win10单系统

    目前不考虑写的太详细了,如果有同学遇到问题了我再完善,主要是把遇到的坑讲下第一步,准备2个U盘(如果不嫌麻烦一个也可以)1.用大白菜或者老毛桃将其中一个做成启动盘2.在window…

    数据库 2023年6月9日
    0367
  • 排序规则

    一、什么是排序规则 mysql官网的说法 The collation is a set of rules (only one rule in this case): “…

    数据库 2023年5月24日
    0189
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球