一篇文章搞定JVM原理

一、什么是JVM

​JVM全称是Java Virtual Machine,即java虚拟机。java语言是跨平台的,一次编译,到处执行。每一种操作系统,执行相关程序的时候,因为操作系统环境的不同,会造成代码不能跨平台执行。而java可以做到,原因在哪里?就在于不同操作系统有不同版本的JVM

​java语言的执行过程:
​ 源代码(.java)----->编译(字节码 .class)----->解释(成为机器码,01010100110)---->机器码
解释执行语言执行如下:
​ 源代码(.js,.sh)—>解释执行(网页能够认识)

1、JVM发展史

1.1Sun Classic

世界上第一款商用Java虚拟机; 1996年1月23日,Sun公司发布JDK
1.0,Java语言首次拥有了商用的正式运行环境,这个JDK中所带的虚拟机就是Classic VM。不过只能使用纯解释器方式来执行Java代码,如果要使用JIT编译器(JIT Compiler(Just-in-time
Compiler) 即时编译器),就要使用第三方外挂,一旦使用了JIT编译器,JIT编译器就完全接管了虚拟机的执行系统,解释器便不再工作了。
如果使用即时编译器就不得不对每一个方法、每一行代码都进行编译,而无论它们执行的频率是否具有编译的价值。基于程序响应时间的压力,这些编译器根本不敢应用编译耗时稍高的优化技术。有的时候一些代码的编译时间比执行时间还要长。
因此这个阶段的虚拟机即使使用了编译器,执行效率也和传统的C/C++程序有很大差距,“Java语言很慢”的形象就是在这时候开始在用户心中树立起来的。

1.2Exact VM

JDK 1.2时,曾在Solaris平台上发布过一款名为Exact VM的虚拟机,它的执行系统已经具备现代高性能虚拟机的雏形:如支持编译器与解释器混合工作模式。 ​
Exact VM因它使用准确式内存管理(Exact Memory Management,也可以叫Non-Conservative/Accurate Memory Management)而得名。
虚拟机可以知道内存中某个位置的数据具体是什么类型。譬如内存中有一个32位的整数123456,它到底是一个reference类型指向123456的内存地址还是一个数值为123456的整数,虚拟机将有能力分辨出来,这样才能在GC(垃圾收集)的时候准确判断堆上的数据是否还可能被使用。
由于使用了准确式内存管理,Exact VM可以抛弃以前Classic VM基于handle的对象查找方式每次定位对象都少了一次间接查找的开销,提升执行性能。
基于handle(句柄)的对象查找:当123456指向的对象经过一次垃圾回收后,内存地址需要重新规整。内存地址发生了变化为654321,不能将内存中所有的值为123456数据都改为654321。使用句柄来管理对象内存地址的变动,所以定位对象时先去句柄查下实际的地址再去查找对象本身的属性。类似于对象的户口变更登记册。
句柄可以理解为:引用的引用。指针的指针。

1.3SC/EV

Sun Classic / Exact VM的生命周期 虽然Exact VM的技术相对Classic
VM来说先进了许多,但是在商业应用上只存在了很短暂的时间就被更为优秀的HotSpot VM所取代,甚至还没有来得及发布Windows和Linux平台下的商用版本。

而Classic VM的生命周期则相对长了许多,它在JDK 1.2之前是Sun JDK中唯一的虚拟机,在JDK 1.2时,它与HotSpot
VM并存,但默认使用的是Classic VM(用户可用java -hotspot参数切换至HotSpot VM),而在JDK 1.3时,HotSpot VM成为默认虚拟机,但Classic VM仍作为虚拟机的“备用选择”发布(使用java -classic参数切换),直到JDK 1.4的时候,Classic VM才完全退出商用虚拟机的历史舞台

​1.4Sun HotSpot VM

​ 01、可以通过执行计数器找出最具有编译价值的代码,根据执行计数器判断是否达到阈值,没到就解释执行, 否则提交编译请求,通知JIT编译器以方法为单位进行编译。
​ 所以:如果一个方法被频繁调用,或方法中有效循环次数很多,将会分别触发标准编译和OSR(栈上替换)编译动作。
​ OSR:由于代码块可能是在解释执行过程中直接切换到本地代码执行,所以也叫做栈上替换(OSR, OnStackReplacement)
​ 02、通过编译器与解释器恰当地协同工作,可以在最优化的程序响应时间与最佳执行性能中取得平衡,即时编译的时间压力也相对减小,这样有助于引入更多的代码优化技术,输出质量更高的本地代码[机器执行码]。

2、jvm的内存划分区域

2.1运行时内存区域划分

jvm运行时内存区域分为了两种:线程隔离、线程共享。
线程隔离分为:栈(虚拟机栈、本地方法栈)、程序计数器
线程共享分为:堆和方法区(在jdk1.8之后,方法区被称之为Metaspace)
在这里插入图片描述

2.2各个区域介绍

2.2.1程序计数器

程序计数器(Program Counter Register)是当前线程所执行的字节码的行号指示器,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,所以程序计数器这类内存区域为“线程私有”的内存。
如果线程正在执行的是Native方法,这个计数器值则为空(Undefined)。
native方法 是与C++联合开发的时候用的!使用native关键字说明这个方法是原生函数,也就是这个方法是用C/C++语言实现的,并且被编译成了DLL,由java去调用。

2.2.2栈

所谓“栈”包括:java虚拟机栈、本地方法栈;他们作用相似,区别只是:虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。程序员人为的分为“堆栈”中的“栈”。
栈里存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用和指向了一条字节码指令的地址。
每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
局部变量表所需的内存空间在编译期间完成分配,其中64位的long和double类型的数据会占2个局部变量空间,其余的数据类型只占用1个。当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。
数栈也要操作栈,主要是在方法计算时存放的栈。

2.2.3堆

Java堆(Java Heap)是Java虚拟机所管理的内存中最大的一块,此内存区域就是存放对象实例,几乎所有的对象实例都在这里分配内存。
Java堆是垃圾收集器管理的主要区域;内存回收的角度来看Java堆中还可以细分为:新生代和老年代;新生代细致一点的有Eden空间、From Survivor空间、To Survivor空间。这两块survivor空间大小一致。
在实现时,既可以实现成固定大小的,也可以是可扩展的,不过当前主流的虚拟机都是按照可扩展来实现的(通过-Xmx设置最大内存和-Xms设置初始内存)
java -Xms10m -Xmx100m Hello

2.2.4方法区

方法区又叫静态区:用于存储已被虚拟机加载的类信息、常量池、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-Heap(非堆);
对于HotSpot虚拟机是使用永久代来实现方法区;
Java虚拟机规范对方法区的限制非常宽松,除了不需要连续的内存和可以选择固定大小或者可扩展外,还可以选择不实现垃圾收集。相对而言,垃圾收集行为在这个区域是比较少出现的,这区域的内存回收目标主要是针对常量池的回收和对类型的卸载,条件相当苛刻。
在jdk1.7中永久代的配置参数-XX:PermSize5m(初始化永久代内存大小),-XX:MaxPermSize10m(最大永久代内存大小)
在jdk1.8中Metaspace的配置参数:-XX:MetaspaceSize=10m(初始化大小),-XX:MaxMetaspaceSize=10m(最大大小)
java中的常量池技术,是为了方便快捷地创建某些对象而出现的,当需要一个对象时,就可以从池中取一个出来(如果池中没有则创建一个),在需要重复创建相等变量时节省了很多时间。
代码理解:

public class ConstantPool {
    public static void main(String[] args) {
        String str1 = new String("hello");
        String str2 = new String("hello");
        /**
         * == 如果比较的对象是基本数据类型,那么比较的就是值
         *    如果比较的是引用对象,那么比较的是两引用指向的内存地址值
         *
         * equals:是Object中定义的方法,默认equals的实现就是this==obj
         *  字符串复写了该方法比较字符串值是否相等
         */
        System.out.println(str1==str2);//false
System.out.println(str1.equals(str2));//true
System.out.println(str1.toString()==str2.toString());//false
        String str3="hello";
        String str4="hello";
System.out.println(str3==str4);//true
System.out.println(new Integer(1) == new Integer(1));//false
        int a = new Integer(127);//自动拆箱
        int b = new Integer(127);
        System.out.println("a==b?" + (a == b));//true
        Integer c = 127;//自动装箱
        Integer d = 127;
        System.out.println("c==d?" + (c == d));//true
        Integer e = 128;
        Integer f = 128;
        System.out.println("e==f?" + (e == f));//false
        /**
         * [-128, 127] 范围内的数据有一个优化的技术,将这个范围内的数据像字符串一样在常量池中创建,后续直接使用常量池终中的数据即可
         * Integer c = 127;//自动装箱
            Integer d = 127;
            c和d是相等
         */
    }
}

二、异常OOM

程序计数器
不会发生OutOfMemoryError情况
java虚拟机栈\本地方法栈区域
如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;如果扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常

public class JavaVMStackSOF {
    private int stackLength = 1;
    public void stackLeak() {
        stackLength++;
        stackLeak();
    }
    public static void main(String[] args) throws Throwable {
        JavaVMStackSOF oom = new JavaVMStackSOF();
        try {
            oom.stackLeak();
        } catch (Throwable e) {
            throw e;
        } finally {
            System.out.println("stack length:" + oom.stackLength);
        }
    }
}


如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常
报错后dump出信息: -XX:+HeapDumpOnOutOfMemoryError
-Xms5m -Xmx5m -XX:+HeapDumpOnOutOfMemoryError

public class HeapOOM {
static class OOMObject {
    }
public static void main(String[] args) {
        List<OOMObject> list = new ArrayList<OOMObject>();
while (true) {
            list.add(new OOMObject());
        }
    }
}

在这里插入图片描述
方法区
当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常

三、对象的引用和可达性分析

3.1对象的四类引用

java中的引用分为:强引用、软引用、弱引用、虚引用(幽灵引用或者幻影引用),这4种引用强度依次逐渐减弱。
强引用: 在程序代码之中正常的类似于“Person p = new Person()”这类的引用;垃圾收集器不会回收掉被强引用的对象。
软引用: 有用但非必须的对象,jdk中提供了SoftReference类来实现软引用;系统在发生内存溢出异常之前,会把只被软引用的对象进行回收。
用途就是可以做缓存。
弱引用: 非必须的对象,jdk中提供了WeakReference类来实现软引用,比软引用弱一些;垃圾回收不论内存是否不足都会回收只被弱引用关联的对象。
虚引用: 对被引用对象的生存时间不影响;无法通过虚引用来取得一个对象实例;为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知;jdk提供PhantomReference类来实现虚引用。

/**
 * Java引用分为:强引用>软引用>弱引用>虚引用
 *
 */
public class ReferenceTest {
    public static void main(String[] args) {
        System.out.println("===========强引用========");
        //强引用
        Person p = new Person();
        System.gc();//手动执行垃圾回收
        System.out.println(p);
        //软引用
        System.out.println("===========软引用========");
        SoftReference<Person> sp = new SoftReference<Person>(new Person());
        System.gc();
        System.out.println(sp.get());
        System.out.println("---------------软引用在内存溢出的表现-------------------------");
        try {
            List<HeapOOM.OOMObject> list = new ArrayList<HeapOOM.OOMObject>();
while (true) {
                list.add(new HeapOOM.OOMObject());
            }
        } finally {
            System.out.println("内存溢出之后的软引用是否存在:");
            System.out.println(sp.get());
            System.out.println("---------------软引用在内存溢出的表现-------------------------");
            //弱引用
            System.out.println("===========弱引用========");
            WeakReference<Person> wp = new WeakReference<Person>(new Person());
            System.gc();
            System.out.println(wp.get());
            System.out.println("===========虚引用========");
            //虚引用
            ReferenceQueue<Person> referenceQueue = new ReferenceQueue<Person>();
            Person person = new Person();
            PhantomReference<Person> pp = new PhantomReference<Person>(person, referenceQueue);
            person = null;
            System.out.println(referenceQueue.poll());
            System.gc();
            System.out.println(pp.get());
            try {
                //gc后等1秒看结果
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(referenceQueue.poll());
            System.out.println("===================================");
            Properties properties = System.getProperties();
            for (Map.Entry<Object, Object> me : properties.entrySet()) {
                System.out.println(me.getKey() + "=" + me.getValue());
            }
            System.out.println("=================获取传递个JVM的参数=========================");
            System.out.println(System.getProperty("zookeeper.root.logger"));//-Dzookeeper.root.logger=INFO,stdout,R
Person p1 = new Person();
            Person nP = p1;//
            p1 = null;
            System.out.println(nP);
        }
    }
}
class Person {
    String name = "张三";
@Override
    public String toString() {
        return name;
    }
}

3.2引用的可达性分析

垃圾回收的对象,是哪些不被引用的对象,所以我们就得需要判定,知道哪些对象不被引用。判定一个对象不被引用的方法就是可达性分析,在可达性分析出现之前还有一种方式——引用计数器法。

3.2.1引用计数器法

引用计数算法基本思想:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。

Person p1 = new Person();
        System.out.println(p1);
        Person p2 = p1;
        System.out.println(p2);
        p1 = null;
        System.out.println(p2);

Person p1 指向了对象new Person();所以当前对象new Person();对应的引用计数器+1,Person p2 = p1;
自然当前对象new Person();对应的引用计数器在+1,为2。当p1 = null;当前对象new Person();失去一个引用,计数结果-1,当计数结果为0的话,则证明当前对象不被引用,则可以被垃圾回收掉。

3.2.2可达性分析

通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连(即不可达)时,则证明此对象是不可用的。
在这里插入图片描述
常见的gc root对象:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象。
  • 方法区中类静态属性引用的对象。
  • 方法区中常量引用的对象。
  • 本地方法栈中JNI(Java Native Interface即一般说的Native方法)引用的对象。

3.3垃圾回收前的垂死挣扎(finalize)

如果我们判定一个对象不可达,就应该将该对象进行gc垃圾回收掉,但是jvm在进行垃圾回收之前会对这些对象进行一轮的筛选,如果相关对象此时重新和引用链的对象建立起了关联,那么是可以逃脱被gc掉的命运,但是不是所有的对象都有着特权,只有我们在编写类的时候,复写Object类中的一个方法finalize(),也就是说在该方法重重新建立了引用,就可以起死回生。

3.3.1两次标记回收的过程

不可达的对象真正死亡需要两次标记:
当不可达时标记第一次标记,当对象覆盖finalize()方法并且finalize()方法没有被虚拟机调用过,此对象将会放置在一个叫做F-Queue的队列之中,稍后由一个由虚拟机自动建立的、低优先级的Finalizer线程去触发这个方法,但并不承诺会等待它运行结束再执行垃圾回收。
finalize()方法是对象逃脱死亡命运的最后一次机会,稍后GC将对F-Queue中的对象进行第二次小规模的标记,如果对象要在finalize()中重新与引用链上的任何一个对象建立关联那么他被移除出“即将回收”的集合,否则就被回收了。

3.3.2代码示例
public class FinalizeObj {
    public static FinalizeObj obj;
@Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("FinalizeObj finalize called !!!");
        obj = this;//在finalize方法中复活对象
    }
@Override
    public String toString() {
        return "I am FinalizeObj";
    }
public static void main(String[] args) throws InterruptedException {
        obj = new FinalizeObj();
        obj = null; //将obj设为null
        System.gc();//垃圾回收
        System.out.println(":-------------------");
        Thread.sleep(1000);//
        if(obj == null) {
            System.out.println("obj is null");
        } else {
            System.out.println("obj is alive");
        }
System.out.println("第2次调用gc后");
        obj = null;//由于obj被复活,此处再次将obj设为null
        System.gc();//再次gc
        Thread.sleep(1000);
        if(obj == null) {
            //对象的finalize方法仅仅会被调用一次,所以可以预见再次设置obj为null后,obj会被垃圾回收,该语句会被调用
            System.out.println("obj is null");
        } else {
            System.out.println("obj is alive");
        }
    }
}

四、垃圾回收

4.1垃圾回收算法

  • 标记清除算法
    最基础的收集算法是“标记-清除”(Mark-Sweep)算法,此方法分为两个阶段:标记、清除。
    标记要清除的对象,统一清除;
    不足有两个:
    一个是效率问题,标记和清除两个过程的效率都不高;
    另一个是空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
    在这里插入图片描述
    因为经过标记清除之后,会有大量的内存碎片,所以边对空间问题有了一个修正,标记整理算法:
    第一步和第二步和标记清除算法一样,只不过为了产生更多的连续的可用空间,将对象整理成连续的。
    在这里插入图片描述
    这种算法因为运行的效率不高的原因,一般都用在对老年代或者永久代的空间进行垃圾整理。
  • 复制算法
    复制算法:它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。
    **优点:**无内存碎片,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。
    **缺点:**实际可用内存缩小为了原来的一半。
    在这里插入图片描述
    需要知道的是,现代商业虚拟都采用这个复制算法来回收新生代中的对象。
    新生代垃圾回收的过程:
    1、将内存分为一块较大的Eden空间和两块较小的Survivor空间;
    2、每次使用Eden和其中一块Survivor。
    3、当回收时,将Eden和Survivor中还存活着的对象一次性地复制到另外一块Survivor空间上,并清理掉Eden和刚才用过的Survivor空间。
    HotSpot虚拟机默认Eden和Survivor的大小比例是8:1:1;浪费10%。
    新生代垃圾回收过程示意图:
    在这里插入图片描述

4.2三种gc方式

  • Minor GC
    就是新生代的垃圾回收过程
  • Major GC
    老年代或者永久代执行的gc,称之为Major GC
  • Full GC
    Minor GC + Major GC

4.3GC内存分配策略

对象在Eden分配:大多数情况下,对象在新生代Eden区中分配。当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC,此时对象会进入survivor区,当对象满足一些条件后会进入老年代。
对象进入老年代有三种策略:
1)长期存活的对象直接进入老年代
虚拟机给每个对象定义了一个对象年龄(Age)计数器。如果对象在Eden出生并经过第一次Minor GC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor空间中,并且对象年龄设为1。对象在Survivor区中每“熬过”一次Minor GC,年龄就增加1岁,当它的年龄增加到一定程度(默认为15岁),就将会被晋升到老年代中。对象晋升老年代的年龄阈值,可以通过参数-XX:MaxTenuringThreshold设置。在这里插入图片描述
2)在survivor中相同年龄的对象总体积超过survivor一半区域时,大于等于该年龄的对象直接晋升到老年代,无须等到MaxTenuringThreshold中要求的年龄。
3)大对象直接在老年代中被创建
虚拟机提供了一个-XX:PretenureSizeThreshold参数,令大于这个设置值的对象直接在老年代分配。这样做的目的是避免在Eden区及两个Survivor区之间发生大量的内存复制。
在这里插入图片描述

4.4GC空间担保问题

1)在发生Minor GC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代的所有对象总空间,如果这个条件成立,那么Minor GC可以确保是安全的。
2)如果不成立,则虚拟机会查看-XX:HandlePromotionFailure设置值是否允许担保失败。
3)如果允许,那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次Minor GC,尽管这次Minor GC是有风险的;如果小于,或者HandlePromotionFailure设置不允许冒险,那这时也要改为进行一次Full GC。

五、常见的垃圾收集器

收集器就是内存回收的具体实现。
java虚拟机规范没有对收集器应该如何实现有任何规定,因为不同版本、不同厂商的虚拟机提供的垃圾收集器都可能会有很大的差异。

5.1gc过程中的并行和并发

并行(Parallel):指多条垃圾收集线程并行工作,但是此时:用户线程仍然处于线程等待状态。
并发(Concurrent):指用户线程和垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),用户程序在继续运行,而垃圾收集程序运行于另一个cpu上。

5.2常见的垃圾收集器

5.2.1Serial

Serial收集器是最基础、历史最悠久的适合新生代的收集器。
特点:单线程、stop-the-world 、复制算法
缺点:影响用户响应时间
优点:回收时简单高效、对于限定单个cpu环境下,serial收集器由于没有线程交互的开销,专心做垃圾收集,可以获得最高的单线程收集效率。
所以:serial 收集器对于运行在client模式下的虚拟机来说,是一个很好的选择。
serialOld收集器是Serial的老年代收集器,采用“标记-整理”
在这里插入图片描述
-XX:+UseSerialGC

5.2.2ParNew

ParNew收集器其实是Serial的多线程版本,除了他是使用多条线程来进行垃圾回收之外和Serial是完全一样的。新生代收集器
特点:多线程、stop-the-world
缺点:单个cpu下,运行效果甚至没Serial好。
优点:回收时简单高效、对于限定多个cpu环境下,效果比serial好。
所以:parnew收集器是运行在server模式下的首选收集器。
在这里插入图片描述

5.2.3parallel scanvenge

Parallel Scanvenge收集器是一个新生代收集器,采用复制算法。
特点:收集新生代,复制算法,多线程,高吞吐、自适应
1、与其它的收集器侧重垃圾回收时用户的停顿时间不同,它主要侧重与吞吐量,吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)。
停顿时间越短就越适合需要与用户交互的程序,高吞吐量则是可以高效率地利用cpu时间尽快完成任务。
2、他有一个自适应开关(-XX:+UseAdaptiveSizePolicy):打开后,用户只需要把基本的内存数据(堆最大,初始量)设置好,然后设置更关注最大停顿时间或者更关注吞吐量,收集器会把细节参数自动调节。
Parallel Old 老年代收集器,采用标记-整理算法
在这里插入图片描述
-XX:+UseParallelGC

5.2.4ConcMarkSweep/CMS

CMS(concurrent mark sweep)收集器是一个以获取最短回收停顿时间为目标的老年代收集器。
特点:并发收集、低停顿。
基于 标记-清除算法实现,但是整个过程比较复杂一些。过程分为4步:
1、初始标记:仅仅标记GCRoot能直接关联到的对象。速度很快,“stop the world”
2、并发标记:GCRoot Tracing。耗时长和用户线程同步。
3、重新标记:修正并发标记时,由于用户程序运行导致的标记变动。“stop the world”停顿稍长一些。
4、并发清除:耗时长,和用户线程同步。
缺点:吞吐量会变低、浮动垃圾无法处理、标记-清除的碎片(设置参数是 fullgc前开启碎片整理功能,gc停顿时间延长)。
可以兼容的新生代收集器:ParNew和Serial
在这里插入图片描述
-XX:+UseConcMarkSweepGC

5.2.5G1(jdk1.9之后才能使用)

G1(Garbage-First)收集器是当今收集器领域最前沿成果之一。2004年sun发表第一篇G1论文,10年后才开发出G1的商用版本。
hotspot开发团队赋予它的使命:未来替掉CMS收集器。
特点:
1、并行与并发:利用多cpu缩短stop-the-world的时间,使用并发方式解决其它收集器需要停顿的gc动作。
2、分代收集:新老代收集区分对待。
3、空间整合:G1从整理看是基于标记-整理,但是局部看是基于复制算法实现的,不会产生碎片。
4、可预测的停顿:能够让使用者指定在M毫秒的时间片段上,消耗在垃圾回收的时间不得超过N毫秒。
过程:初始标记、并发标记、最终标记、筛选回放。前三个和CMS一致,筛选回放是根据用户设置的停顿目标来选择回收价值最高的进行回收。
在这里插入图片描述
-XX:+UseG1GC

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
为什么要学JVM1、一切JAVA代码都运行在JVM之上,只有深入理解虚拟机才能写出更强大的代码,解决更深层次的问题。2、JVM是迈向高级工程师、架构师的必备技能,也是高薪、高职位的不二选择。3、同时,JVM又是各大软件公司笔试、面试的重中之重,据统计,头部的30家互利网公司,均将JVM作为笔试面试的内容之一。4、JVM内容庞大、并且复杂难学,通过视频学习是最快速的学习手段。课程介绍本课程包含11个大章节,总计102课时,无论是笔试、面试,还是日常工作,可以让您游刃有余。第1章 基础入门,从JVM是什么开始讲起,理解JDK、JRE、JVM的关系,java的编译流程和执行流程,让您轻松入门。第2章 字节码文件,深入剖析字节码文件的全部组成结构,以及javap和jbe可视化反解析工具的使用。第3章 类的加载、解释、编译,本章节带你深入理解类加载器的分类、范围、双亲委托策略,自己手写类加载器,理解字节码解释器、即时编译器、混合模式、热点代码检测、分层编译等核心知识。第4章 内存模型,本章节涵盖JVM内存模型的全部内容,程序计数器、虚拟机栈、本地方法栈、方法区、永久代、元空间等全部内容。第5章 对象模型,本章节带你深入理解对象的创建过程、内存分配的方法、让你不再稀里糊涂。第6章 GC基础,本章节是垃圾回收的入门章节,带你了解GC回收的标准是什么,什么是可达性分析、安全点、安全区,四种引用类型的使用和区别等等。第7章 GC算法与收集器,本章节是垃圾回收的重点,掌握各种垃圾回收算法,分代收集策略,7种垃圾回收器的原理和使用,垃圾回收器的组合及分代收集等。第8章 GC日志详解,各种垃圾回收器的日志都是不同的,怎么样读懂各种垃圾回收日志就是本章节的内容。第9章 性能监控与故障排除,本章节实战学习jcmd、jmx、jconsul、jvisualvm、JMC、jps、jstatd、jmap、jstack、jinfo、jprofile、jhat总计12种性能监控和故障排查工具的使用。第10章 阿里巴巴Arthas在线诊断工具,这是一个特别小惊喜,教您怎样使用当前最火热的arthas调优工具,在线诊断各种JVM问题。第11章 故障排除,本章会使用实际案例讲解单点故障、高并发和垃圾回收导致的CPU过高的问题,怎样排查和解决它们。课程资料课程附带配套项目源码2个159页高清PDF理论篇课件1份89页高清PDF实战篇课件1份Unsafe源码PDF课件1份class_stats字段说明PDF文件1份jcmd Thread.print解析说明文件1份JProfiler内存工具说明文件1份字节码可视化解析工具1份GC日志可视化工具1份命令行工具cmder 1份学习方法理论篇部分推荐每天学习2课时,可以在公交地铁上用手机进行学习。实战篇部分推荐对照视频,使用配套源码,一边练习一遍学习。课程内容较多,不要一次性学太多,而是要循序渐进,坚持学习。      

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值