JVM内存模型与对象垃圾回收分析

1.JVM

说明:本文是我对自己学习知识的一个简单总结,可能存在许多不足,我希望通过此方式来回顾知识,加强理解,也希望大家能指出文中的错误与不足,互相学习,谢谢。

  • jvm(java虚拟机)是java程序运行的地方,它存放了java对象的信息。java程序能够一次编译到处运行就是因为有jvm的存在,它是与操作系统交互的桥梁。之前的C语言在编写程序分配内存时候,如果不及时释放内存,就很容易造成内存泄漏,而jvm有自己的一套内存回收机制,所以java程序员有更多的精力去编写程序,而不是去担心内存泄漏的问题。
  • jvm结构图如下:
    在这里插入图片描述

这里简单说明:堆和方法区的数据是线程共享的,所以在多线程中需要注意这一部分数据的一致性问题,也就是线程安全问题。

  • 之前的一篇文章介绍过java类加载进jvm的过程,传送门:https://blog.youkuaiyun.com/qq_37803406/article/details/103332506 本文主要是介绍类加载进入jvm后对象的分配与方法的执行。

2.案例说明

  • 首先我们来看如下代码
public class Demo {
    public void compute(){
        int a = 1;
        int b = 2;
        int c = (a+b)*3;
        System.out.println(c);
    }
    public static void main(String[] args) {
        Demo demo = new Demo();
        demo.compute();
    }
}
  • 代码很简单,相信大家都能看明白,这里再简单总结下,一个Demo类里面有一个compute()方法,用来计算输出一个数值,在main()方法中实例化了这个类,然后调用了compute()方法。我下面将对这一系列操作做些分析。

3.java对象存放

  • 这里先简单介绍些模型与概念

在这里插入图片描述

  • 首先上面Demo类会通过类加载器将类的基本信息加载到方法区中。
  • 在main方法中通过new Demo();创建了一个Demo类的对象,该对象存放在堆内存中。
  • 在执行main方法时,先将main方法压到线程栈中执行,一个main方法对应了一个栈帧。通过javap -c Demo查看字节码,如下图所示:

在这里插入图片描述

  • main方法当执行到demo.compute()时候,【demo是new Demo()的一个引用,指向了堆内存地址】,这时候又会创建一个compute方法的栈帧,然后运行该方法。
  • 一个栈帧对应一个方法,栈帧的数据结构如上图,分别由局部变量表【这里对应上面compute方法中的a b c】,操作数栈【如上图,对应字节码最前面的序号,用来标识程序执行到哪一步了,当发生上下文切换时,能够指定程序该从哪一步运行】,动态连接【指向运行时常量池中该栈帧所属方法的引用,这里是demo】,方法出口信息【方法的返回值】。
  • 当compute方法执行完毕后输出结果,弹出栈,然后再执行main方法,main方法也执行完毕后也弹出栈,最后将对象通过垃圾回收器回收对象使用的内存空间。

4.java对象回收

  • 这里主要是分析对象在jvm中的回收,不会仔细介绍垃圾回收器和算法,只会简单提及,只会文章会有相关介绍。

  • java对象在实例化之后会在堆中分配一块内存使用,堆的内存结构如下所示
    在这里插入图片描述

  • 堆分为新生代和老年代【占堆内存的2/3】,新生代【占堆内存的1/3】,新生代中又分为Eden区【对象创建生成分配的内存空间】,from区和to区。

  • 例如,堆分配了3g大小的内存,分配堆内存大小通过配置jvm运行时参数 -xmx3072M -xms3072M来指定堆最大分配的内存和启动时分配的内存为3072M,这样新生代分到1024M内存,老年代分配到2048M内存。Eden分配到大约800M,from,to区分各配到大约100M。

5.垃圾回收

  • 怎样判断对象是否该回收呢?在JVM中有两种算法来实现这个判断,一个是引用计数法,另一个是可达性分析。这里具体算法的实现特点不做展开,可以自行百度了解。
  • 对象在Eden区创建,当Eden区满了会执行minor gc去回收垃圾,然后将存活的对象放到from区,当第二次执行minor gc后,会将Eden区存活的对象和from区存活的对象放到to区,当经历过几个周期后,还存活的对象就会存放到老年代中。
  • 这里回顾下对象的组成,对象由对象的头部信息,实例信息和对齐填充组成。头部信息就包含了gc的年龄信息【经历一次minor gc没有被回收,那么gc年龄+1,当达到一定年龄就会放到老年代中】

6.jvm简单调优分析

  • 有了上面这些预备知识,这里来分析下如何设置堆中各个部分的大小来达到JVM使用最优。
  • 这里有一点要说明:当老年代也存满对象后,会触发full gc,这时候会产生stw(stop-the-world 全局停顿),这时候系统会停止现在的工作进行垃圾回收,给用户的体验就是系统卡死了。
  • 接下来我们来看一个例子,类使用双十一这样的高并发情况,怎样设置每一个JVM的内存分配就很重要了,不合理的设置可能导致程序卡死,内存溢出。
  • 假设现在有一个高并发系统,系统每一秒可以产生80M的对象,之后这些对象就没有用了(变成垃圾对象),如下图所示:

在这里插入图片描述

  • 对于800M的Eden区来说,14秒后就会触发一次minor gc ,但有可能在第14秒最后一个对象还存活,这时候这个对象就会到from区,但是JVM中会对对象大小进行动态判断,如果对象大小大于from或to区的一半,那么该对象就会直接分配到老年代,那么经过【14*2048/60=478】478秒后就会触发full gc,系统就会长时间卡死。
  • 因为对象大小大于from或to区的一半,那么该对象就会直接分配到老年代,所以我们可以增加from和to区的大小,这样就几乎不会触发full gc了,我们可以将老年代设置为1g大小,新生代设置为2g大小,这时候from 和to区分配到200M内存,这样就几乎不会发生full gc了。
  • 如果full gc后内存任然不够,这时候就会抛出java.lang.OutOfMemoryError异常。

总结:jvm内存模型和jvm调优虽然平时接触不多,但是很重要,它是java运行的基础环境,当出现问题时候,我们了解了jvm才能分析出怎样去解决这些问题,本文从java对象的创建,到对象的销毁,到对象的回收做了简单分析介绍,但是jvm的知识远不止这些,还需要与大家一起交流学习,弥补不足,谢谢大家的支持。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值