JVM全解

一、对象在哪块内存分配

数组和对象在堆内存分配;某些对象没有逃逸出去的方法,可能被优化为在栈上分配

二、JVM有哪些运行时内存区域,分别解释

Java 8

  • 程序计数器

    • 程序计数器是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器,分支,循环,跳转,异常处理,线程回复等都需要依赖这个计数器来完成。
    • 由于Java的多线程是通过线程轮流切换完成的,一个线程没有执行完时就需要一个东西记录它执行到哪了,下次抢占到了CPU资源时再从这开始,这个东西就是程序计数器,正是因为这样,所以它也是“线程私有”的内存。
    • 如果一个线程执行一个主要方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是一个本地方法,这个计数器的值则为空,此内存区域是唯一一个在Java的虚拟机规范中没有规定任何OutOfMemoryError异常情况的区域。
  • 虚拟机栈

    • 虚拟机栈描述的是Java方法执行的内存模型,每个方法在执行时都会创建一个栈帧用来存储局部变量表,操作数栈,动态链接,方法出口等信息,每一个方法从调用到执行完成的过程就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
    • 局部变量表存放了编译期可知的各种基本数据类型(boolean,byte,char,short,int,float,long,double),对象引用(reference类型即所有引用类型的父类,它不等同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)和returnAddress类型(指向了一条字节码指令的地址)。
    • 异常状况:当栈帧一直在执行入栈操作时,线程请求的栈的深度大于虚拟机栈所允许的深度时,就会发生栈溢出(StackOverflowError),一般造成这个问题的原因是程序中方法被循环调用没有退出。虚拟机栈也可以动态扩展,但当超过Java虚拟机规范中所规定的长度时,就会报OutOfMemoryError(内存溢出)异常。
    • 堆是JVM里最大的一块内存区域,被所有线程共享,在虚拟机启动时创建,此区域的目的就是存放对象实例和数组,几乎所有的对象实例都在这分配(随着JIT的发展已经不是那么绝对了)
    • Java堆是垃圾收集管理的主要区域,由于现在收集器基本都采用分代收集方法,所以Java的堆中还可以分为新生代,老年代,永久代,1.8之后取消了永久代;其中新生代又划分为Eden空间,From Survivor空间,To Survivor空间。无论怎么划分都是为了更好的回收,分配,利用内存。个人理解:只要是new出来的都在Java堆中。
    • 当一直死循环创建一个对象时,会出现内存溢出(OutOfMomroryError),可以通过-Xmx和-Xms来控制,当项目较大时,就能很快找到问题所在(后面会有介绍)。
  • 方法区

    • 方法区与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
  • 运行时常量池

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

    • 本地方法栈和虚拟机栈相似,区别就是虚拟机为虚拟机栈执行Java服务(字节码服务),而本地方法栈为虚拟机使用到的Native方法服务。本地方法栈中使用的语言,使用方式,数据结构没有强制要求。有的虚拟机比如(HotSpot)直接就将虚拟机栈和本地方法栈和二为一。

三、栈帧包含哪些结构

局部变量表,操作数栈,动态连接,返回地址,附加信息

四、JVM的内存模型是什么

JVM 试图定义一种统一的内存模型,能将各种底层硬件以及操作系统的内存访问差异进行封装,使 Java 程序在不同硬件以及操作系统上都能达到相同的并发效果。它分为工作内存和主内存,线程无法对主存储器直接进行操作,如果一个线程要和另外一个线程通信,那么只能通过主存进行交换。

五、 JVM是如何确定垃圾对象

1.判断对象是否回收

1.引用计数算法

  • 基本思想:给对象添加一个引用计数器,每当有一个地方引用它时,计数器加1,当引用失效时,计数器减1,任何时刻计数器为0的对象就是不可能再被使用的,这个对象就是需要被回收的
  • 优缺点:引用计算算法的实现比较简单,判定效率也很高,大部分情况在它都是一个不错的算法;但是一些主流的Java虚拟机中却没有使用这种算法,主要是因为它无法解决对象之间循环相互引用的问题
    2.可达性算法
  • 基本思想:这个算法的思想就是通过一系列“GC Roots”对象作为起点进行搜索,如果在“GC Roots”和一个对象之间没有可达路径,则该对象就是不可达的,这个对象就会被判定为可回收对象,所走过的路径称之为引用链。简而言之,就是以一个对象为起点,看能不能走到那个对象,走不到的对象就是不可达的,所以这个起点很重要。
  • 可用作GC Roots的对象
    1.虚拟机栈中引用的对象
    2.方法区中类静态属性引用的对象
    3.方法区中常量引用的对象
    4.本地方法栈中引用的对象

2.引用

概述
通过前面的学习我们知道,无论是通过引用计数算法设置计数器判断对象的引用还是通过可达性算法判断对象的引用链是否可达,都涉及到了引用这个问题。简而言之,如果一个变量是一个类类型,那么这个变量就是一个引用。这种定义很纯粹,这样的话,一个对象就只有被引用和没被引用两种状态。我们希望能描述这样一种对象,当内存空间足够时,则白柳这些对象,当内存空间不够时,则抛弃这些对象,通过这些引用的名字大概就能知道它们代表的意思。

  • 强引用:在程序代码中普遍存在,只要强引用存在,那么垃圾收集器就永远不会回收被引用的对象
  • 软引用
    • 当一个对象与GC Roots之间存在软引用时,垃圾收集器是否回收这个对象就去决与内存的紧张程度了,如果内存空间足够,不会被回收,如果内存空间不足时,将会难逃被回收的厄运。
    • 对象与GC Roots之间存在软引用时,这个对象被称为软可达对象。
    • 在垃圾收集器还没有回收它的时候,软引用对象就像强引用对象一样,都能被程序正常使用和访问,但需要通过软引用对象间接访问(通过SoftenReference类来实现)。
    • 用来描述有用但非必须的对象,只有在内存不足时JVM才会回收该对象,这一特性可以用来解决OOM问题(内存溢出),之一特性还可以用来实现图片缓存,网页缓存。
  • 弱引用
    • 这种引用指向的对象,一旦被垃圾收集器扫描到,就会被回收。
    • 通过WeakReference类来实现弱引用
    • 如果一个对象仅仅是偶尔使用,并且在使用时随时就能获取,但不影响此对象的垃圾收集,那么应该用WeakReference来引用该对象。
  • 虚引用
    • 虚引用是用PhantomReference创建的引用,它是最弱的一种引用关系,一个对象是否有虚引用的存在,完全不会对其生命周期构成影响,也无法通过虚引用来获得一个实例对象。
    • 虚引用的唯一作用: 一个对象设置虚引用就是在这个对象在被垃圾收集器回收时收到一个系统通知。可以利用虚引用来做一些对象销毁前的操作,如资源释放等。

3.对象的自我拯救(生存还是死亡)

概述
前面我们学了可达性算法,那一个对象在可达性算法中已经是不可达对象,那么这个对象就真正非死不可吗?其实要宣告一个对象的死亡,没有这么简单,至少要经过两次标记过程,不可达的对象只是出于“缓刑”阶段。
自我拯救
如果一个对象在通过可达性算法分析后,发现没有与GC Roots相连接的引用链,那么它将会被第一次标记并进行第一次筛选,筛选的条件是此对象是否有必要执行finalize()方法

  • 当对象没有重写finalize()方法或者finalze()方法已经被虚拟机调用过,虚拟机讲这两种情况都认为此对象没有必要执行finalize()方法,即自救失败。
  • 当对象重写了finalize()方法时,那么这个对象将会被放置在一个叫做F-Queue的队列之中,后面垃圾收集器将会对F-Queue中的对象进行第二次小规模的标记,如果对象要在finalize()中成功拯救自己,只要与引用链上的任何一个对象建立关联即可),如把当前对象的引用this赋值给某对象的类变量/成员变量即可。第二次标记时这个对象讲将会被移除“缓刑”阶段(自救成功),如果没有与引用链上的对象建立关联,那就自救失败。

六、垃圾回收器

什么是Stop the world
简称STW,即在执行垃圾收集算法时,Java应用程序的其他所有除了垃圾收集收集器线程之外的线程都被挂起,此时程序只能运行GC线程进行运行,其他线程则会全部暂停,等待GC线程执行完毕后才能再次运行。这些工作都是由虚拟机在后台自动发起和自动完成的,是在用户不可见的情况下把用户正常工的线程全部停下来,这对于很多的尤其是实时性要求很高的程序来说是难以接受的。但是有些时候这个是无法避免的,随着垃圾收集器的改进,用户线程的停顿时间也在不断缩短。
所以垃圾回收坚持的一个原则就是尽量减少“Stop The World”的时间和次数。
Stop the world出现多半由于GC引起的:

  • 老年代空间不足
  • System.gc()方法调用
  • 新生代GC时老年代的内存平均值大于老年代剩余空间
  • 又连续的大对象需要分配
  • Dump线程,分析存在阻塞和瓶颈的线程
  • 死锁检查

1.垃圾回收器的基本原理

对于GC来说,当程序员创建对象时,GC就开始监控和这个对象的地址、大小以及使用情况。
通常,GC采用有向图的方式记录和管理堆(heap)中的所有对象,通过这种方式确定哪些对象时可达的,哪些对象是不可达的,当GC确定一些对象为不可达时,GC就有责任回收这些内存空间。
程序员可以手动执行System.gc(),通知GC运行,但是Java语言规范并不保证GC一定会执行。

2.JVM有哪些垃圾回收器

串行(Serial):使用单线程进行垃圾回收的回收器。
并行(Parallel):之多条垃圾收集西城并行工作,但此时用户线成任然处于等待状态。
并发(Concurrnet):只用户线成与垃圾收集线成同时执行(不一定是并行,可以是交替执行),用户线成在继续执行,而垃圾收集器运行在另一个CPU上。

  • 回收新生代的收集器:Serial、ParNew、Parallel Scavenge。
  • 回收老年代的收集器包括Serial Old、Parallel Old、CMS。
  • 回收整个Java堆的G1收集器。

1.Serial收集器(复制算法):新生代单线程收集器,标记和清理都是单线程,有点事简单高效。
2.ParNew收集器(复制算法):新生代并行收集器,实际上是Serial收集器的多线程版本,在多核CPU环境下有着比Serial更好的表现。
3.Parallel Scavenge收集器(复制算法):新生代并行收集器,追求高吞吐量,高效开勇CPU,吞吐量=用户线程时间/(用户线程时间+GC线程时间),高吞吐量可以高效率的利用CPU时间,尽快完成程序的运算任务,适合后台应用等对用户交互相应要求不高的场合。
4.Serial Old收集器(标记- 整理算法):老年代单线程收集器,Serial收集器的老年代版本。
5.Parallel Old收集器(标记-整理算法):老年代并行收集器,吞吐量优先,Parallel Scavenge收集器的老年代版本;
6.CMS收集器(标记-清除算法):老年代并行收集器,以获取最短回收停顿时间为目标的收集器,具有高并发、低停顿的特点,追求跟高GC回收停顿时间。
7.G1收集器(标记-整理算法):Java堆并行收集器,G1收集器是JDK1.7提供的一个新收集器,G1收集器基于标记-整理算法实现,也就是说不会产生内存碎片。此外,G1收集器不同于之前收集器的一个重要特点是:G1回收的范围是整个Java堆(包括新生代、老年代),而前六种收集器的范围仅限于新生代或老年代。

七,垃圾收集算法

stop the world会在执行某一个垃圾收集算法的时候产生,JVM为了执行垃圾回收,会暂时java应用程序的执行,等垃圾回收完成后,再继续运行。尽可能减少stop the world的时间,就是我们优化JVM的主要目标。
所以垃圾回收坚持的一个原则就是尽量减少“Stop The World”的时间和次数。

1.标记——清除算法

标记-清除算法是一种最基础的算法,后面的收集算法都是根据这种算法的不足进行改进而得到的。分为标记和清除两个阶段。

  • 标记:标记的过程就是遍历所有GC Roots,然后将GC Roots可达的对象标记为存活对象。
  • 清除:遍历堆中所有对象,将没有标记的对象全部清除掉。
    缺点1.效率问题,标记和清除效率太低(标记和清除都需要遍历全堆对象) 2.空间问题:标记清除之后会产生大量连续的内存碎片,空间碎片太多可能会导致以后在程序运行中需要分配较大对象时,无法找到足够的连续内存从而不得不再次触发垃圾收集动作

2.复制算法

为了解决标记-清楚算法中的效率问题,出现了一种“复制”的收集算法。

  • 基本过程
    它将内存按容量划分为大小相同的两块,每次只使用其中的一块。当这一块的内存用完了,就将还活着的对象复制到另一块上面,然后再把已使用过的内存空间一次性清理掉
  • 缺点
    效率问题:当对象存活率较高时,复制次数过多,效率降低
    空间问题:内存缩小了一半,需要额外的空间做分配担保

3.标记-整理算法

为了解决复制算法的缺点,就提出了标记-整理算法
1.标记:标记的过程就是遍历所有的GC Roots,然后将GC Roots可达的对象标记为存活对象。
2.整理:让所有存活的对象都向一段移动,然后直接清理掉端边界意外的内存

4.分代收集算法

堆结构分代

  • 概念:堆的结构是基于分代理论(垃圾回收)的,这种结构划分,是为了更好地进行垃圾回收。
  • 意义:堆内存是虚拟机管理的内存中最大的一块,也是垃圾回收最频繁的一块区域,我们程序所有的对象实例都存放在堆内存中。所以给堆内存分代就是为了提高垃圾回收的效率。试想一下,如果堆内存没有区域划分,所有的新创建的对象和生命周期很长的对象放在一起,随着程序的执行,堆内存需要频繁进行垃圾收集,而每次回收都要遍历所有的对象,遍历这些对象所花费的时间代价是巨大的,会严重影响我们的GC效率。
  • 划分:Java虚拟机根据对象对象存活的周期不同,一般将堆内存划分为新生代,老年代,永久代(对HotStop虚拟机而言)
    1.新生代:新生成的对象优先存放在新生代中,现在的商业虚拟机都采用的复制算法来回收新生代。IBM公司的专门研究表明,新生代中的对象98%是朝生夕死的,所以并不需要按照1:1的比例来划分内存空间,而是将内存划分为一块较大的Eden空间和两块较小的Survivor空间,默认比例为8:1:1( 可以通过参数 –XX:SurvivorRatio 来设定 ),这样就能够充分利用内存空间减少浪费,那么将会留下10%的空间来存放回收后还存活的对象就足够了,如果不够时,就依赖老年代来进行分配担保。关于这部分具体的知识以及新生代中为什么要将空间分为两个Survivor区,请参考:
    2.老年代:部分对象会在From Survivor和To Survivor区域中复制来复制去,如此交换15次(由JVM参数MaxTenuringThreshold决定,这个参数默认是15),最终如果还是存活,就存入到老年代,由此看出,老年代中对象的存活率很高的,所以采用复制算法来实现不太理想,就使用标记-整理算法或者标记-清理算法。

分代收集算法
所以分代收集算法就是根据各个年代的特点选择最适合的收集算法 。

  • 所以在新生代中,对象的大量死亡,只有少数对象存活,复制算法最合适。
  • 在老年代中,对象存活率高,没有额外的空间对它进行分配担保,所以标记—清理算法或者标记—整理算法值是最合适的。
  • 给不同的代选择合适的收集算法,这也是堆结构分代的意义所在。

八、GC(Minor GC ,Major GC,Full GC)

1.GC

  • GC(GarbageCollection)是垃圾回收机制,在java中开发人员无法使用指针来自由的管理内存,GC是JVM对内存(实际上就是对象)进行管理的方式。GC使得Java开发人员摆脱了繁琐的内存管理工作,让程序的开发更有效率。
  • 对于程序员来说,分配对象使用new关键字;释放对象时,只要将对象所有引用赋值为null,让程序不能够再访问到这个对象,我们称该对象为"不可达的"或者"不被引用"。GC将负责回收所有"不可达"对象的内存空间。

2.GC机制

对于GC来说,当程序员创建对象时,GC就开始监控这个对象的地址、大小以及使用情况。通常,GC采用有向图的方式记录和管理堆中的所有对象。通过这种方式确定哪些对象是可达的,哪些是不可达的。当GC确定确定一些对象为不可达的时候,GC就有责任回收这些内存空间。
可以马上回收内存。
程序员可以手动执行System.gc(),通知GC运行,但是Java语言规范并不保证GC一定会执行。

3.Minor GC ,Major GC,Full GC三者区别

1.Minor GC

  • Minor GC是指从新生代空间(包括 Eden 和 Survivor 区域)回收内存。
  • 回收过程:采用复制算法
    • 首先把Eden区和Survivor From区域中存活的对象复制到Survivor To中(如果有对象的年龄达到了老年的标准,直接存在老年区),同时把这些对象的年龄+1(如果Survivor To内存不够了,就放到老年代),然后在将Eden区和Survivor From区清理,最后Survivor From和Survivor To互换,Survivor To成为下一次GC时的Survivor From区,无论啥时候,都有一块空闲的Survivor区。
    • 触发条件:
      (1)当Eden区满时。
      (2)新创建的对象的大小大于Eden区剩余的空间时。

2.Major GC

  • Major GC是指从老年代回收内存
  • 回收过程:标记—清除算法,标记—整理算法
  • 老年代的对象比较稳定,所以MajorGC不会频繁执行。在进行MajorGC前一般都先进行了一次MinorGC,使得有新生代的对象晋升入老年代,导致空间不够用时才触发。当无法找到足够大的连续空间分配给新创建的较大对象时也会提前触发一次MajorGC进行垃圾回收腾出空间。

3.Full GC

  • Full GC清理整个堆内存区,包含了新生代和老年代。
  • 触发条件
    (1)调用System.gc时,系统建议执行Full GC,但是不必然执行
    (2)老年代空间不足
    (3)通过Minor GC后进入老年代的平均大小大于老年代的可用内存
    (4)在新生代回收内存时,由Eden区和Survivor From区把存活的对象向Survivor To区复制时,对象大小大于Survivor To空间的可用内存,则把该对象转存到老年代(这个过程称为分配担保),且老年代的可用内存小于该对象大小。即老年代无法存放下新年代过度到老年代的对象的时候,便会触发Full GC。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值