win 修改jvm时间_JVM深入(二)

GC详解

GC的作用域

GC的作用域如下图所示。

5a511763c3947f0db1e46eeb3137a27e.png

关于垃圾回收,只需要记住分代回收算法,即不同的区域使用不同的算法。

不同区域的GC频率也不一样:

  • 年轻代:GC频繁区域。
  • 老年代:GC次数较少。
  • 永久代:不会产生GC。

一个对象的历程

一个对象的历程的如下图所示。

c2b0a630b226f1d29d349702c75c3f1c.png

JVM在进行GC时,并非每次都是对三个区域进行扫描的,大部分的时候都是对新生代进行GC。 GC有两种类型:

  • 普通GC(GC):只针对新生代 。
  • 全局GC(Full GC):主要是针对老年代,偶尔伴随新生代。

GC的四大算法

引用计数法

引用计数法只需要了解即可,JVM 一般不采用这种方式进行GC。它的原理如下图所示。

73392e6c42180b46bd3fd303ebc48581.png

原理:每个对象都有一个引用计数器,每当对象被引用一次,计数器就+1,如果引用失效,计数器就-1,当计数器为0,则GC可以清理该对象。

缺点:

  • 计数器维护比较麻烦。
  • 循环引用无法处理。

复制算法

年轻代中GC使用的就是复制算法。

1ad3117463c9305c8b271fb2aa2e0591.png

原理:

  • 一般普通GC之后,Eden区几乎都是空的了。
  • 每次存活的对象,都会被从from区和Eden区等复制到to区,from区和to区会发生一次交换,每当GC后幸存一次,就会导致这个对象的年龄+1,如果这个年龄值大于15(默认GC次数,可以修改),就会进入养老区。记住一个点就好,谁空谁是to

复制算法的原理如下图所示。

b7155ac439639685fab9782dae3a27c9.png

优点:

  • 没有标记和清除的过程,效率高。
  • 不会产生内存碎片。

由于Eden区对象存活率极低!,据统计99% 对象都会在使用一次之后引用失效,因此在该区中推荐使用复制算法。

标记清除算法

老年代一般使用这个GC算法,但是会和后面的标记整理压缩算法一起使用。其原理如下图所示。

28e6b5609e913a6909e2183a8334aa29.png

原理:

  • 先扫描一次,对存活的对象进行标记。
  • 再次扫描,回收没有被标记的对象。

优点:不需要额外的空间。

缺点:

  • 需要两次扫描,耗时严重。
  • 会产生内存碎片,导致内存空间不连续。

标记清除压缩算法

标记清除压缩算法,也叫标记整理算法,该算法是在标记清除算法的基础上进行改进的算法,解决了标记清除算法会产生内存碎片的问题,但是相应的耗时可能也较为严重。其原理如下图所示。

3090f111adbe37adbe3ddd7c0ec958a1.png

原理:

  • 先扫描一次,对存活的对象进行标记。
  • 第二次扫描,回收没有被标记的对象。
  • 压缩,再次扫描,将活着的对象滑动到一侧,这样就能让空出的内存空间是连续的。

当一个空间很少发生GC,可以考虑使用此算法。

GC算法小结

内存效率:复制算法>标记清除算法>标记整理算法

内存整齐度:复制算法=标记整理算法>标记清除算法

内存利用率:标记整理算法=标记清除算法>复制算法

从效率上来说,复制算法最好,但是空间浪费较多。为了兼顾所有的指标,标记整理算法会平滑一些,但是效率不尽如意。 实际上,所有的算法,无非就是以空间换时间或者以时间换空间。

没有最好的算法,只有最合适的算法。所以上面说的分代收集算法,并不是指一种算法,而是在不同的区域使用不同的算法。

综上所述:

  • 年轻代,相对于老年代,对象存活率较低,特别是在Eden区,对象存活率极低,99% 对象都会在使用一次之后引用失效,因此推荐使用复制算法。
  • 老年代,区域比较大,对象存活率较高,推荐使用标记清除压缩算法。

JVM 垃圾回收的时候如何确定垃圾?GC Roots又是什么?

什么是垃圾?简单的说,就是不再被引用的对象。,如:

Object object=null;

如果我们要进行垃圾回收,首先必须判断这个对象是否可以回收。 在Java中,引用和对象都是有关联的,如果要操作对象,就要通过引用来进行。

可达性分析算法

可达性分析算法,简单来说就是通过从GC Root这个对象开始一层层往下遍历,能够遍历到的对象就是可达的,不能被遍历到的对象就是不可达的,不可达对象就是要被回收的垃圾。其原理如下图所示。

5caffafa4017afc9f0e5c3ff3ba3a84b.png

一切都是从 GC Root 这个对象开始遍历的,只要在这里面的就不是垃圾,反之就是垃圾。

什么是GC Root?

  • 虚拟机栈中引用的对象。
  • 类中静态属性引用的对象。
  • 方法区中的常量。
  • 本地方法栈中Native方法引用的对象。

如下代码所示:

public class GCRoots{            private byte[] array = new byte[100*1024*1024]; // GC root,开辟内空间!    private static GCRoots2 t2; // GC root;    private static final GCRoots3 t3 = new GCRoots3(); // GC root;        public static void m1(){        GCRoots g1 = new GCRoots(); //GCroot        System.gc();    }        public static void main(String[] args){        m1();    }}

总结:

  • 对于数组,如果只是在类成员中进行定义而没有声明数组大小,不是GC Root;如果已经声明了数组大小,则是GC Root,因为此时它已经开辟了内存空间。
  • 对于静态成员对象属性,只要定义了,不管初始化值是null还是new出了对象,都是GC Root。

JVM常用参数

JVM只有三种参数类型:标配参数X参数XX参数

标配参数

标配参数是指在JVM各个版本之间都非常稳定,很少有变化的参数。如:

java -versionjava -helpjava -showversion
4a7f81e4e6d00f39df17498bb5c23846.png

X参数

X参数只要了解即可,如下X参数用于修改JVM的运行模式。

-Xint          # 解释执行-Xcomp         # 第一次使用就编译成本地的代码-Xmixed        # 混合模式(Java默认)
6d123f5fcee986c86cc85cece64e500a.png

XX参数之布尔型(重点)

-XX: +或者-某个属性值, + 代表开启某个功能,- 表示关闭了某个功能。 如以下代码让程序睡眠21亿秒:

package com.wunian.gc;​//jps -l 查看堆栈信息,获得当前java程序端口号//jinfo -flag PrintGCDetails  5360  查看运行中的java程序,某项虚拟机参数是否开启(输出+号表示开启,-表示关闭)//jinfo -flag MetaspaceSize  6312 查看元空间大小//jinfo -flag MaxTenuringThreshold  6312 查看控制新生代中对象需要经历多少次GC晋升到老年代,默认为15//jinfo -flags 6312 查看指定端口的所有信息//java -XX:+PrintFlagsInitial 查看java环境初始默认值public class GCDemo {​    public static void main(String[] args) throws InterruptedException {        System.out.println("Hello World");        Thread.sleep(Integer.MAX_VALUE);    }}

程序运行后,打开DOS窗口,执行jps -l命令查看堆栈信息。得到当前程序运行的端口号,再执行jinfo -flag PrintGCDetails 端口号命令来查看刚刚运行的Java程序的PrintGCDetails参数是否开启,如果输出参数-XX:后面是-开头,表示没有开启,+开头表示已经开启了。

665aad85b536b410b682ae6a95531622.png

关闭程序,在IDEA配置中添加JVM参数-XX:+PrintGCDetails,再次启动程序,使用刚才的命令再次查看一下PrintGCDetails参数是否开启,输出参数-XX:后面是+开头,说明已经开启了该参数。

843b36d3c7d4d5545fa6790270ed22d2.png

XX参数之key=value型

设置元空间大小为128M:-XX:MetaspaceSize=128m

执行jinfo -flag MetaspaceSize 端口号可以查看指定程序的元空间大小。

f6691e007f2e6fa581a28e2dc55fa44e.png

设置进入老年区的存活年限(默认是15年):-XX:MaxTenuringThreshold=15

该参数主要是控制新生代需要经历多少次GC晋升到老年代中的最大阈值。在JVM中用4个bit存储(放在对象头中),所以其最大值是15。

执行jinfo -flag MaxTenuringThreshold可以查看进入老年区的存活年限。

c883d5698965f4bc6de6468292d1f5b6.png

查看某个端口的所有信息的默认值:jinfo -flags 端口号

-XX:+UseParallelGC表示默认使用的是并行GC回收器。

9612c508baa4f84193318a211c861df8.png

经典面试题:-Xms, -Xmx,是XX参数还是X参数?

1.-Xms表示设置初始堆的大小,等价于:-XX:InitialHeapSize

2.-Xmx表示设置最大堆的大小,等价于:-XX:MaxHeapSize

因此,-Xms, -Xmx是XX参数,这种写法只不过是语法糖,方便书写。

一般最常用的东西都是有语法糖的。

初始的默认值

查看Java 环境初始默认值:-XX:+PrintFlagsInitial,只要在这里面显示的值,都可以手动赋值,但是不建议修改,了解即可。

cdd3d53ec2d68100ae1c21fc3f8e7740.png

=表示是默认值。 :=表示值被修改过。

查看被修改过的值:

java -XX:+PrintFlagsFinal -Xss128k GCDemo   # 查看被修改过的值!启动的时候判断
5b3d51cecbfa7cc62e0a93b70cdc3acb.png

查看用户修改过的配置的XX选项:java -XX:+PrintCommandLineFlags -version

d5d037ecc61799dae28d2867bb7d805e.png

常用的JVM调优参数

  • -Xms:设置初始堆的大小。
  • -Xmx:设置最大堆的大小。
  • -Xss:线程栈大小设置,默认为512k~1024k。
  • -Xmn: 设置年轻代的大小,一般不用改动。
  • -XX:MetaspsaceSize :设置元空间的大小,这个在本地内存中。
  • -XX:+PrintGCDetails :输出详细的垃圾回收信息。
  • -XX:SurvivorRatio:设置新生代中的 Eden/s0/s1空间的比例。例如: uintx SurvivorRatio = 8表示Eden:s0:s1 = 8:1:1 uintx SurvivorRatio = 4表示Eden:s0:s1 = 4:1:1
  • -XX:NewRatio:设置年轻代与老年代的占比。例如: NewRatio = 2表示新生代:老年代=1:2,默认新生代整个堆的1/3。 NewRatio = 4表示新生代:老年代=1:4,默认新生代整个堆的1/5。
  • -XX:MaxTenuringThreshold:进入老年区的存活阈值。例如: MaxTenuringThreshold = 15表示GC15次后存活的对象进入老年区。

常见的几种OOM

java.lang.StackOverflowError

栈溢出,最常见的OOM之一,方法调用自身,示例代码如下:

package com.wunian.gc;/** * 栈溢出 java.lang.StackOverflowError * 方法调用自身 */public class OOMDemo {​    public static void main(String[] args) {        a();    }​    public static void a(){        a();    }}

java.lang.OutOfMemoryError: Java heap space

堆溢出,最常见的OOM之一,字符串无限拼接,示例代码如下:

package com.wunian.gc;​import java.util.Random;/** * 堆溢出  java.lang.OutOfMemoryError: Java heap space * -Xms10m -Xmx10m */public class OOMDemo2 {​    public static void main(String[] args) {        String str="coding";        while(true){            str+=str+new Random(1111111111)+new Random(1111111111);        }    }}

java.lang.OutOfMemoryError: GC overhead limit exceeded

GC回收时间过长(次数过多)也会导致 OOM,可能CPU占用率一直是100%,频繁GC但是没有什么效果。示例代码如下:

package com.wunian.gc;import java.util.ArrayList;import java.util.List;/** *  GC回收时间(次数)过长也会导致 OOM; java.lang.OutOfMemoryError: GC overhead limit exceeded *   -Xms10m -Xmx10m -XX:MaxDirectMemorySize=5m -XX:+PrintGCDetails */public class OOMDemo3 {    public static void main(String[] args) {        int i=0;        List list =new ArrayList<>();        try {            while(true){                list.add(String.valueOf(++i).intern());                /**                 *   String.intern()是一个Native方法,底层调用C++的 StringTable::intern方法实现。                 * 当通过语句str.intern()调用intern()方法后,JVM 就会在当前类的常量池中查找是否存在与str等值的String,                 * 若存在则直接返回常量池中相应Strnig的引用;若不存在,则会在常量池中创建一个等值的String,                 * 然后返回这个String在常量池中的引用。                 */            }        } catch (Exception e) {            System.out.println("i=>"+i);            e.printStackTrace();            throw e;        }    }}

java.lang.OutOfMemoryError: Direct buffer memory

基础缓冲区错误,使用NIO方法分配的本地内存超出了JVM参数设置的最大堆外内存。设置最大Java堆外内存大小:-XX:MaxDirectMemorySize=5m,示例代码如下:

import sun.misc.VM;import java.nio.ByteBuffer;import java.util.concurrent.TimeUnit;/** *  基础缓冲区的错误! java.lang.OutOfMemoryError: Direct buffer memory *  -XX:MaxDirectMemorySize可以设置java堆外内存的峰值 * -Xms10m -Xmx10m -XX:MaxDirectMemorySize=5m -XX:+PrintGCDetails */public class OOMDemo4 {    public static void main(String[] args) throws InterruptedException {        System.out.println("配置的MaxDirectMemorySize"+ VM.maxDirectMemory()/(double)1024/1024+"MB");        TimeUnit.SECONDS.sleep(2);        //故意破坏        //ByteBuffer.allocate();分配 JVM的堆内存,属于GC管辖        //ByteBuffer.allocateDirect();//分配本地OS内存,不属于GC管辖        分配了6M内存,但是jvm参数设置了最大堆外内存是5M        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(6 * 1024 * 1024);    }}

java.lang.OutOfMemoryError: unable to create new native thread

高并发环境下,此错误更多的时候和平台有关,出现此错误的可能原因有:

  • 应用创建的线程太多。
  • 服务器不允许你创建这么多线程。

示例代码如下:

package com.wunian.gc;/** * 服务器线程不够了,超过了限制,也会爆出OOM异常 * java.lang.OutOfMemoryError: unable to create new native thread */public class OOMDemo5 {        public static void main(String[] args) {        for (int i = 1; ; i++) {            System.out.println("i=>"+i);            new Thread(()->{                try {                    Thread.sleep(Integer.MAX_VALUE);                } catch (InterruptedException e) {                    e.printStackTrace();                }            },""+i).start();        }    }}

java.lang.OutOfMemoryError: Metaspace

Java8之后使用元空间代替永久代,使用的是本地内存。元空间主要用于存储:

  • 虚拟机加载类信息
  • 常量池
  • 静态变量
  • 编译后的代码

要模拟元空间溢出,只需要不断的生成类即可,这里需要用到Spring中的Enhancer类,示例代码如下:

package com.wunian.gc;​import org.springframework.cglib.proxy.Enhancer;import org.springframework.cglib.proxy.MethodInterceptor;import org.springframework.cglib.proxy.MethodProxy;​import java.lang.reflect.Method;/** * 元空间溢出 java.lang.OutOfMemoryError: Metaspace * -XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m */public class OOMDemo6 {​    static class OOMTest{}​    public static void main(String[] args) {        int i=0;//模拟计数器        try {            //不断的加载对象!底层使用Spring的cglib动态代理            while (true) {                i++;                Enhancer enhancer=new Enhancer();                enhancer.setSuperclass(OOMTest.class);                enhancer.setUseCache(false);//不使用缓存                enhancer.setCallback(new MethodInterceptor() {                    @Override                    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {                        return method.invoke(o,args);                    }                });                enhancer.create();            }        } catch (Exception e) {            System.out.println("i=>"+i);            e.printStackTrace();        }    }}

深入理解垃圾回收器

GC算法如引用计数算法、复制算法、标记清除算法、标记整理算法都是方法论,垃圾回收器就是这些算法对应的落地的实现。

四种垃圾回收器

1、串行垃圾回收器,单线程工作,执行GC时会停止所有的线程直到GC结束(STW:Stop the World)。其原理如下图所示。

decca770ca7559fc2a83da128b3d22f4.png

2、并行垃圾回收器,多线程工作,也会导致STW。其原理如下图所示。

0636da9232bb7e112c49c15c60bfc288.png

3、并发垃圾回收器,在回收垃圾的同时,可以正常执行线程,并行处理,但是如果是单核CPU,只能交替执行。其原理如下图所示。

010dd871a2afbbd911bdc4f6e28a8543.png

4、G1垃圾回收器,将堆内存分割成不同的区域,然后并发的对其进行垃圾回收。Java9以后为默认的垃圾回收器。其原理如下图所示。

115368b664543160778d699bac9d98a3.png

查看默认的垃圾回收器:java -XX:+PrintCommandLineFlags -version

11e3a15571d52c04b487bca5cc7bf513.png

Java的垃圾回收器有哪些?

Java曾经由7种垃圾回收器,现在有6种。主要垃圾回收器的位置分布和关系如下图所示。

0da8e13baa3164632dd015c9879d3b4c.png

上图中,红色箭头表示新生区中使用了对应的垃圾回收器,在老年区只能使用对应箭头指向的垃圾回收器。蓝色箭头表示曾经的垃圾回收器有过的对应关系。 6种垃圾回收器名称分别是:

  • DefNew : 默认的新一代 【Serial 串行】
  • Tenured : 老年代 【Serial Old】
  • ParNew : 并行新一代 【并行ParNew】
  • PSYoungGen : 并行清除年轻代 【Parallel Scavcegn】
  • ParOldGen: 并行老年区

JVM的Server/Client模式

现在的JVM默认都是Server模式,Client几乎不会使用。以前32位的Windows操作系统,默认都是Client的 JVM 模式,64位的默认都是 Server模式。

垃圾回收器之间的组合关系

上述6种垃圾回收器都是组合使用的,新生区使用了某种垃圾回收器,养老区会使用与之对应的垃圾回收器,并不是自由搭配的。如下图所示。

bb59fe38a74b9de5e01439046e319f0f.png

如何选择垃圾回收器

1、单核CPU,单机程序,内存小。选择-XX:UseSerialGC

2、多核CPU,吞吐量大,后台计算。选择XX:+UseParallelGC

3、多核CPU,不希望有时间停顿,能够快速响应。选择-XX:+UseParNewGC 或者 XX:+UseParallelGC

G1垃圾回收器

以往垃圾回收器的特点

1、年轻代和老年代是各自独立的内存区域。

2、年轻代使用Eden+s0+s1复制算法。

3、老年代垃圾收集必须扫描整个老年代的区域。

4、垃圾回收器原则:尽可能少而快的执行GC。

G1垃圾回收器的原理

G1(Garbage-First)垃圾回收器 ,是面向服务器端的应用的回收器。其原理如下图所示。

e40e65a7fe317a05eb5fe92857a2187d.png

原理:将堆中的内存区域打散,默认分成2048块。不同的区间可以并行处理垃圾,在GC过程中,幸存的对象会复制到另一个空闲分区中,由于都是以相等大小的分区为单位进行操作,因此G1天然就是一种压缩方案(局部压缩)。

使用G1垃圾回收器:-XX:+UseG1GC

G1垃圾回收器最大的亮点是可以自定义垃圾回收的时间。设置最大的GC停顿时间(单位:毫秒):XX:MaxGCPauseMillis=100 ,JVM会尽可能的保证停顿小于这个时间。

G1垃圾回收器的优点

  • 没有内存碎片。
  • 可以精准的控制垃圾回收时间。

强引用、软引用,弱引用和虚引用

主要学习三个引用类:SoftReferenceWeakReferencePhantomReference

10684781e05be67af9f74acef890f8c3.png

强引用

假设出现了异常或OOM,只要是强引用的对象,都不会被回收。强引用就是导致内存泄露的原因之一。

package com.wunian.ref;/** * 强引用 * -XX:+PrintGCDetails -Xms5m -Xmx5m */public class StrongRefDemo {​    public static void main(String[] args) {        Object o1=new Object();//这样定义的默认就是强引用        Object o2=o1;        o1=null;​        System.gc();        System.out.println(o1);//null        System.out.println(o2);//java.lang.Object@6e0be858    }}

软引用

相对于强引用弱化了。如果系统内存充足,GC不会回收该对象,但是内存不足的情况下就会回收该对象。

package com.wunian.ref;​import java.lang.ref.SoftReference;/** * 软引用 *  -XX:+PrintGCDetails -Xms5m -Xmx5m */public class SoftRefDemo {​    public static void main(String[] args) {        Object o1=new Object();//这样定义的默认就是强引用        //Object o2=o1;        SoftReference o2=new SoftReference<>(o1);//软引用        System.out.println(o1);//java.lang.Object@6e0be858        System.out.println(o2.get());//得到引用的值  java.lang.Object@6e0be858        o1=null;        try {            byte[] bytes=new byte[10*1024*1024];        } catch (Exception e) {            e.printStackTrace();        } finally {            System.out.println(o1);//null            System.out.println(o2.get());//null  //由于堆内存不足被回收        }        //System.gc();    }}

弱引用

不论内存是否充足,只要是GC就会回收该对象。

package com.wunian.ref;​import java.lang.ref.WeakReference;​/** * 弱引用 * -XX:+PrintGCDetails -Xms5m -Xmx5m */public class WeakRefDemo {​    public static void main(String[] args) {        Object o1=new Object();//这样定义的默认就是强引用        WeakReference o2 = new WeakReference<>(o1);​        System.out.println(o1);//java.lang.Object@6e0be858        System.out.println(o2.get());//得到引用的值  java.lang.Object@6e0be858​        o1=null;        System.gc();​        System.out.println(o1);//null        System.out.println(o2.get());//null    }}

软引用、弱引用的使用场景

假设现在有一个应用,需要读取大量的本地图片。

1、如果每次读取图片都要从硬盘中读取,影响性能。

2、一次加载到内存中,可能造成内存溢出。

我们的思路:

1、使用一个HashMap保存图片的路径和内容。

2、内存足够,不清理。

3、内存不足,清理加载到内存中的数据。

虚引用

虚就是虚无,虚引用就是没有这个引用。虚引用需要结合队列使用,其主要作用是跟踪对象的垃圾回收状态。

package com.wunian.ref;​import java.lang.ref.PhantomReference;import java.lang.ref.ReferenceQueue;import java.util.concurrent.TimeUnit;/** * 虚引用 */public class PhantomRefDemo {​    public static void main(String[] args) throws InterruptedException {        Object o1=new Object();        //虚引用需要结合队列使用        ReferenceQueue referenceQueue=new ReferenceQueue<>();        PhantomReference objectPhantomReference=new PhantomReference<>(o1,referenceQueue);​        System.out.println(o1);//java.lang.Object@6e0be858        System.out.println(objectPhantomReference.get());//null        System.out.println(referenceQueue.poll());//null​        o1=null;        System.gc();        TimeUnit.SECONDS.sleep(1);​        System.out.println(o1);//null        System.out.println(objectPhantomReference.get());//null        //这好比是一个垃圾桶,通过队列来检测哪些对象被清理了,可以处理一些善后工作        System.out.println(referenceQueue.poll());//java.lang.ref.PhantomReference@61bbe9ba    }}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值