Java 虚拟机、栈区、堆区、GC

什么是虚拟机?

虚拟机就是将平台无关的.class文件的字节码翻译成平台相关的机器码,来实现跨平台;

JVM

jvm是一种规范,能将按照JVM规范生成的字节码转换为机器能执行的机器码;

它能识别 .class后缀的文件,并且能够解析它的指令,最终调用操作系统上的函数,完成我们想要的操作;

程序执行:

Java 文件->编译器>字节码->JVM->机器码;
Android的java虚拟机就是按照JVM的规范,进行特殊定制的java虚拟机,里面的参数经过了调优以达到更适合特定设备(android设备)使用;

JVM三大角色

  1. 类加载器 :将编译好的.class文件加载至运行时数据区
  2. 运行时数据区 :存放系统运行时的数据
  3. 执行引擎 :执行当前进程内要完成的操作

是在运行时数据区;

栈是线程独有的,保存其运行状态和局部自动变量的(所以多线程中局部变量都是相互独立的,不同于类变量)。栈在线程开始的时候初始化(线程的Start方法,初始化分配栈),每个线程的栈互相独立。每个函数都有自己的栈,栈被用来在函数之间传递参数。操作系统在切换线程的时候会自动的切换栈

栈中放的是一个个的栈帧(Method);

一个栈默认的大小为1024K(1兆);

当方法循环调用或递归调用过多时,会发生栈溢出;

 栈帧

每个方法对应一个栈帧;

栈帧包含

  1. 局部变量表:局部变量数组或本地变量表;存放了代码起始行号、变量下标等信息;
  2. 操作数栈:进行运算时,将变量和结果压入和移除的先进后出的栈;
  3. 动态链接:运行时常量池中,结尾处以特殊符号(#)为指引的“路标”,以进行执行顺序;
  4. 方法返回地址:调用方法的PC寄存器的值;

堆区就是一组连续指定的内存地址的逻辑空间;

堆区是共享区,任何线程都可以访问堆中的共享数据;

为了优化GC性能,堆区以“年龄”划分为三块结构区域:

(Android中以年龄6为划分,其他为15);

  1. 年轻代young:多为一般的临时对象,GC次数小于6,即:GC5次不死;
  2. 老年代old:GC次数大于等于6,即GC6次不死,会从年轻代划分为老年代;
  3. 永久代(对HotSpot虚拟机而言);

年轻代和老年代的内存大小默认为1:2;

三种GC

  1. minor GC:  只发生在年轻代;
  2. major GC:只发生在老年代,在发生前会尝试调用minor GC(不是绝对发生);性能比 minor GC慢10倍;
  3. Full GC:发生在全部;

年轻代

划分为2个区域:内存比例默认8:2;

  1. Eden区:当年轻代内存够用时,所有出生的对象都放在这里
  2. survivor区:对象内存地址排序使用;
survivor区又划分为2个区域:内存比例:1:1; 存活的对象在这两个区域中来回进行内存地址排序(GC的复制算法);
  1. From区(s0)
  2. To区(s1)

TLAB:

  1. 在Eden区中,总内存大小只占Eden的1%;
  2. 为每个线程开辟的一小块私有缓存区域,来放每个线程的私有数据,防止加锁影响分配速度;
  3. 若对象在TLAB中分配失败,则会尝试加锁保证原子性,从而直接在Eden中分配;

对象在年轻代区域内存分配流程:

  1. 很多很多对象出生在Eden区;
  2. 当Eden区域内存满时,触发第一次minor gc;
  3. Eden区存活的对象进入survivor中的From区域,年龄+1,进行内存地址排序;
  4. 又有很多很多对象出生在Eden区;
  5. 当Eden区域内存满时,触发第二次minor gc;
  6. Eden区存活的对象和上次在From区域中仍然存活的对象,一起进入To区域,年龄+1,进行内存地址排序;
  7. 又有很多很多对象出生在Eden区;
  8. 当Eden区域内存满时,触发第三次minor gc;
  9. Eden区存活的对象和上次在To区域中仍然存活的对象,一起进入From区域,年龄+1,进行内存地址排序;
  10. 一直循环
  11. 当某次minor GC时,survivor区(From/To)中有对象年龄等于6 或者From/To放不下时,将From/To中的部分对象移入老年代;

注意:当发生minorGC时会触发STW行为:暂停当前所有的其他线程!!! 所以频繁的内存抖动,会带来卡顿;

老年代

默认内存是年轻代的2倍;放年龄大的对象;

当minor gc完年轻代,年轻代内存不足时:

  1. 当老年代区域内存够时,把对象放进来;
  2. 当老年代区域内存不足时,发生major gc;
  3. 当gc完,内存还不足时,调用Full GC;
  4. 当gc完,内存还不足时, 毁灭吧OOM;

对象逃逸:

逃逸:在当前方法中,创建的对象被其他方法使用;

未逃逸:在当前方法中,创建的对象并没有被其他方法使用

栈上分配:

在对象未逃逸时,在JIT编译器指令调优时,会将未逃逸对象直接在栈上进行分配,不再进行堆区分配;(栈帧用完就销毁了)

标量替换:

在对象未逃逸时,JIT编译器指令调优时,会将未逃逸的引用对象进行标量替换(将引用对象转化为其内部的一个个基本类型),提升性能;

对象在内存中的分布:

  1. 对象头:64位;头信息和类型指针;
  2. 实例数据:方法和内部的变量;
  3. 对齐填充:用若干个0对齐;

头信息:包含hashcode、GC分代年龄、线程持有锁、偏向线程ID、偏向时间戳、锁类型(最后两位);

GC

什么是垃圾?

在程序中没有任何指针指向的对象,这个对象就是要被回收的垃圾;

为什么要进行GC?

  1. 防止内存被消耗完,释放垃圾对象
  2. 对内存空间碎片进行管理:整理或者在一个空闲表里按顺序将地址

垃圾回收相关算法:

1、垃圾确认算法(标记阶段算法):

  1. 引用计数算法:用一个整型变量记录对象被其他对象引用的次数;当引用数为0时它就是一个垃圾对象;缺陷:当有对象进行循环引用时,引用数无法为0,也就无法释放,发生内存泄漏;
  2. GCRoot可达性分析算法:解决了对象循环引用问题;当对象没有被顶层的root对象所引用,那么它就是一个垃圾对象;

GCRoot:

一个set集合,存放root对象;

root:一个指针,他保存了堆里面的对象,但自己又不在堆里面;

2、清除垃圾算法(清除阶段算法):

所有内存回收都是基于快照(只要动内存):停掉所有线程STW;

  1. 标记-清除算法(Mark-Sweep):从头到尾遍历堆内存;遍历到对象header里不是GCRoot可达对象,则清除;把内存地址进行回收,放到空闲表中
  2. 复制算法(Copying):内存分为两块同样大小;一块满了,把活着的对象按顺序放入另一块中,之前满的里面的对象,全部干掉;一直循环;场景:年轻代中的survivor区(From和To);缺点:每次交换时地址都发生改变,需要维护对象的地址;
  3. 标记-压缩算法(Mark-Compact):先进行清除算法,清除完,经过算法运算,将存活对象地址进行依次按顺序排列整理;

3、分代收集算法

经过研究发现:

70%~90%的对象都是生命周期短的对象,且频繁产生;

1%~30%的对象都是生命周期长的对象;

通过上面两种现象,将堆区划分为年轻代和老年代

年轻代:将复制算法进行优化:分为8:2的区域,2又分为1:1进行复制算法;

老年代:使用压缩算法:先清除再整理排序,不需要维护一个空闲表;

4、增量收集算法

上述的算法都会触发STW,挂起所有线程,影响用户体验;

增量收集算法:针对老年代;

  1. 标记:线程都挂起
  2. 清除:和线程一起运行
  3. 整理:每次只收集一部分区域(线程挂起),收集完,线程继续运行;用来减小每次线程挂起时间;

缺点:增大线程切换开销,系统吞吐量增大;

应用于CMS收集器;

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值