JVM对象创建与内存回收机制

对象的创建过程有如下步骤:
在这里插入图片描述
1.类加载检查:
        虚拟机遇到一个new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过,如果没有,那么必须先执行相应的类加载过程
        new指令对应到语言层面上讲是,new关键词、对象克隆、对象序列化等。
2.分配内存
        对象所需内存的大小在类加载完成后便可完全确定,为对象分配空间的任务等同于把一块确定大小的内存从Java堆中划分出来。这个步骤有两个问题:

  1. 如何划分内存
  2. 在并发情况下,可能出现在给对象A分配内存,指针还没来得及修改,对象B又同时使用了原来的指针来分配内存的情况。
    划分内存的方法:
            “指针碰撞”(默认)(Bump the Pointer):
            如果Java堆中的内存是绝对规整的,所有用过的内存都放在一边,空闲的内存放在另一边,中间放着一个指针作为分界点的指示器,那所分配内存就仅仅是吧那个指针向空闲空间那边挪动一段与对象大小相等的距离。
            “空闲列表”(Free List)
            如果Java堆中的内存并不是规整的,已使用的内存和空闲内存相互交错,那就没有办法简单的进行指针碰撞了,虚拟机就必须维护一个列表,记录上哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录
            解决并发问题的方法:
            1.CAS(Compare and swap)
            虚拟机采用CAS配上失败重试的方式保证更新操作的原子性来分配内存空间的动作进行同步处理。
            2.本地线程分配缓冲(Thread Local Allocation Bufffer, TLAB)
            把内存分配的动作按照线程划分在不同的空间中进行,即每个线程在Java堆中预先分配一小块内存。通过-XX:+/-UseTLAB参数来设定迅即是否使用TLAB(JVM会默认开启-XX:+UseTLAB),- XX:TLABSize指定TLAB大小。

3.初始化
        内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),如果使用TLAB,这一工作过程也可以提前至TLAB分配时进行。这一步操作保证了对象的实例字段在Java代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。
4.设置对象头
        初始化零值之后,虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例,如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息。这些信息存放在对象的对象头Object Header之中。
        对象除了成员变量之外,还有对象头,对象填充,对象在内存中存储的布局可以分为3块区域:对象头、实例数据和对齐填充
        1.Mark Word标记字段(32位占4字节,64位占8字节)自身运行时数据哈希表,GC分代年龄,锁状态标志,线程执有锁,偏向线程ID,偏向时间戳。
        2.Klass Pointer类型指针(开启压缩4字节,关闭压缩8字节)。类的元数据指针(D。代码放在方法区,JVM使用Java头的类型指针拿到方法区的头。
        3.数组长度(4字节,只有数组对象才有)
5.执行(init)方法
        给对象真正的赋值以及执行构造方法。

对象大小与指针压缩

        启用指针压缩-XX:+UseCompressedOops(默认开启),禁止-XX:-UseCompressedOops
压缩对象地址到32位
为什么要进行指针压缩?

  1. 在64位平台的HotSpot中使用32位指针,内存使用会多出1.5倍左右,使用较大指针在主内存和缓存之间移动数据,占用较大带宽,同时GC也会承受较大压力。
  2. 为了减少64位平台下内存的消耗,启用指针压缩功能。
  3. 在JVM中,32位地址最大支持4G内存,可以通过对象指针的压缩编码、解码方式进行优化,使得JVM只用32位地址就可以支持更大的内存配置(小于等于32G)
  4. 堆内存小于4G时,不需要启用指针压缩,JVM会直接去除高32位地址,即使用低虚拟地址空间
  5. 堆内存大于32G时,压缩指针会失效,会强制使用64位堆Java对象寻址,这样会现1的问题,所以堆内存不要大于32G为好。

        -XX:+UseCompressedClassPointers默认开启的,只压缩对象头里的类型指针Klass Pointer对象内存分配。
        对象逃逸分析:比如User对象在方法执行后返回,则这个对象逃逸了,减少GC压力。
        -XX:DoEscapeAnalysis开启逃逸分析参数,JDK7后默认开启
        标量替换:一个对象如果经过逃逸分析能够确定这个对象在栈上分配,这里会做一个优化,即便栈帧里没有一整块空间放对象,也能够通过其他方法存在栈帧上。
        如果对象不逃逸,栈帧只放对象成员变量,把完整的对象打散放到栈帧上去,这几个变量都标识属于某一个对象。
        标量与聚合量:标量即不可以被进一步分解的量,而Java的基本数据类型就是标量,标量的对立就是可以被进一步分解的量,而这种量称为聚合量。而Java对象就是可以被进一步分解的聚合量。
        开启标量替换-XX:+EliminateAllocations,JDK7后默认开启
        -Xmx 15m -Xms 15m -XX:+DoEscapeAnslysis -XX:+printGC -XX:+ElininateAllocations.大对象直接进入老年代
        -XX:PretenureSizeThreshold=1000000 -XX:+UseSerialGC(支持大对象ParNew)。定义大对象配置,这个参数只在Serial和ParNew两个收集器下有效
        长期存活的对象将进入老年代。当它的年龄增加到一定程度,默认为15岁,CMS收集器默认为6岁,对象晋升到老年代的年龄阀值,通过-XX:maxTenuringThreshold设置对象动态年龄判断。

对象动态年龄判断:

        当前放对象的S区域里(其中一块区域,放对象的那块S区),一批对象总大小大于这块S区域内存大小的50%(-XX:TargetSurvivorRatio可以指定),那么此时大于等于这批对象年龄最大值的对象,就可以直接进入老年代了,例如Survivor区域里现在有一批对象年龄1+年龄2+年龄n的多个年龄对象总和超过了Survivor区域的50%,此时会把年龄n(含)以上的对象都放入老年代,这个规则其实是希望那些可能是长期存活的对象,尽早进入老年代,对象动态年龄判断机制一般是在minor gc之后触发。把年轻代设大点(-Xmn)

老年代空间分配担保机制

在这里插入图片描述
年轻代每次minor gc之前JVM都会计算下老年代剩余可用空间,如果这个可用空间小于年轻代里现有的所有对象大小之和(包括垃圾对象),就会看到一个“-XX:-HandlePromotionFailure”(jdk1.8默认设置了)的参数是否设置了。如果有这个参数,就会看看老年代的可用内存大小,是否大于之前每一次minor gc后进入老年代的对象的平均大小。如果上一步结果是小于或者之前说的参数没有设置,那么就会触发一次Full GC,对老年代和年轻代一起回收一次垃圾,如果回收完还是没有足够空间存放新的对象就会发生OOM。当然,如果minor gc之后剩余存活的需要挪动到老年代的对象大小还是大于老年代可用空间,那么也会触发Full GC,Full GC完之后如果还是没有空间放minor gc之后的存活对象,则也会发生OOM。

对象内存回收

堆中几乎放着所有的对象实例,对堆垃圾回收前的第一步就是要判断哪些对象已经死亡(即不能再被任何途径使用的对象)

引用计数法

给对象中添加一个引用计数器,每当有一个地方引用它,计数器就加一;当引用失效,计数器就减一;任何时候计数器为0的对象就是不可能再被使用的。
这个方法实现简单,效率高,但是目前主流的虚拟机中并没有选择这个算法来管理内存,其主要的原因是它很难解决对象之间相互循环引用的问题。

可达性分析算法

将“GC Roots”对象作为起点,从这些节点开始向下搜索引用的对象,找到的对象都标记为非垃圾对象,其余未标记的对象都是垃圾对象。
GC Roots根节点:线程栈的本地变量、静态变量、本地方法栈的变量等等

常见引用类型

Java的引用类型一般分为四种:强引用、软引用、弱引用、虚引用
软引用:将对象用SoftReference软引用类型的对象包裹,正常情况不会被回收,但是GC做完后发现释放不出空间存放新的对象,则会把这些软引用的对象回收掉,软引用可用来实现内存敏感的告诉缓存。
软引用在实际中有重要的应用,例如浏览器的后退按钮。按后退时,这个后退时显示的网页内容时重新进行请求还是从缓存中取出呢?这就要看具体的实现策略了。
(1)如果一个网页在浏览器结束就进行内容的回收,则按后退查看前面浏览过的页面时,需要重新构建。
(2)如果将浏览过的网页存储到内存中会造成内存的大量浪费,甚至会造成内存溢出
软引用、弱引用和虚引用在GC时没空间时会被回收。

finalize()方法最终判定对象是否存活。

如果你的对象实现了finalize,这个对象会做标记,不会马上回收,第二次标记,清理掉前执行该方法,finalize只会自救一次。

如何判断一个类是无用的类

方法区主要回收的是无用的类,那么如何判断一个类是无用类呢?

  • 类在方法区,该区要被回收满足该类的实例都被回收。该类所有的实例都已经被回收,也就是Java堆中不存在任何该类的实例
  • 加载类的Class Loader被回收了,自定义的类加载器是有可能需要回收,比如JSP类加载器。
  • 该类对应的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值