Java 内存区域

Java 内存区域

​ java 的内存区域主要通过逻辑分为

  • 程序计数器(Proram Counter Register)

    主要是记录程序执行到的位置

  • Java 虚拟机栈(Java Virtual Machine Stack)

    主要是记录 Java 中的方法调用

  • 本地方法栈(Native Method Stacks)

    主要是记录 Java 中执行 Java 本地的方法(Native 修饰的方法)

  • Java 堆(Java Heap)

    主要是分配创建对象的内存信息

  • 方法区(Method Area)

    用于存储被虚拟机加载的类型信息,常量,静态变量,即时编译器(会直接将热点代码编译成本地代码)编译后的代码缓存数据

  • 运行时常量池(Runtime Constant Pool)

    是方法区的一部分,用于存放编译期生成的各种字面量与符号引用。类加载后存放到方法区的运行时常量池中

  • 直接内存(Direct Memory)

    是虚拟机运行时数据区的一部分

Java 对象创建

  1. JVM 在遇见 new 指令时
  2. 检查是否在常量池中有一个定位到的符号引用,并且检查符号引用代表的类是否进行类加载的过程
    1. 如果没有经历过类加载过程,就会进行类加载
  3. 检查过后就会在堆空间对新的对象分配内存(具体大小由类加载过程中决定)
    1. 如果 Java 堆中内存是规整的,在堆中的空闲内存和非空闲内存是分开的,当将内存指针向空闲内存空间方法挪动一段与对象大小相等的距离,这种分配的方式被称为“内存碰撞”(Bump The Pointer)
    2. 如果 Java 堆中内存是不规则的,堆中的空间空闲内存和使用的内存相互交错,这种情况虚拟机就会维护一个列表,记录那一些内存可用,在其中找到可用的内存划分给对象实例,并且更新对应的列表,这种分配方式为“空闲列表”(Free List)
    3. 分配对象是频繁发生的行为,在分配内存的时候是会出现线程安全的问题的,需要考虑线程的安全性
      1. 可以将分配内存的方法进行同步处理(加锁)
      2. 可以在不同的内存中进行分配内存,预先分配一小块内存,称为本地分配缓存(Thread Local Allocation Buffer,TLAB),只有本地缓存被使用完了,分配性的缓存区的时候才进行同步**(通过 -XX:+UseTLAB 参数决定是否开启)**
  4. 内存分配完成后会将对象中的属性初始化为默认值(保证在不给默认值的时候属性可用)
  5. 设置对象的必要属性(GC 年龄,Hash 码)
  6. 调用 <init>() 构造函数

对象内存布局

​ 主要分为三部分:对象头(Header),实例数据(Instance Data),对象填充(Padding)

对象头(Mark Work)

​ 存储对象自身的运行时数据**(哈希码(HashCode),GC 分代年龄,锁状态标志位,线程的持有锁,偏向锁的线程 ID,偏向时间戳)这些长度为 32 比特 和 64 比特**。由于需要利用虚拟机的空间效率,Mark Work 被设计成动态的,可以根据对象状态复用自己的内存空间

存储内容标志位状态
对象哈希码,对象的分代年龄01未锁定
指向锁记录的指针00轻量级锁定
指向重量级锁的指针10膨胀(重量级锁定)
空,不需要记录信息11GC 标志位
偏向锁线程 ID,偏向时间戳,对象分代年龄01可偏向

​ 对象头一部分是类型指针(指向其元数据),确定对象是哪一个类的实例,如果对象是一个 Java 数组,对象头中还需要用于记录数组的长度

​ 接下来实例数据部分是存储有效信息**(代码在程序中所定义的各种类型字段类型(包括父类继承下了的数据))这部分数据受到虚拟机分配策略参数(-XX:FieldsAllocationStyle 参数)和字段在 Java 源码中定义的顺序影响(默认的分配顺序 longs/doubles、ints、shorts/chars、bytes/booleans、oops(Ordinary Object Pointers,OOPS))**。父类中出现的属性可能出现在子类前,如果使用 -XX:CompactFields 参数(默认为 true)运行窄的变量允许插入到父类变量的间隙中

​ 对象的对齐填充,是占位符的作用。填充属性到 8 的整数倍

对象的访问定位

​ 创建对象自然是为后续对象使用,Java 对象会通过栈上的 reference 数据类来操作堆上的具体的对象,但是未规定具体的访问方式所以具体的的访问定位是由具体的虚拟机实现的主要是两种:

  • 句柄访问

    将堆空间划分一块内存作为句柄池,其中包括到对象的实例数据指针到对象类型数据的指针

  • 指针访问

    其中的对象类型数据指针是储存在对象的实例数据中

​ 句柄访问的方法如果对象的 reference 中的存储的稳定的句柄地址,在对象移动(垃圾收集时移动对象的)时只会改变句柄池中的实例数据指针而不会改变其 reference 中的值

​ 直接类型指针速度快,节省了一次指针定位的时间开销,在对象访问频繁的情况下可以减少开销

OutOfMemoryError 异常情况

Java 堆溢出

​ 使用虚拟机参数:-Xms(设置堆的最小配值) -Xmx(设置堆的最大值)

​ java.lang.OutOfMemoryError: Java heap space

​ 是给新的对象分配内存空间的时候,发现堆内存空间不够新的对象分配,说明堆内存中有未被垃圾收集器处理的垃圾对象(未被使用的对象)

虚拟机栈和本地方法栈溢出

​ 对于 HotSpot 拥有 -Xoss 设置本地方法栈大小(设置没有效果),-Xss 设置栈容量(唯一设置栈的深度)

​ 使用 JVM 参数:-Xss(设置栈的深度)

  • 对于线程请求深度大于虚拟机允许的最大深度抛出:StackOverflowError 异常

    当栈帧太大还是虚拟机栈太小,当新的栈帧内存无法分配的时候就会抛出该异常

  • 如果虚拟机栈允许动态扩展,当前栈容量无法申请到足够内存是会抛出:OutOfMemoryError

    当虚拟机允许动态扩展栈的深度的时候,如果栈帧无法分配内存的时候就会抛出该异常。一直创建新的线程的时候就会出现该异常(每个线程都会分配对应的独立的内存,如果分配到栈的内存越大,那么线程创建的数量就会减少,建立新的线程的时候就会出现该异常)

方法区和运行时常量池溢出

​ Java8 舍弃了之前的永久代而替换为了元空间,所以对于方法区在永久代和元空间有所不同

永久代

​ 使用的 JVM 参数:-XX:PermSize=6M -XX:MaxPermSize=6M

​ 当出现溢出的时候就会出现 java.lang.OutOfMemoryError: PermGen space

​ 说明运行时常量池时属于方法区的(JDK6 之前的 HotSpot 虚拟机的永久代的一部分)

元空间

​ 使用 JVM 参数:-XX:MaxPermSize(JDK7 使用设置永久带大小) -XX:MaxMetaspaceSize(JDK8 设置元空间大小)

​ 当出现溢出时错误就是 java.lang.OutOfMemoryError: Java heap space

​ 主要是 JDK7 是原本的存放在永久代的字符常量池移到了 Java 堆中,而 JDK8 中使用了元空间替代了永久带,字符常量池也存在堆空间中,所以 JDK7 和 JDK8 如果常量池无法分配内存时都会抛除该异常

String.intern 返回引用测试

​ String.intern():是 native 方法(本地方法),如果常量池中不存在此字符串就会加入到常量池中,如果存在就返回其对应的引用

    public static void main(String[] args) {
        String s1 = new StringBuilder("计算机").append("软件").toString();
        System.out.println(s1.intern() == s1);

        String s2 = new StringBuilder("ja").append("va").toString();
        System.out.println(s2.intern() == s2);
    }		

​ 在 JDK6 中会得到两个 false:由于调用 .intern() 其会将其加入到常量池中,但是 StringBuilder 创建的对象在 Java 堆中所有会出现两个 false(一个对象的引用在常量池,一个对象引用在堆空间)

​ 在 JDK7 及以上版本会出现一个 true 和 一个 false。

  • 出现 true:常量池已经移植到了 java 堆中,使用 intern() 方法就满足首次出现实例的引用,所以 StringBuilder() 返回的引用和 StringBuilder 创建的引用是同一个
  • 出现 false:是由于 java 这个关键字在使用 toString() 方法前就已经出现在了 常量池中,使用 intern 获取的对象引用和 StringBuilder 创建的引用不同
使用 CGLIB 使得方法区出现内存溢出异常

​ 因为 CGLib 代理会创建代理类,如果使用产生的大量的类信息就会导致内存溢出

​ 异常信息 java.lang.OutOfMemoryError: PermGen space

​ 这种异常出现在大量代理类生成的情况下

元空间设置参数
  • -XX:MaxMetaspaceSize:设置元空间最大的大小,默认是本地内存大小
  • -XX:MetaspaceSize:元空间初始大小,达到最大值的时候就会触发垃圾收集器对类进行卸载
  • -XX:MinMetaspaceSize:控制垃圾收集后最小的元空间剩余大小的百分比
  • -XX:MaxMetaspaceFreeRatio:控制最大元空间剩余容量百分比
本机直接内存溢出

​ 直接内存溢出,通过 -XX:MaxDirectMemorySize 指定,如果不指定,默认是 java 堆的最大值(一般通过 -Xmx 指定)

​ 会直接抛出 java.lang.OutOfMemoryError 异常

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值