整体上讲解一下JVM


千里之行,始于足下。jvm,程序的基石。

JVM内存区域

宏观分布图

  1. 程序计数器: 一个cpu(内核)同一时刻只能处理一个线程,为了线程能正确的轮询,每个线程都有一个计数器,记录执行的位置,线程私有
  2. 虚拟机栈: 方法执行时的内存模型,每个方法创建时都创建一个栈帧,存储局部变量,操作数栈,动态链接,方法出口等。局部变量包括(基本类型和对象的引用)线程私有
  3. 本地方法栈: 与虚拟机栈作用一样,区别就是虚拟机栈为执行java方法服务,本地方法栈为执行native方法服务
  4. 堆: 所有对象和数组都在堆上分配,存放的具体的对象的实例,区域比较大。垃圾回收的主要管理区域线程共享
  5. 方法区: 存储加载的类信息,常量,静态变量线程共享
  6. 常量池: 方法区的一部分,一个类中除了有版本,字段,方法,接口等描述信息(类信息),还有常量,存储的字面量和符号引用

重点讲解一下java 堆与垃圾回收

垃圾回收的前提是对象已经死了,怎么判断死了呢!且看继续分解。

判断对象是否已经死了—也就是有被回收的基础了

  1. 引用计数法: 就是判断一个对象有多少引用 >=1 说明有人用,因为这就有个问题如果两个对象互相引用就悲剧了,其实是垃圾。
  2. 可达性分析法: 通过"GC ROOT"结点作为起始点开始往下搜索,如果有怎么都搜索不到,说明不可达,可以理解为图或者树中的孤立的点,其中可以作为ROOT的点如下:
    a. 虚拟机栈中引用的对象
    b. 方法区中静态属性引用的对象
    c. 方法区中常量引用的对象
    d. navtive方法中引用的对象
    特殊说明 以上只能判断对象是有死的基础了但是不是肯定死了,死刑缓期执行,还需要垃圾回收的层层筛选。

垃圾回收算法

  1. 标记-清除: 首先标记,然后垃圾回收清除,这样带来两个问题:
    a. 效率比较低下
    b. 会产生大量不连续碎片,和硬盘一样
  2. 复制算法: 把内存分成两个部分,把活着的直接复制到另外一个空白部分,这样就不会有碎片了。但是这样带来的问题是内存就小了一半。
  3. 标记-整理: 思想类似标记清除,但是这里不是清除,而是让存活的对象向一端移动。
  4. 分代收集: 上述两种算法的改良版,将内存分代(按照使用率)如下图:从左到右的移动。现在主要算法
    堆的具体分配图

重点说说这个分代收集

为什么需要把堆分代?不分代完全可以,分代的唯一理由就是优化GC性能。如果没有分代,所有的对象都在一块,GC要找到哪些对象没用,会对堆的所有区域进行扫描(费劲)。很多对象都是朝生夕死的,如果分代的话,我们把新创建的对象放到某一地方,当GC的时候先把这块存“朝生夕死”对象的区域进行回收,这样就会腾出很大的空间出来。(对照上图往下看)

新生代

主要是用来存放新生的对象 由于频繁创建对象,所以新生代会频繁触发MinorGC进行垃圾回收。新生代又分为 Eden区、ServivorFrom、ServivorTo三个区。
Eden:Java新对象的出生地(如果新创建的对象占用内存很大,则直接分配到老年代)。当Eden区内存不够的时候就会触发MinorGC,对新生代区进行一次垃圾回收。
survivor 1:也叫from 保留了一次MinorGC过程中的幸存者。
survivor 2:也叫to 上一次GC的幸存者,作为这一次GC的被扫描者。
MinorGC的过程使用复制清除算法和并行收集器,新创建的对象都会被分配到Eden区(一些大对象特殊处理),这些对象经过第一次Minor GC后,如果仍然存活,将会被移到Survivor区。对象在Survivor区中每熬过一次Minor GC,年龄就会增加1岁,当它的年龄增加到一定程度时,就会被移动到年老代中。在GC开始的时候,对象只会存在于Eden区和名为“From”的Survivor区,Survivor区“To”是空的。紧接着进行GC,Eden区中所有存活的对象都会被复制到“To”,而在“From”区中,仍存活的对象会根据他们的年龄值来决定去向。年龄达到一定值(年龄阈值,可以通过-XX:MaxTenuringThreshold来设置)的对象会被移动到年老代中,没有达到阈值的对象会被复制到“To”区域。经过这次GC后,Eden区和From区已经被清空。这个时候,“From”和“To”会交换他们的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎样,都会保证名为To的Survivor区域是空的。Minor GC会一直重复这样的过程,直到“To”区被填满,“To”区被填满之后,会将所有对象移动到年老代中

老年代

老年代里面的对象几乎个个都是在 Survivor 区域中熬过来的,它们是不会那么容易就 “死掉” 了的。
Full GC 过程标记-清除算法,发生的次数不会有 Minor GC 那么频繁,并且做一次 Full GC 要比进行一次 Minor GC 的时间更长。 另外,标记-清除算法收集垃圾的时候会产生许多的内存碎片 ( 即不连续的内存空间 ),此后需要为较大的对象分配内存空间时,若无法找到足够的连续的内存空间,就会提前触发一次 GC 的收集动作。

永久代

指内存的永久保存区域,主要存放Class和Meta(元数据)的信息,Class在被加载的时候被放入永久区域. 它和和存放实例的区域不同,GC不会在主程序运行期对永久区域进行清理。所以这也导致了永久代的区域会随着加载的Class的增多而胀满,最终抛出OOM异常。
在Java8中,永久代已经被移除,被一个称为“元数据区”(元空间)的区域所取代。元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。类的元数据放入 native memory, 字符串池和类的静态变量放入java堆中. 这样可以加载多少类的元数据就不再由MaxPermSize控制, 而由系统的实际可用空间来控制.
采用元空间而不用永久代的几点原因:
  1、为了解决永久代的OOM问题,元数据和class对象存在永久代中,容易出现性能问题和内存溢出。
  2、类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出(因为堆空间有限,此消彼长)
  3、永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值