JVM内存结构及垃圾回收

目录

一、JVM运行时数据区

二、堆里有哪些区域,Java8做了哪些改变

三、对象的生命周期  

四、垃圾回收


一、JVM运行时数据区

  • 方法区:用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据

                   (1)线程共享的

                   (2)运行时常量池:

                  A、是方法区的一部分
                         B、存放编译期生成的各种字面量和符号引用
                         C、Class文件中除了存有类的版本、字段、方法、接口等描述信息,还有一项是常量池,存有这个类的 编译期生成的各种字面量和符号引用,这部分内容将在类加载后,存放到方法区的运行时常量池中。

  • 程序计数器:指向当前线程正在执行的字节码指令,线程私有的,jvm会通过字节码引擎去执行我们的字节码指令,但java是多线程语言,当cpu轮转时,就需要一块这样线程独占的空间去记录字节码执行的现场和位置。
  • 虚拟机栈:虚拟机栈是Java执行方法的内存模型。每个方法被执行的时候,都会创建一个栈帧,把栈帧压人栈,当方法正常返回或者抛出未捕获的异常时,栈帧就会出栈。(1)栈帧:栈帧存储方法的相关信息,包含局部变量数表、返回值、操作数栈、动态链接
  • a、局部变量表:包含了方法执行过程中的所有变量。局部变量数组所需要的空间在编译期间完成分配,在方法运行期间不会改变局部变量数组的大小。
  • b、返回值:如果有返回值的话,压入调用者栈帧中的操作数栈中,并且把PC的值指向 方法调用指令 后面的一条指令地址。
  • c、操作数栈:操作变量的内存模型。操作数栈的最大深度在编译的时候已经确定(写入方法区code属性的max_stacks项中)。操作数栈的的元素可以是任意Java类型,包括long和double,32位数据占用栈空间为1,64位数据占用2。方法刚开始执行的时候,栈是空的,当方法执行过程中,各种字节码指令往栈中存取数据。
  • d、动态链接:每个栈帧都持有在运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态链接。
  • (2)线程私有
  • 本地方法栈:完全类似的,只不过上面是给java程序用,这里是给native方法用的。
  • 堆空间:Java对象存储的地方,也是JVM调优和分析的重点。(1)Java堆是虚拟机管理的内存中最大的一块(2)Java堆是所有线程共享的区域(3)在虚拟机启动时创建(4)此内存区域的唯一目的就是存放对象实例,几乎所有对象实例都在这里分配内存。存放new生成的对象和数组(5)Java堆是垃圾收集器管理的内存区域,因此很多时候称为“GC堆”

二、堆里有哪些区域,Java8做了哪些改变

堆空间所有线程共享,存放数组与对象;堆划分为新生代和老年,新生代分为Eden和Survivor,Survivor区又可以FromSurvivor和ToSurvivor。Java8以后方法区的规范中,移除了永久代,用元空间替代

Java7及以前版本的Hotspot中方法区位于永久代中。同时,永久代和堆是相互隔离的,但它们使用的物理内存是连续的。永久代的垃圾收集是和老年代捆绑在一起的,因此无论谁满了,都会触发永久代和老年代的垃圾收集。

在Java8中,元空间(Metaspace)登上舞台,方法区存在于元空间(Metaspace)。同时,元空间不再与堆连续,而且是存在于本地内存(Native memory)。所以java8元空间,基本不存在元空间的OOM内存溢出;但是java7的永久代位于JVM内存中,会存在永久代的OOM错误;
好处:内存隔离,当Java Heap空间不足时会触发GC,但Native memory空间不够却不会触发GC。

三、对象的生命周期  

1.对象是如何分配和流转的

对象优先分配在新生代的Eden区。
新生代如果对象满了,会触发minor GC回收掉没人引用的垃圾对象,然后在分配。
如果对象躲过了15次垃圾回收,就会放入老年代里。
如果触发动态年龄判断,大于阈值的N岁对象也会进入老年代。
如果老年代也满了,就会触发FullGC,把老年代里没人引用的垃圾对象清理掉,然后再分配。
大对象数组直接进入老年代

2.对象什么时候进入老年代

躲过15次MinorGC后进入老年代,可以通过设置-XX:MaxTenuringThreshold=15
动态对象年龄判断,当前S区的对象的总大小>当前S区的50%,可直接进入老年代。
大对象直接进入老年代,大对象阈值可以设置:-XX:PretenureSizeThreshold。
MinorGC后对象太多无法放入S区。

3.对象的引用类型

    Java语言为程序员提供了4个不同级别对象引用类型,按照级别从高到低分别为:强引用-StrongReference、软引用-SoftReference、弱引用-WeakReference和虚引用PlantomReference。

4.如何判断对象是否可以被回收

引用计数法:

为每一个创建的对象分配一个引用计数器,用来存储该对象被引用的个数。当该个数为零,意味着没有人再使用这个对象,可以认为“对象死亡”。每当有一个地方去引用它时候,引用计数器就增加1。但是,这种方案存在严重的问题,就是无法检测“循环引用”:当两个对象互相引用,它俩的计数都不为零,因此永远不会被回收。而实际上对于开发者而言,这两个对象已经完全没有用处了。

比如我们都曾经遇到过:两个类相互是对方的成员变量,toString的时候,相互调用。造成循环引用。

因此,Java 里没有采用这样的方案来判定对象的“存活性”。

可达性分析:

可达性分析基本思路是把所有引用的对象想象成一棵树,从树的根结点 GC Roots 出发,持续遍历找出所有连接的树枝对象,这些对象则被称为“可达”对象,或称“存活”对象。不能到达的则被可回收对象。

下面这张图就是可达性分析的描述:

我们发现,GC Roots 本身是一个出发点,也就是说我们每次进行可达性分析的时候都要从这个初始点出发。换句话说,初始点我们一定是可达的。那么,Java 里有哪些对象可以作为GC Roots呢?主要有以下四种:

  • 虚拟机栈(帧栈中的本地变量表)中引用的对象。
  • 方法区中静态属性引用的对象。
  • 方法区中常量引用的对象。
  • 本地方法栈中 JNI 引用的对象。

5.对象何时被回收

回收时机:当对象永久地失去引用后,系统会在合适的时候回收它所占的内存。但是真正的触发回收是自动的,不可预期的。

四、垃圾回收

1.垃圾回收算法有哪些

    JVM 提供了不同的回收算法来实现这一套回收机制,通常垃圾收集器的回收算法可以分为以下几种:

  • 分代回收是主流垃圾回收器的一致设计:堆划不同区,根据对象年龄来存储。

  • 新生代主要采用复制算法;

  • 老年代主要采用标记-清除/整理算法;

  • 老年代采用的标记整理法比新生代的复制算法速度慢10倍以上!

2.新生代的回收机制

  1. 刚开始对象都分配在Eden区,运行一段时间Eden逐渐饱和。

  2. Eden区满了触发MinorGC,复制存活对象到S1区。Eden区再次清空

  3. Eden区再次满了,触发MinorGC,复制Eden+S1存活对象到S2。

  4. Eden区和S1区清空,再次开始分配对象。

新生代划分出Eden、S1和S2区,GC过程中,在新生代的回收过程中始终保持一块S区空着,循环往复!

3、老年代的回收时机

a.MinorGC前,JVM自我检查发现Minor后进入老年代的对象太大,老年代放不下,此时提前触发FullGC;

b.MinorGC后,剩馀对象太大,老年代装不下。

但是如果FullGC后仍然无法存储对象,JVM抛出OOM内存溢出异常!

4.垃圾回收的整个流程回顾:

  • 代码里创建的对象,优先会在新生代里分配,随著方法执行的结束,对象无人引用就变成了垃圾对象。随著时间的拉长,垃圾对象越来越多,一旦新生代不够用了,就会触发一次MinorGC,把没有引用的垃圾对象回收掉,腾出一大部分空间出来。
  • 如果是那些长週期存活的对象(比如注解了@Service和@Controller等的Spring Bean对象),在新生代里会持续躲过多次垃圾回收,躲过一次,年长一岁,当它长大到15岁的时候就会转移到老年代里去!
  • 当老年代里的对象越来越多,新进入的老年代的对象无法装下的时候就会触发OldGC或是FullGC,如果GC解决不了只能触发OOM到应用层。

老年代空间分配担保机制

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值