Java虚拟机和内存管理

Java虚拟机和内存管理

java虚拟机的运行流程

运行流程分为两部分:编译时环境和运行时环境
当一个java文件经过java编译器编译成class文件,这个class文件就会由虚拟机处理,java虚拟机和java语言美而有必然联系,它只和class文件有关,因为无论什么语言,只要能编译成class文件,都可以用虚拟机识别并执行;

java虚拟机的结构

java虚拟机结构图
java虚拟机区域说明图
比较难理解的几点说明下
1.程序计数器
为了保证程序能够连续的执行下去,处理器必须具有某些手段来确定下一条指令的地址,而程序计数器就是这种作用,cpu是以线程为执行单位,所以这个是线程独立的区域,每个线程会有一个单独的程序计数器,并且他永远不会OOM;
2.java虚拟机栈
线程私有,生命周期和线程一致。描述的是 Java 方法执行的内存模型:每个方法在执行时都会床创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行结束,就对应着一个栈帧从虚拟机栈中入栈到出栈的过程。
局部变量表:存放了编译期可知的各种基本类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference 类型)和 returnAddress 类型(指向了一条字节码指令的地址)
StackOverflowError:线程请求的栈深度大于虚拟机所允许的深度。
OutOfMemoryError:如果虚拟机栈可以动态扩展,而扩展时无法申请到足够的内存。
3.堆
创建的对象都是分配在这个区域,GC管理的区域

对象的创建

当虚拟机接受到new 指令时,会做下面的操作

  1. 首先检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经被加载、解析和初始化过。如果没有,执行相应的类加载。
  2. 类加载检查通过之后,为新对象分配内存(内存大小在类加载完成后便可确认)。在堆的空闲内存中划分一块区域(‘指针碰撞-内存规整’或‘空闲列表-内存交错’的分配方式)。
    说明:内存分配是根据内存是否规整决定用哪种方式的,是否规整是个垃圾回收期采用哪种算法有关(标记清除或者标记整理)
    • 指针碰撞:使用于规整的内存,将内存中的指针向内存空闲的一端移动对象占内存大小的空间就可以了
    • 空闲列表:因为内存是不规整的,虚拟机会维护一个列表记录哪块内存是可用的,这样在分配内存的时候就从列表查询到足够大的内存分给该对象;
  3. 前面讲的每个线程在堆中都会有私有的分配缓冲区(TLAB),这样可以很大程度避免在并发情况下频繁创建对象造成的线程不安全。
  4. 内存空间分配完成后会初始化为 0(不包括对象头),接下来就是填充对象头,把对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息存入对象头。
  5. 执行 new 指令后执行 init 方法后才算一份真正可用的对象创建完成。

java中的引用类型

java引用类型

对象的生命周期

  1. 创建阶段
  2. 应用阶段
  3. 不可见阶段:在程序中找不到对象的强引用,仍可能被特殊的强引用(GC Roots持有)
  4. 不可达阶段:找不到任何强引用,并且垃圾回收器发现不可达
  5. 收集阶段:会调用finalize()方法;
  6. 终结阶段:等待回收
  7. 对象空间重新分配阶段:彻底销毁

搜索算法

  1. 计数器算法:(引用一次+1,引用失效-1,当次数为0时,回收),缺点是两个需要回收的相互引用,次数不为0,就不会回收
  2. 根可达性算法:根搜索算法是通过一些“GC Roots”对象作为起点,从这些节点开始往下搜索,搜索通过的路径成为引用链(Reference Chain),当一个对象没有被 GC Roots 的引用链连接的时候,说明这个对象是不可用的。
    GC Roots 对象包括:
    a) 虚拟机栈(栈帧中的本地变量表)中的引用的对象。
    b) 方法区域中的类静态属性引用的对象。
    c) 方法区域中常量引用的对象。
    d) 本地方法栈中 JNI(Native 方法)的引用的对象.

垃圾收集算法

  • 标记-清除算法(对应上面的空闲列表)
    标记-清除算法示意图
    好处:不需要对对象进行移动,并且仅对不存活对象进行处理,在存活对象比较多的情况下,比较实用
    缺点:因为直接清除对象,会造成内存碎片,不利于后期对其他对象的内存分配
  • 复制算法
    复制算法示意图
    只对存活的对象进行复制,完全扫描一遍后,将前面的整段内存进行删除
    好处:在存活对象比较少的情况下,极为高效
    缺点:需要在内存中开辟一片空间,进行复制
  • 标记-压缩算法(对应上面的指针碰撞)
    标记-整理算法示意图
    在标记清除算法上优化的,当清除B之后,后面的引用都往前移动,这样解决了内存碎片的问题,但是需要移动,成本也是很高的

分代回收

GC分为年轻代,年老代和持久代
垃圾收集分为两种:
1.年轻代收集:Minor Collection
2.年老代收集:Full Collection,一般年老代收集会伴随至少一次年轻代收集,它的收集频率低,耗时长

  • 年轻代:又可以划分为Eden空间,from Survivor空间,to Survivor空间,因为年轻代多是生命周期短的对象,Eden区和from区的比例是8:1,年轻代使用的是复制算法;
    当执行一次年轻代收集,Eden空间存活的对象会被复制到to空间,同时from区(上一次垃圾收集存活下来的对象)的也会复制到to区,然后清空Eden区和from区,这个时候from区和to区更换内存中的位置,也就是之前的from区变成to区,这是为了保证,每次to区都是空的;
    有两种情况下,Eden区和from区的对象直接晋升老年区
    1.存活的对象年龄超过一个值(可以设置),这个值代表经历多少次minor GC收集依旧活下来的对象,超过以后直接晋级
    2.to区存储的容量到达阈值,内存不够了,则直接晋级年老代
  • 年老代:使用的是标记清除算法或者标记整理算法,不适合用复制算法,因为对象的生命周期比较长,会造成内存浪费

总结,问题

1–被标记为不可达的对象会立即被垃圾回收器收集吗?
这个和对象的声明周期有关,当发现对象不可达之后,垃圾回收器准备好要收集该对象,如果该对象重写了finalize()方法,则会调用该方法,如果这个方法执行完,依旧处于不可达状态,就被会回收,当然如果该对象没有重写这个方法,则该对象会进入终结状态,等待回收
2–GC触发的时机

  1. JVM无法再为新的对象分配内存空间了
  2. 手动调用System.gc()方法(强烈不推荐),即使调用了,也不会立即执行,还会加大JVM的压力
  3. 低优先级的GC线程,被运行时就会执行

3–STW机制(stop the world)
意味着 JVM 因为要执行GC而停止了应用程序的执行。当Stop-the-world发生时,除了GC所需的线程以外,所有线程都处于等待状态,直到GC任务完成。GC优化很多时候就是指减少Stop-the-world发生的时间。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值