JVM java虚拟机知识汇总

JVM

什么是jvm?

jvm就是java虚拟机,所有的java程序都运行于jvm内部.

特点:一次编译到处运行,自动内存管理,自动垃圾回收功能 ,不仅可以执行java字节码文件还可以运行其他语言编译后的字节码文件,是一个跨语言平台.

jvm是运行于操作系统之上的与硬件没有直接的交互.

jvm主要任务就是负责将字节码装载到其内部,解释/编译为其对应的平台上的机器指令执行,jvm用类加载器来装载class文件

JVM整体组成可分为以下四个部分

1.类加载器 2.运行时数据区 3.执行引擎 4.本地库接口

1 程序在执行前需要将java代码转换为字节码(.class),jvm需要通过类加载器将字节码文件加载到内存中去,内存(运行时数据区),而字节码文件是jvm的一套指令集规范,并不能直接交给底层的操作系统,因此需要特定的命令解析器 执行引擎 来将字节码文件翻译成为底层系统指令再交由CPU去执行,而这个过程需要调用其他语言的接口就是(本地库接口)来实现整个程序的功能

JVM组成

jvm组成指的是运行时数据区

java编译器输入的指令流

1.基于栈式架构

设计和实现更加简单,适用于资源受限的系统

使用零地址指令分配,其执行过程依赖于操作栈,指令集更小,编译器容易实现

不需要硬件支持,可移植性好,更好的实现跨平台

2.基于寄存器式架构

指令完全依赖于硬件,可移植性差

性能优秀,执行更高效.

完成一项操作使用的指令更少

所以由于java的跨平台性,java的指令集都是由基于栈式架构来设计的 .

类加载器

类加载器只负责class文件的加载,是否能运行不关乎类加载器

加载的类信息存放于一块称为方法区的内存空间,除了类的信息还存放字符串常量池中的元素

类加载的过程

加载 验证 准备 解析 初始化

final修饰的static实例化变量,在编译时初始化,不会为实例变量初始化

初始化(类什么时候开始初始化?)

1.创建类的实例new一个对象

2.访问某个类或接口的静态变量,或对其静态变量进行赋值

3.调用类的静态方法

4.反射

5.初始化一个类的子类(会首先初始化该类的父亲)

类的初始化顺序

父类static 子类static 父类构造方法 子类构造方法

类加载器分类

1.引导类加载器

2.自定义类加载器(派生于抽象类)

自定义类加载器->应用程序类加载器->扩展类加载器(jar,lib…)->引导类加载器

双亲委派机制

安全,可以避免用户自己编写的类动态替换java中的核心类,像java.lang.String

避免全限定命名的类重复加载里面的方法(findloadclass())判断当前类是否已经加载

如果一个类加载器收到了加载类的请求,它并不会自己去加载,而是把这个类委托给自己的父类加载 如果父类加载器无法完成类加载 子类加载器才会尝试去执行

沙箱安全机制

防止恶意代码污染java源代码 起到一个引导作用

面试题

在jvm中如何判断两个对象属于一个类

1.类的全类名完全一致,也就是类的地址完全一致

2.类的加载器必须完全相同

使用程序计数器存储字节码指令地址有什么用?为什么使用程序计数器记录当前线程的执行地址呢?

jvm字节码解释器需要改变程序计数器的值来知道下一条需要执行的字节码指令

因为CPU需要不断的切换各个线程,这时候切换回来后就知道从哪里开始执行

程序计数器为什么被设定为线程私有的?

因为多线程在一个特定时间里只会执行某一个线程中的一个方法,CPU会不停的切换,这样会必然导致程序中断或恢复所以给每个线程都分配一个程序计数器,这样各个线程之间互不干扰独立计算.

类的主动使用与被动使用

每个类首次被主动使用时才会初始化

主动使用

通过new关键字

访问类的静态变量,包括更新和读取

访问类的静态方法

对某个类进行反射操作,会导致类的初始化

初始化子类会导致父类初始化

执行该类的main函数

被动使用

引用该类的静态常量 但是该常量不能为计算那种

构造某个类的数组不会导致类的初始化

主动使用和被动使用的区别在于类是否会被初始化

运行时数据区组成

程序计数器

是一块较小的内存空间,可以看做是当前线程所执行的指示器

作用:用来存储下一条指令的地址,也指即将要执行的指令代码,由执行引擎读取下一条指令 ,程序控制流的指令器循环,异常处理等等都要依赖程序计数器. 字节码解释器工作的时候就是依靠程序计数器的值来进行下一条需要执行的字节码指令.

内存空间很小,运行速度最快的存储区域,

每个线程都有自己的程序计数器,是线程私有的,生命周期随着线程的结束而结束

任何时间线程只有执行一个方法,也就是当前方法,程序计数器会存储当前方法的jvm指令地址,如果是在执行native方法则是未定值.

java虚拟机栈

java方法执行的内存模型 每个方法在调用和销毁的过程中,都对应这一个线帧在栈中从入栈到出栈的过程

是为了对应java的跨平台性 缺点是指令集少,性能下降

栈是运行时的单位,栈是解决程序的运行问题,或者说如何处理数据,堆是存储时的单位,数据怎么存,存在哪里的问题

java虚拟机栈是线程私有的,生命周期和线程一致

作用:主管java程序的运行,它保存方法的局部变量,并参与方法的返回和调用,一个栈帧对应一个方法,栈帧里面存放局部变量

特点:栈是一种快速有效的分配存储方式,访问速度仅次于程序计数器

jvm对java栈的操作:调用方法:进栈,执行结束后出栈.

对于栈来说不存在垃圾回收问题

栈中出现的异常:

1.StackOverflowerror(栈溢出):线程请求的栈深度大于虚拟机所允许的深度

2.outofmemoryerror:如果虚拟机为可动态扩展,而扩展时无法申请到足够的内存

栈中存储什么?

每个线程都有自己的栈,栈中存储线帧,以线帧为单位存储,在这个线程上执行的每个方法都对应一个线帧,线帧是一个内存区块,是一个数据集,里面有方法执行的各种数据信息.

栈的运行原理:

java对java栈的操作只有两个,就是对栈帧的压栈和出栈,遵循先进后出,后进先出的原则

在一条活动的线程中,只有一个活动栈,即只有在执行当前方法的栈帧是有效的,这个线帧被称为当前栈,与当前线帧对应的执行方法叫做当前方法,定义这个方法的类称作当前类

执行引擎运行的所有字节码指令只针对当前线帧进行操作,

如果在该方法中调用了其他方法,对应新的线帧就会被创建出来,放在栈的顶端,称为新的当前线帧

不可能在一个线程中运行另一个线程中的线帧(方法);

如果当前方法调用了其他方法,方法返回之后,当前线帧会传回此方法的执行结果

java方法有两种返回方式:1.通过函数中的return指令,另一种是抛出异常 两种方式都会让栈帧被弹出

栈帧中存放于1.局部变量表2.方法返回地址(在运行完之后为了调用这个方法,所以必须留一个地址)3.操作数栈(计算其中的表达式,也就是计算功能)4.动态链接(引用常量池时使用)5.一些附加信息

面试题:

1.什么情况下会导致栈溢出?

栈溢出就是方法创建时栈帧超过了栈的深度,最有可能就是方法递归产生这种结果

2.通过调整栈的大小,就能保证不溢出吗?

不能

3.分配的栈内存越大越好吗?

不,会延缓这种情况的出现,但是会影响其他内存空间

4.垃圾回收机制是否会涉及到虚拟机栈?

不会,因为java虚拟机栈采用的是压栈和进栈的操作

本地方法栈

与虚拟机栈的作用相同 只不过调用的是操作系统本地的 为操作系统本地的native方法服务

java虚拟机栈只管理java方法而本地方法栈管理本地方法

本地方法栈也是线程私有的

允许被实现成固定或者是可动态扩展大小的内存,内存溢出方法也是相同的

本地方法是用c语言写的

具体做法是在native method stack中登记native方法,在执行引擎执行时加载本地方法库

java堆

java中内存最大的一块 里面主要存放对象实例

一个jvm实例只存在一个堆内存,堆也是java内存的管理区域

java堆区在jvm启动时就被创建,其空间大小也就确定了,是jvm管理最大的一块内存空间

堆内存大小可以调节 -XMS:堆起始大小内存设定 -Xmx:堆最大内存大小设定 一般情况下可以将起始值和最大值设定为一致,这样可以减少垃圾回收之后堆内存重新分配大小的次数,提高效率.

所有线程共享java堆

在方法结束后,堆中的对象不会被马上被移除,在垃圾收集时才会被除 堆是执行垃圾回收的重点区域

java为啥划分堆内存区域?

将对象根据存活率进行划分,将存活时间长的放在固定区,减少垃圾扫描时间和垃圾回收器的频率

分为伊甸园区 幸存者0区 幸存者1区 养老区

new的新对象先放到伊甸园区然后伊甸园区不用的对象进行销毁之后剩余的对象存放在幸存者0区,如果发生垃圾回收机制,则幸存者0区的对象去幸存者1区,每次保证有个 幸存者区是空的,去养老区默认次数为15次也可以设置参数,当养老区的内存不足时就会再次出发GC机制进行内存清理

在hotspot中伊甸园区空间和另外两个幸存者空间的缺省所占比例为8:1:1

新生区的对象(包括伊甸园区,幸存者0区,幸存者1区)生命周期超过15,就会去养老区

TLAB机制:

为什么会有tlab?

java堆区是线程共享区域,任何线程都可以访问到堆区去共享数据

由于对象实例在创建时非常的频繁,因此在并发环境下从堆区划分内存空间是线程不安全的,为避免多个线程操作同一地址,需要使用加锁机制.进而影响分配速度

什么是tlab?

THREAD LOCAL ALLOCATION BUFFER 线程本地分配缓存区,这是一个线程专用的内存分配区域,可以对其进行设置-XX:userTLAB,

java使用TLAB来避免多线程冲突,在给对象分配内存时,每个线程使用自己的TLAB,这样就可以避免线程同步,提高了对象分配的效率.

TLAB空间的内存非常小,缺省的情况下仅占有真个伊甸园区空间的1%,也可以通过设置TLAB空间所占用伊甸园区空间的百分比大小

字符串常量池为什么要调整位置

jdk7中将字符串常量池放在了堆空间中,因为回收效率很低导致永久代内存不足,放到堆中,能及时回收内存

方法区

用于存储被类加载的类信息 常量静态变量 编译后的代码等数据

方法区,是一个被线程共享的内存区域,其中主要存储加载的类字节码,方法区中包含了一个特殊的区域"运行时常量池".

方法区看做是一块独立于java堆的内存空间

方法区的大小跟堆空间一样,可以选择固定大小或者可扩展,

方法区的大小决定了系统可以保存多少个类,如果系统定义了太多的类,导致方法区溢出

方法区的垃圾收集主要回收两部分内容,运行时常量池中废弃的常量和不再使用的类型. jvm方法区是有垃圾回收的

判断一个类是否属于不再被使用的类需要满足下面三个条件:

1.该类所有的实例都已经被回收,java堆中不存在该类及其任何派生子类的实例.

2.加载该类的类加载器已经被回收

3.该类对应的java.lang.Class对象没有在任何地方引用,无法在任何地方通过反射访问该类的方法

面试题:

1.说一下jvm内存模型吧,有哪些区?分别干什么的?

2.jvm的内存分布/内存结构?栈和堆的区别?堆的结构?为什么有两个幸存者区?伊甸园区和幸存者区的比例分配?

3.jvm内存分区,为什么要有新生代和老年代?

4.讲讲jvm运行时数据库区?

5.什么时候对象会进入老年代?

6.jvm的方法区中会发生垃圾回收吗?

线程间共享

堆,方法区 每个线程:独立包括程序计数器,栈,本地方法栈.

本地方法接口

什么是本地方法?

一个native method就是一个java调用非java代码的接口,该方法的实现由非java语言实现,比如c语言.在定义一个native method,并不提供实现体,因为其实现体是由非java语言在外面实现的,关键字native可以与其他所有的java标识符连用,但是abstract除外.

为什么要使用native method

1.与java环境外交互

2.与操作系统交互(线程最后要回归于操作系统的线程)

3.sun s java

执行引擎

执行引擎是java虚拟机核心的组成部分之一

jvm的主要任务是负责装载字节码到其内部.但字节码并不能够直接运行在操作系统之上,因为字节码指令并非等价于本地机器指令,他内部包含的仅仅只是一些能够被jvm所识别的字节码指令.符号表,以及其他辅助信息.

如果想让一个java程序运行起来,执行引擎的任务就是将字节码指令解释为对应平台上的本地机器指令才可以,

作用:jvm的执行引擎将高级语言翻译为机器语言.

前端编译:

从java程序员到字节码文件的这个过程叫做前端编译

后端编译

就是执行引擎的作用将高级语言翻译成为机器语言

解释器

当java虚拟机启动时会根据预定义的规范对字节码文件采用逐行解释的方式执行,将每条字节码文件中的内容翻译为对应平台的本地机器指令执行

解释器的真正意义上所承担的角色就是一个运行时的翻译者,将字节吗文件中的内容翻译为对应平台的本地机器指令执行,执行效率低.

JIT编译器

虚拟机将源代码一次性直接编译成和本地机器平台相关的机器语言,但是并不是马上执行

为什么java是半编译半解释型语言?

刚开始java语言定位为"解释执行"哈斯耦合比较准确的,后来java也发展出可以直接生成本地代码的编译器.现在jvm在执行java代码的时候,通常都会将解释执行与编译执行二者结合起来进行.

JIT编译器执行效率高为什么还需要解释器?

1.当程序启动后,解释器可以马上发挥作用,响应速度快,省去编译时间,立即执行.

2.编译期要想发挥作用把代码编译成本地代码,需要一定的执行时间,但编译为本地大吗后,执行效率高.就需要采用解释器与即时编译器并存的架构来换取一个平衡点.

垃圾回收

java和c++语言的区别,就在于垃圾收集技术和内存动态分配上,c++语言没有垃圾收集技术,需要程序员手动的收集.

垃圾收集

垃圾收集并不是java语言的伴生物,是使用内存动态分配和垃圾收集技术的lisp语言诞生

关于垃圾收集的三个问题?

那些内存需要回收?

什么时候回收?

如何回收?

垃圾收集机制是java的招牌能力,极大的提高了开发的效率.

什么是垃圾?

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

如何不及时对内存中的垃圾进行处理,那么这些垃圾对象所占的内存空间会一直保留到应用程序的结束,被保留的空间无法被其他对象使用,甚至可能会导致内存溢出.

为什么需要GC(垃圾回收机制)?

内存中如果不清理垃圾,内存的空间迟早都会被占用完,被消耗完,不断的分配内存空间而不进行回收.

释放没用的对象,垃圾回收也可以清除内存中的记忆碎片,碎片整理将所占用的堆内存移到堆的另一端,一遍jvm将整理出来的内存分配给新的对象,

程序所应付的业务和功能越来越强大,没有垃圾回收机制就不能保证应用程序的正常进行

手动进行垃圾回收机制容易造成内存泄漏,内存泄漏会导致垃圾对象永远无法被删除,随着程序的运行时间,会出现内存溢出和应用程序崩溃

java垃圾回收机制

自动内存管理

自动内存管理的优点:自动内存管理,无需开发人员手动参与内存的分配与回收,这样会降低 内存泄漏 和 内存溢出的风险

垃圾回收机制 主要作用与java堆

从次数上讲:频繁的收集新生代区,较少的收集老年代区,基本不怎么收集方法区(元空间)

垃圾回收算法

垃圾标记阶段算法: 主要为了判断对象是否存活

死亡的对象? 就是不再被引用

判断对象存活一般有两种方式:引用计数算法和可达性分析算法.

引用计数算法

对每个对象保存一个整型的引用计数器属性,用于记录对象被引用的情况

对象被引用了就加一 引用失效时就减一,当引用计数器的值变为0时就表示对象不可能再被使用,就进行回收

优点: 实现简单,垃圾对象便于辨识;判定效率高,回收没有延迟性

缺点:1.需要单独的字段存储计数器,这样就让存储空间的负担变得更重

2.每次赋值都需要更新计数器,时间效率降低

3.引用计数器有个严重的问题:无法处理循环引用的情况.

因此在javaGC中并没有使用垃圾回收算法中的引用计数算法

可达性分析算法

可达性分析算法:称为根搜索算法,追踪性垃圾收集

优点: 不仅具备简单和执行高效的特点,可以有效的解决引用计数算法中无法处理循环引用的情况,防止内存泄漏的发生.

1.可达性分析算法是以根对象集合为起始点,按照从上到下的方式搜素被根对象集合所连接的目标对象是否可达.

2.使用可达性分析算法后,内存中的存活对象都会被根对象集合直接或间接连接着,搜索所走过的路径称为引用链

3.如果目标对象没有任何引用链相连,则是不可达的,就认为对象已经死亡,可以标记为垃圾对象.

4.在可达性算法中,只有能够被根对象集合直接或间接连接的对象才是存活对象

GC根可以是哪些元素?

1.虚拟机栈中引用的对象

2.本地方法中引用的对象

3.方法区中类静态属性引用的对象

4.方法区中常量引用的对象(字符串常量池里的引用)

5所有被同步锁synchronized持有的对象

6.java虚拟机内部的引用

除了堆空间的周边,比如虚拟机栈,本地方法栈,方法区,字符串常量池等地方对堆空间进行引用的,都可以作为GC根进行可达性的分析

由于root采用栈方式存放变量和指针,所以如果一个指针,他保存了堆内存里面的对象,但是自己又不存放在堆内存里面,那么他就是一个root

对象的finalization机制

finalize()方法机制

对象销毁前的回调函数:finalize();

java语言可以提供对象终止机制允许提供对象在被销毁前的自定义处理逻辑 垃圾回收此对象之前,总会先调用这个对象的finalize方法

finalize方法允许在子类中被重写,用于在对象被回收时进行资源释放,通常在这个方法中进行一些资源释放和清理的工作,比如关闭文件,桃姐字,和数据库连接等

永远必要主动调用某个对象的finalize()方法,应该交给垃圾回收机制去使用

1.在finalize()时可能会导致对象复活

2.finalize()方法的执行时间是没有保障的,他完全由GC线程所决定,极端情况下,如果不发生finalize()方法将没有执行机会

3.一个糟糕的finalize()会严重影响GC的性能,比如finalize是个死循环

生存还是死亡?

由于finalize方法的存在,虚拟机中的对象一般处于三种可能的状态

从所有根节点都无法访问到某个对象,说明对象已经不再被使用了,一般来说此对象需要被回收.但事实上也并非是"非死不可"只是暂时处于缓刑阶段 还有可能复活自己,因此在立即回收这方面是不合理的

因此定义了虚拟机中的对象可能的三种状态

1.可触及的 :从根结点开始可以到达这个对象

2.可复活的: 对象的所有引用都被释放,但是对象可能在finalize中复活

4.不可触及的:对象的finalize被调用,但是并没有复活 那么就是不可触及阶段 不可触及的对象不可能被复活,因为finalize只会被调用一次

上述三种状态,是由于finalize方法 的存在进行的区分,只有在对象不可触及时才可以被回收

垃圾回收阶段算法

1.标记清除算法

当堆空间中的有效内存空间被耗尽的时候,就会停止整个程序,然后进行两项工作,第一项为标记,第二项为清除

标记:Collector从引用根节点开始遍历,标记所有被引用的对象,一般是在对象中的header中记录为可达对象.

清除:Collector对堆内存从头到尾进行线性的遍历,如果发现某个对象在其header中没有被标记为可达对象,则将其回收.

优点:基础和常见的垃圾收集算法容易理解

缺点:标记清除算法的效率不高 在进行GC的时候需要停止整个程序,用户体验差,这中方式清理出来的空闲内存是不连续的,产生内碎片,需要维护一个空闲的列表.

这里的清除并不是真的置空,而是把需要的对象地址保存在空闲的地址列表里,下次有新对象需要加载时,判断垃圾的位置空间是否够,如果够就存放.(覆盖原有的地址)

2.复制算法

它将内存按容量划分为大小相等的两块,每次只使用其中的一块,在垃圾回收时将正在使用的内存中的存活对象复制到未被使用的内存块中,之后清除正在使用的内存块中的所有对象,交换两个内存的角色,最后完成垃圾回收.

优点:没有标记和清除过程,实现简单,运行高效

复制过去后保证空间的连续性,不会出现碎片问题

缺点:需要两倍的空间 内存占用大 时间开销大

应用场景:在新生代中应用较多

3.标记压缩算法

复制算法的高效性是建立在存活对象少,垃圾对象多的前提下,这种情况在新生代中经常发生,在老年代更常见的情况下大部分对象都是存活对象,复制的成本也很高,需要使用其他算法

第一阶段和标记清除算法一样,从根节点开始标记所有被引用的对象

第二阶段将所有的存活对象压缩到内存的另一端,按顺序排放.之后,清理边界外所有的空间.

这个算法等同于标记清除算法完之后再进行对内存碎片的一次清理

可以称之为标记 清除 压缩算法

本质差异:标记清除算法是非移动式的,标记压缩是移动式的

优点:清除了标记清除算法中内存区域分散的缺点,我们需要给新对象分配内存时,jvm只需要持有一个内存的起始地址,清除了复制算法中内存减半的的代价

缺点:效率比复制算法低,移动对象的同时,如果对象被其他对象引用,则还需要调整引用的地址

小结

效率上来说 复制算法是当之无愧的老大,但是却浪费了太多内存

标记压缩的算法速率最慢

分代收集算法

不同的对象生命周期是不一样的因此不同生命周期的对象可以采用不同的收集方式,以便回收高效率,根据各个年代的特点(新生代,老年代)使用不同的回收算法,以提高垃圾回收的效率.

新生代 区域相对老年代较小,对象生命周期短,存活率低,回收频繁

老年代:区域较大,对象生命周期长,存活率高,回收不及新生代频繁

分代的思想被广泛使用,几乎所有的垃圾回收器都区分新生代和老年代

增量收集算法和分区算法

增量收集算法

如果垃圾回收时间过长应用程序会被挂起很久影响稳定性因此有增量收集算法

每次垃圾收集线程只收集一小片区域的内存空间,接着切换到应用程序线程

缺点:间断性的产生了应用程序代码,因为线程切换和上下文转换的消耗,会使垃圾回收的总体成本上升,造成系统吞吐量的下降.

分区算法

将整个堆空间划分成连续的不同小区间,每一个小区间都独立使用,独立回收,这种算法的好处是可以控制一次回收多个小区间.

垃圾回收相关概念

System.gc()通过这个方法的调用,会显示触发Full GC 同时对老年代和新生代进行回收,尝试释放被丢弃对象占用的内存

但是不能确保立即生效

一般都是自动进行GC 不需要手动

内存溢出和内存泄漏

内存溢出

OUtofmemoryerror 没有空闲内存,并且垃圾收集器也无法提供更多内存

内存泄漏

对象不会再被程序调用了,但是GC又不能回收

不会立即让程序崩溃,但会逐渐内存会被蚕食,直到所有内存被耗尽,导致程序崩溃 这里的内存不是指物理内存,是虚拟内存

例子:

单例模式

单例的生命周期和应用程序是一样的,所以在单例程序中,如果持有对外部对象引用的话,这个外部对象是不能被回收的,否则会导致内存泄漏的产生.

close()方法手动关闭

STW

stop the world 指的是在垃圾回收中产生应用程序的停顿

STW 和jvm都是在后台自动发起和自动完成的,在用户不可见的情况下

对象引用

在jdk1.2版本之后 java对引用的概念进行了扩充,将引用分为

1.强引用

2.软引用

3.弱引用

4.虚引用

这四种引用强度依次减弱,除了强引用外,其他三种引用在javalang包中可以找到他们的身影

强引用:

是指代码在程序代码之中存在的引用赋值,无论在任何情况下,只要强引用关系还在,垃圾收集器,就永远不会回收掉被引用的对象,宁可报错,也不会GC强引用

在Java程序中,最常见的引用类型就是强引用,也就是我们最常见的普通对象引用,也就是默认引用类型,在java语言中使用new操作符创建一个新的对象,并将其赋值给一个变量的时候,这个变量就成为指向该对象的一个强引用,只要强引用的对象是可触及的,垃圾回收器就永远不会回收掉被引用的对象,只要强引用的对象是可达的,jvm宁可报异常,也不会回收强引用

相对的,软引用,弱引用和虚引用的对象是软可触及,弱可触及,和虚可触及,在一定条件下,都是可以被回收的.

强引用是造成java内存泄漏的主要原因之一.

软引用:

在系统将要发生溢出之前,将会把这些对象列入回收范围中进行第二次回收,如果这次回收还没有足够的内存,才会抛出内存溢出异常

内存不足则回收 软引用是用来描述一些还有用,但非必须的对象.只要被软引用关联的对象,在系统将要被发生内存溢出异常前,会把这些对象列进回收范围之中进行第二次回收,如果这次回收还没有足够的内存才会抛出内存溢出异常. 软引用通常用来实现内存敏感的缓存.

垃圾回收器在某个时刻决定回收可达的对象的时候,会清理软引用,并可选的吧引用存放到一个引用队列.

弱引用:

发现就被回收 用来描述那些非必须的对象

被引用关联对象只能生存到下一次垃圾收集之前,当垃圾收集器工作时,无论内存空间是否足够,都会回收掉被弱引用关联的对象.

软引用和弱引用的区别就是在GC的时候 需要通过算法检查是否回收软引用的对象,而对于弱引用的对象GC总是进行回收,弱引用对象更容易更快被GC回收

虚引用:

一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引来获得一个对象的实例.为一个对象设置虚引用关联的唯一目的就是能在这个对象被回收器回收时收到一个系统通知.

垃圾回收器

垃圾回收器没有在规范中进行过多的规定,可以有不同的版本的jvm来实现

从不同的角度分析垃圾收集器,可以将GC分为不同的类型

垃圾回收器分类

按线程数分,可以分为串行垃圾回收器和并行垃圾回收器

串行回收指的是在同一时间段内只允许有一个CPU用于执行垃圾回收机制,此时工作线程被暂停,直到垃圾收集工作停滞

并行收集可以运用多个CPU同时执行垃圾回收,因此提升了应用的吞吐量,不管有并行回收仍然和串行回收一样,采用独占式,使用stw机制

按照工作模式分,可以分为并发式垃圾回收器,和独占式垃圾回收器

并发式垃圾回收器与应用程序线程交替工作,以尽可能的减少应用程序的停顿时间

独占式垃圾回收器(STW)一旦运行,就停滞应用程序中所有用户线程,直到垃圾回收过程完全结束

按工作内存区间分,又可分为年轻代垃圾回收器和老年代垃圾回收器

性能指标

吞吐量:运行用户代码时间占总运行时间的比例

总运行时间:程序的运行时间+内存回收的时间

垃圾收集的开销:垃圾收集所用时间与总运行时间的比例

暂停时间:执行垃圾收集时,程序的工作线程被暂停的时间

收集频率:相对于应用程序的执行,收集操作发生的频率

内存占用:java堆区所占内存的大小

快速:一个对象从诞生到回收所经历的时间

垃圾收集器 HOTSPOT

1.serial垃圾收集器(单线程)
2.Parnew垃圾收集器(多线程)
3.Parallel Scavenge** **垃圾收集器(多线程)
4.Serial Old 垃圾收集器(单线程)
5..Parallel Old 垃圾收集器(多线程)

CMS回收器(低延迟)

CMS收集器是以获取最短回收停顿时间为目标的收集器,他在垃圾收集时使得用户线程和GC线程并发执行,因此在垃圾收集过程中用户也不会感到明显的卡顿

初始标记:STW,仅使用一条初始标记线程对所有与GCroots直接关联的对象进行标记.

并发标记:使用多条标记线程,与用户线程并发执行,次过程进行可达性分析,标记处所有废弃对象,速度很慢

重新标记:STW 使用多条标记线程并发执行,将刚才并发标记过程中新出现的废弃对象标记出来

并发清除:只使用一条GC线程,与用户线程并发执行,清除刚才标记的对象,这个过程非常耗时

并发标记和并发清除过程耗时最长,且可以与用户线程一起工作,因此

CMS收集器的内存回收过程是用户线程一起并发执行的

CMS的优点:

并发收集 低延迟

CMS的弊端:

1.会产生内存碎片,导致并发清除后,用户线程可用的空间不足,在无法分配大对象的情况下,不得不提前触发FULL GC

2.CMS收集器对CPU资源非常敏感 在并发阶段,他虽然不会导致用户停顿,但是会因为占用了一部分线程而导致应用程序变慢,总吞吐量会降低

3.CMS收集器无法处理浮动垃圾

G1 Garbage First回收器(区域划分代式)

既然我们已经有了前几个GC,为什么还发布G1GC?

原因在于应用程序所对应的业务越来越庞大,复杂,用户越来越多,没有GC就不能保证应用程序的正常进行,而经常造成STW的GC跟不上实际的需求,所以才会不断地尝试对GC进行优化

官方给G1设定的目标是在延迟可控的情况下尽可能的获得尽可能高的吞吐量,所以才担当起"全功能收集器"的重任和期望

为什么名字叫做G1?

因为G1是一个并行回收器,它把堆内存分割为很多不相关的区域

使用不同的region来表示Eden,幸存者0区,幸存者1区,老年代

G1GC有计划的避免整个java堆中进行全区域的垃圾收集,G1跟踪各个region里面的垃圾堆积的价值大小,在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的region

由于这种方式的侧重点在于回收垃圾最大量的区间所以G1的名字:垃圾优先

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值