JVM对象内存分配

JVM对象内存分配

对象创建流程

在这里插入图片描述

内存分配方法

类检查通过后,JVM会给对象在对内存中分配空间。
内存分配方法:指针碰撞和空闲列表
指针碰撞:java内存是绝对规整的,用过的内存放一边,空闲的内存放一边,中间放着指针,为对象分配内存即把指针向空闲的那边挪动对象大小的内存空间
空闲列表:内存不规整的情况下,JVM维护了一张空闲列表,记录哪些内存是可用的,分配时从列表上找到一个足够大的空间给实例对象。

内存分配的并发问题

可能出现多个线程创建对象实例,分配内存的情况
解决办法:CAS或者TLAB本地线程分配缓冲
CAS(compare and sweap)乐观锁,多线程情况下,不加锁,若没有发生冲突,结果和预期相同则进行下一步操作,否则重试直到成功。一般使用加上volatile一起使用,cas保证了原子性,volatile保证了变量的可见性。
TLAB(Thread Local Allocation Buffer)本地线程分配缓冲,每个线程在堆中预先分配了一小块内存空间。通过­XX:+/­UseTLAB参数来设定虚拟机是否使用TLAB(JVM会默认开启­XX:+UseTLAB),­XX:TLABSize 指定TLAB大小。

对象头

所有的类信息其实是放在方法区中,C++代码实现的,堆中的对象指针指向方法区中的类的信息。堆中类的信息相当于是C++类信息的镜像,给开发人员使用(反射得到类的名称,构造方法,方法之类的)
对象头:主要有2部分组成,标记字段(hash码,GC分代年龄,锁状态等)和类型指针(堆中的对象指向类元信息的指针)
在这里插入图片描述

例子:
/**
 * 查看对象头,计算对象大小
 */
public class ClassHeader {
    public static void main(String[] args) {
        System.out.println("---------Object对象---------");
        ClassLayout classLayout = ClassLayout.parseInstance(new Object());
        System.out.println(classLayout.toPrintable());

        System.out.println("---------数组对象---------");
        ClassLayout classLayout1 = ClassLayout.parseInstance(new int[]{});
        System.out.println(classLayout1.toPrintable());
        

        System.out.println("---------实例对象---------");
        ClassLayout classLayout2 = ClassLayout.parseInstance(new A());
        System.out.println(classLayout2.toPrintable());

    }

    private static class A {
        int id;
        String name;
        byte b;
        Object o;
    }
}
执行结果

在这里插入图片描述

指针压缩

jdk6以后默认开启指针压缩,默认为4字节,能够节约空间内存并且提高cpu运行效率。
使用小的指针在内存与缓存直接移动数据占用更小的带宽,GC承受更小的压力。
若堆内存大于32G,指针压缩失效,依旧是8字节

计算机机位32bit,64bit

计算机机位32bit,64bit是代表cpu的寻址能力,寄存器位宽,一次处理的位数。32bit寻址能力2^32约等于4G,内存再大就浪费了,cpu寻址能力最多4G

对象逃逸分析

若对象只在方法内有效,不会被外部访问(如设置完属性无return出去),相反的则是逃逸对象。把非逃逸对象分配到栈内存中,方法执行完出栈就自动释放内存空间,无需GC。

标量替换

标量:不可再被分解的类(如基本数据类型和引用数据类型)
若栈中没有足够大的连续内存分配给非逃逸对象,JVM不会创建该对象,而是存类的成员变量和标记(标记是属于哪个类的)。
开启标量替换参数(-XX:+EliminateAllocations)jdk7以后自动开启
一般栈上分配依赖与逃逸分析+标量替换

堆内存分配(对象动态年龄判断)

年龄1+年龄2+年龄n的多个年龄对象总和超过了Survivor区域的50%,此时就会把年龄n(含)以上的对象都放入老年代。对于朝生夕死的对象,可以将年轻代调大一些,大一些的对象就不会直接进入老年代触发full gc了

老年代内存分配担保机制

减少minor gc的次数,提高效率。在进行minor gc前先判断老年区的内存是否存当前eden区的对象,若有直接进行minor gc。如果没有,看看有没有设置老年代内存分配担保机制(历史上minor gc移到老年代的平均对象大小是否比老年代小)若小则进行minor gc,否则进行full gc再进行minor gc,能够减少minor gc的次数,速度更快,效率更高。
在这里插入图片描述

对象内存回收方法

对象内存回收方法:引用计数法和可达性分析法
引用计数法:每个对象有一个引用计数属性,新增一个引用时计数+1,释放时计数-1,当计数为0时可以回收。
可达性分析法:将“GC Roots” 对象作为起点,从这些节点开始向下搜索引用的对象,找到的对象都标记为非垃圾对象,其余未标记的对象都是垃圾对象
GC Roots根节点:线程栈的本地变量、静态变量、本地方法栈的变量等等

引用类型

强引用:GC不会回收,new()出来的对象
软引用:GC回收,new()对象作为参数如网站的回退,存入缓存不存入内存,有没有影响不大,垃圾回收了网页缓存对象,可以再重新构建。
弱引用:GC回收,将对象用WeakReference软引用类型的对象包裹,弱引用跟没引用差不多,很少用。
如:此处的ThreadLocal是弱引用。Thread维护ThreadLocalMap存储数据的容器
ThreadLocalMap由一个个Entry对象组成,Entry对象的键:ThreadLocal对象 值:当前线程所对应的值
虚引用:GC回收,虚引用也称为幽灵引用或者幻影引用,它是最弱的一种引用关系,几乎不用

finalize()方法

一个对象再回收之前若实现了finalize()方法,会先执行此方法再进行GC,若此对象和引用链上的对象建立起关联即不会被GC,一个对象的finalize()方法只会执行一次。不推荐使用

full gc垃圾回收

full gc不仅会收集整个堆中的垃圾对象,而且会回收方法区(元空间)类信息,但回收方法区的类信息内存空间较少因为要求比较严格
元空间回收满足要求
1.对象的实例在堆中全无引用的实例
2.类加载器被回收
3.其他地方无引用,无法通过反射来访问此类

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值