JVM结构和GC垃圾回收

JVM知识

一、JVM结构

1、类加载子系统,用于将class字节码文件加载到JVM中

2、JVM运行时数据区,又被称为内存结构

线程共享数据

(1)堆:放对象的地方

(2)方法区(元空间):存储类的模板信息,xxx.class的模板信息,堆中同一个类的对象都指向同一个类模板

线程私有数据

(3)栈:放局部变量、操作数等的地方

(4)本地方法栈:存储本地方法(native)的位置

(5)程序计数器:指向当前线程所执行的字节码指令的地址/行号

堆内存和栈内存,两者的区别? 
 ①创建变量是普通变量,定义时在栈内存中分配,变量在程序运行到作用域外后被释放。而数组和对象本身在堆中分配,即使程序运行到使用new产生数组和对象的语句所在的代码块之外,数组和对象本身占用的堆内存也不会被释放。数组和对象在没有引用变量指向它的时候,才变成垃圾,不能再被使用,但是仍然占着内存,在随后的一个不确定的时间被垃圾回收器释放掉,这个也是Java比较占内存的主要原因。实际上,栈中的引用变量指向堆内存中的变量,这就是Java中的指针。
 ②通俗来讲:堆是用来存放对象的,而栈是用来执行程序的。
 ③jvm只有一个堆区(heap),被所有线程共享;
 每个线程包含一个栈区(stack),每个栈中的数据都是私有的,其他的栈不能访问,但是同一个栈中变量是共享的;分为:局部变量表、操作数栈、动态链接【这三个是栈帧里面的】、本地方法栈、程序计数器

3、执行引擎:执行代码指令的

JVM的结构图如下

JVM结构

二、程序执行的过程

1、每一个线程在执行Java代码的时候,其都会有一块自己私有的堆(先进后出)、本地方法栈、程序计数器

当线程执行某一个方法A的时候会在私有栈空间中分配一块空间被称为栈帧,当该方法A中又调用了其他方法B,那么该方法A的栈帧会被压栈,然后方法B被分配一块栈帧;当方法被执完之后会出栈;

2、每一个栈帧中都包含了:局部变量表、操作数栈、动态链接、方法出口

局部变量表:局部变量存储的位置

操作数栈:操作数栈可理解为java虚拟机栈中的一个用于计算的临时数据存储区(只是一些基本类型的数据,对象存储在堆里面)。

动态链接:方法中调用其他方法时的地址,一般是需要将方法的符号引用(我理解为方法名被编译后的字符串)转化为其在内存中地址的直接引用,这些符号引用存在于方法区中的运行时常量池

方法出口:方法执行完之后的出口;

main 方int a = 1的执行过程:

首先,在线程的栈中开辟一块空间作为main方法的栈帧,然后将1这个常量压入操作数栈,将a变量存入局部变量表中,然后再将1出栈赋值到局部变量表中的a;

其他加减乘除等数据操作都是先将操作数压入操作数栈,然后执行引擎做运算处理后再出栈赋值给接收的局部变量。


线程私有数据空间

【补充一个小点:javap -c xxx.class生成JVM指令, 后面可以加 > 文件名,可以输出到文件中】

三、堆的解析

内存占比

1、新生代(占堆的1/3)

(1)Eden区(占新生代的8/10)

(2)suivivor区

  • From区(占新生代的1/10)

  • to区(占新生代的1/10)

2、老年代(占堆的2/3)

新创建的对象都是在新生代中创建的。

GC过程:分代回收

(Ⅰ)当Eden区的对象爆满无法再为一个新对象分配内存的时候,就会触发GC垃圾回收>>>minor GC,这种minor GC相对来说是GC里最多的;

在发生minor gc的时候,jvm会对对象进行回收(涉及到eden和suivivor的from),为什么
只涉及eden和from?因为此时to是空的。这里就要牵扯到from和to的转换问题。

(Ⅱ)回收的判断依据是GC ROOT,这里涉及到根搜索算法,java对象的关联关系其实就是一个树形结构,从根到叶子节点遍历,死掉的对象也就和根断开了连接关系,处于游离状态。如果Eden/From区的对象处于游离状态,就是类似于有向图中不可达的节点,没有任何引用指向这个对象,这个时候这个对象就会被抛弃和回收;如果有引用就会将这个对象放到我们的From区,如果我们的From区一批同年龄对象内存占用超过50%,就会把大于这批对象年龄的更老老对象放到老年代中去;

【有人可能会i想,50%就会被回收,那岂不是From区永远都不会满,也就不会出现From区和to区的转换,那to区不就形同虚设了?

如何理解这里的一批同年龄对象呢,举个例子,如果内存为100,现在有40个Age = 1的,35个Age = 2的,剩下25个为Age = 3的,那么GC的时候不会把25个Age = 3的和25个Age = 2的移到老年代,而只是把25个Age = 3移到老年代。

这个意思也就是说,致使From区占用达到50%的那个对象,与他相同年龄的都会被留下来,比这个年龄大的才会被挪走,判断依据是年龄而不是比例】

(Ⅲ)当minor GC再次被触发的时候,那么又会有新的对象被放到From区,这个时候From区之前放进去的对象如果仍然被引用,那么它的Age就会+1(只要触发一次minorGC没被回收的对象Age 就+1),如果Age达到阈值15(默认为15)的时候就会将该对象放到老年代中;

(Ⅳ)如果From区满了,那么此时From区会变为to区,to区会变为From区,然后就把原From区的不被回收的对象拷贝到新的From区中;也就是时刻保持名字为to的堆区为空的,这是由于新生代的GC是采用的复制-清除算法,下面会说原因。

(Ⅵ)此外如果设置了-XX:PretenureSizeThreshold这个参数,那么如果你要创建的对象大于这个参数的值,比如分配一个超大的字节数组,此时就直接把这个大对象放入到老年代,不会经过新生代。

(Ⅶ)老年代爆满的时候会触发full GC,stop-the-world(STW),此时系统停顿不能提供任何服务。

四、关于GC的一些思考

1、为什么Java需要采用分代回收策略呢?

是为了减少full GC,尽可能的让老年代里的对象少一些。

如果不采用分代回收策略的话,新生代一爆满就把对象放到老年代中去,那么老年代很可能很快就爆满了,而老年代里的老不死的对象无处可去就只能fullGC,那么就会导致系统卡顿变慢。

2、为什么会有From区、to区

  • 因为Java的GC采用的是复制算法,那么为什么采用复制算法呢?

(1)首先我们的新生代的对象很容易就挂了要回收,如果不采用复制算法,直接在原位置回收(即标记-清除回收)的话,就可能会导致内存碎片变多,这样的话新生代内存利用率就会下降。

(2)新生代是很活跃的区域,一直有新对象的创建,内存碎片太多就导致实际可用空间变小,那么就会频繁出发垃圾回收,这样会影响系统效率,所以采用复制算法

(3)有的人可能会认为可以不使用复制算法,From区和to区共同使用,两个都用的话可用空间岂不是会更大吗?这样比内存碎片会大更多

个人认为:两个空间都用的话,第一,GC处理速度会变慢,因为不只是考虑一片空间了;第二,时间久了内存碎片也会逐渐增多;第三,任何一个空间满了都只能往老年代里转移,老年代的full GC会增多

  • 那么采用了复制算法又为啥要有两片区域From、to区呢?

(1)既然要使用复制算法,就决定了必须要有两片区域,一篇是源区域,一片是目的区域,把源区域的可用对象赋值到目的区域,并且复制的这个过程把源区域中没用的对象回收掉

五、何时回收对象

1、引用计数算法

给对象添加一个引用计数器,每当有一个地方引用这个对象时,计数器值加1,每当一个引用失效时,计数器减1,任何时刻计数器为0的对象就是不可能再被使用的。在java虚拟机中,并没有使用这个算法来管理内存,其中最主要的原因就是它很难解决对象之间循环引用的问题。

2、可达性分析算法

JVM的主流实现是可达性分析,可达性分析在概念上其实也不难理解,它的基本思路就是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时(图论里面专业一点来说,就是从GC Roots到这个对象不可达),则证明对象是不可用的,大致可以像下图理解。

六、GC算法

1、标记清除

适用于存活对象多的情况,例如老年代,扫描了整个空间两次(第一次:标记存活对象;第二次:清除没有标记的对象)

缺点:产生内存碎片

2、复制算法

从根集合节点进行扫描,标记出所有的存活对象,并将这些存活的对象复制到一块儿新的内存。

现在的商业虚拟机都采用这种收集算法来回收新生代。

适用场合:

存活对象较少的情况下比较高效,存活对象多的话复制成本挺高的。

扫描了整个空间一次(标记存活对象并复制移动)

用于年轻代(即新生代):基本上98%的对象是"朝生夕死"的,存活下来的会很少

3、标记整理

复制算法的高效性是建立在存活对象少、垃圾对象多的前提下的。标记法是在存货对象多的前提下。

标记-压缩算法是一种老年代的回收算法,它在标记-清除算法的基础上做了一些优化。将所有的存活对象压缩到内存的一端。之后,清理边界外所有的空间,为了去除内存碎片。

4、分代收集算法

分代收集算法就是目前虚拟机使用的回收算法,它解决了复制算法不适用于老年代的问题,将内存分为各个年代。一般情况下将堆区划分为老年代(Tenured Generation)和新生代(Young Generation),在堆区之外还有一个代就是永久代(Permanet Generation)。

新生代存活率低,可以使用复制算法。而老年代对象存活率搞,没有额外空间对它进行分配担保,所以只能使用标记清除或者标记整理算法。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值