JVM垃圾回收机制算法分析

JVM内存运行时数据区


一、什么是垃圾回收机制

gc垃圾回收机制&&算法

什么是垃圾回收机制:

不定时去堆内存清理不可达对象。不可达的对象并不会马上就会直接回收,而是至少要经过两次标记的过程。

public class Test {
    public static void main(String[] args) {
        Test test = new Test();
        // test = null;
        System.gc(); // 手动回收垃圾
    }

    @Override
    protected void finalize() throws Throwable {
        // gc回收垃圾之前调用
        System.out.println("垃圾回收机制...");
    }
}
算法:
标记清除
引用计数法
复制算法
标记压缩
分代算法

二、内存溢出与内存泄露的区别

内存溢出:项目需要4G内存,服务器只有3G内存,内存不够用

内存泄漏的定义:对象已经没有被应用程序使用,但是垃圾回收器没办法移除它们,因为还被引用着。

定义很多静态变量,垃圾是不会回收的,这个对象又没有被引用,再申请内存时报错内存泄漏。

三、引用计数算法

概述:给对象中添加一个引用计数器,每当一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器都为0时的对象就是不再被使用的,垃圾收集器将回收该对象使用的内存。  用在eden区。

优缺点:
优点:引用计数收集器可以很快的执行,交织在程序运行中。
缺点:无法检测出循环引用。如父对象有一个子对象的引用,子对象反过来引用父对象,这样他们的引用计数永远不可能为0,而且每次加减非常浪费内存。

引用计数,每个对象都会有一个标记,默认是15次,gc回收时,为0时 gc直接回收掉。不为0时,对象没有被引用减1,被引用加1。如果次数加到大于15,进入到新生代s0区,或者s1区(复制算法),再增加次数比较多的时候进入老年代(分代算法)。

四、复制算法

复制算法--新生代(s0、s1大小是相等的,只有一个区域能存活) 


复制算法优点:连续性,不会产生碎片化(碎片化表示残留)。


五、标记清除与标记压缩算法

标记清除和标记压缩一般用在老年代。



六、分代收集算法 

       根据内存中对象的存活周期不同,将内存划分为几块,Java的虚拟机中一般把内存划分为新生代和老年代,当新创建对象时一般在新生代中分配内存空间,当新生代垃圾连续回收几次之后仍然存活的对象会被移动到老年代内存中,当大对象在新生代中无法找到足够的连续内存时也直接在老年代中创建。

对于新生代和老年代来说,新生代回收频率很高,但是每次回收耗时很短,而老年代回收频率较低,但是耗时会相对较长,所以应该尽量减少老年代GC。

为什么垃圾回收机制如果频繁执行会降低程序效率? 垃圾回收时的停顿现象

       垃圾回收的任务是识别和回收垃圾对象进行内存清理,为了让垃圾回收器可以更高效的执行,大部分情况下,会要求系统进入一个停顿的状态。停顿的目的是为了暂停所有的应用线程,只有这样的系统才不会有新垃圾的产生。同时停顿保证了系统状态在某一个瞬间的一致性,也有利于更好的标记垃圾对象。因此在垃圾回收时,都会产生应用程序的停顿。

新生代:eden区  采用引用计数算法; s0 s1区 采用复制算法。
老年代:采用标记压缩算法,一般不会用标记清除算法,会产生碎片。

七、垃圾收集器与jmeter压力测试工具用法

什么是垃圾收集器?
串行收集器 单线程收集垃圾 效率低
单线程执行回收操作,回收期间暂停所有应用线程的执行,client模式下的默认回收器,通过-XX:+UseSerialGC命令行可选项强制指定。一般不用串行收集器。

并行收集器 多线程收集垃圾 效率高  调优选择并行

并行回收器
并行回收器在串行回收器基础上做了改进,他可以使用多个线程同时进行垃圾回收,对于计算能力强的计算机而言,可以有效的缩短垃圾回收所需的实际时间。
并行回收器工作时的线程数量可以使用XX:ParallelGCThreads参数指定。

Tomcat配置调优测试---Jmeter压力测试工具 

先添加线程组
添加sampler http请求

添加监听器 聚合报告


Average:平均响应时间
Median:中位数,也就是50%用户的响应时间
90%Line:90%用户的响应时间
Min:最小响应时间
Max:最大响应时间
Error%:本次测试中出现的错误的请求的数量/请求的总数
Throughput:吞吐量--默认情况下表示每秒完成的请求数

KB/Sec:每秒从服务器端接收到的数据量

八、tomcat参数调优测试-串行回收

测试串行吞吐量
-XX:+PrintGCDetails -Xmx32M -Xms32M
-XX:+HeapDumpOnOutOfMemoryError
-XX:+UseSerialGC
-XX:PermSize=32M

扩大堆的内存
-XX:+PrintGCDetails -Xmx512M -Xms32M

结论:最大内存越大,吞吐量越高

调整初始堆内存

-XX:+PrintGCDetails -Xmx512M -Xms512M

结果:吞吐量提高

Edit Configuration



九、tomcat参数调优测试-并行回收

并行回收(UseParallelGC)
-XX:+PrintGCDetails -Xmx512M -Xms512M
-XX:+HeapDumpOnOutOfMemoryError
-XX:-UseParallelGC

-XX:PermSize=32M

新生代回收UseParNewGC在jdk9中被移除...

并行合并回收(UseParallelGC)
-XX:+PrintGCDetails -Xmx512M -Xms512M
-XX:+HeapDumpOnOutOfMemoryError
-XX:+UseParallelOldGC
-XX:ParallelGCThreads=8

-XX:PermSize=32M

ParallelGCThreads:设置垃圾收集器并行阶段使用的线程数,一般设置是cpu核数*2


结论:并行回收吞吐量高于串行回收

jvm参数官方文档地址:http://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html

gc调优指南:http://www.oracle.com/technetwork/java/javase/gc-tuning-6-140523.html


深入理解Java虚拟机与参数调优

一、Java内存结构概述


java内存模型和Java内存结构区别:
java内存模型(多线程JVM)
java内存结构(jvm虚拟机存储空间)

jvm内存结构



二、新生代与老年代 



三、堆内存参数配置

虚拟机参数配置

什么是虚拟机参数配置

       在虚拟机运行的过程中,如果可以跟踪系统的运行状态,那么对于问题的故障排查会有一定的帮助,为此,在虚拟机提供了一些跟踪系统状态的参数,使用给定的参数执行Java虚拟机,就可以在系统运行时打印相关日志,用于分析实际问题。我们进行虚拟机参数配置,其实就是围绕着堆、栈、方法区进行配置。

堆的参数配置
-XX:+PrintGC                  每次出发GC的时候打印相关日志
-XX:+UseSerialGC           串行回收
-XX:+PrintGCDetails       更详细的GC日志
-Xms 堆初始值
-Xmx 堆最大可用值
-Xmn 新生代堆最大可用值

-XX:SurvivorRatio            用来设置新生代中eden空间和from/to空间的比例

总结:在实际工作中,我们可以直接将初始的堆大小与最大堆大小相等,这样的好处是可以减少程序运行时垃圾回收次数,从而提高效率。

常用api

import java.text.DecimalFormat;

/**
 * Created by yz on 2018/03/26.
 */
public class JvmDemo {
    public static void main(String[] args) throws InterruptedException {
        byte[] bytes01 = new byte[1*1024*1024];
        System.out.println("分配了1M内存");
        jvmInfo();
        Thread.sleep(3000);
        byte[] bytes02 = new byte[4*1024*1024];
        System.out.println("分配了4M内存");
        jvmInfo();
    }

    /**
     * 将kb转换成M
     * @param maxMemory
     * @return
     */
    private static String toM(long maxMemory){
        float num = maxMemory/(1024*1024);
        DecimalFormat df = new DecimalFormat("0.00");
        String m = df.format(num);
        return m;
    }

    /**
     * 查看最大内存配置信息、当前空闲内存、已使用内存
     */
    private static void jvmInfo(){
        // 最大内存配置信息 默认的堆内存大小
        long maxMemory = Runtime.getRuntime().maxMemory();
        System.out.println("最大内存配置信息:"+maxMemory+","+toM(maxMemory)+"M");
        // 当前空闲内存
        long freeMemory = Runtime.getRuntime().freeMemory();
        System.out.println("当前空闲内存:"+freeMemory+","+toM(freeMemory)+"M");
        // 已使用内存
        long totalMemory = Runtime.getRuntime().totalMemory();
        System.out.println("已使用内存:"+totalMemory+","+toM(totalMemory)+"M");
    }
}
设置最大堆内存

参数:-Xms5M -Xmx20M -Xlog:gc* -XX:+UseSerialGC -XX:+PrintCommandLineFlags


初始值5M 最大值20M  垃圾回收机制回收了5次 

[3.455s][info][gc             ] GC(4) Pause Young (Allocation Failure) 5M->4M(9M) 5.649ms

修改值为:-Xms20M -Xmx20M

垃圾回收机制回收了2次

[3.398s][info][gc           ] GC(1) Pause Young (Allocation Failure) 5M->4M(19M) 3.026ms

jvm参数调优:

1.堆初始值与堆内存最大值一定要保持一致,减少垃圾回收机制次数。

为什么堆初始值越比堆内存最大值越小,垃圾回收机制次数越多?

因为一上来想用20M,但是初始值只有5M,这个时候要去回收,不然没有空间。生产环境初始值20M 最大值4G 等于没有调优。调优目的,就是减少垃圾回收次数,每次垃圾回收时是非常影响效率的。

修改值为:-Xms1024M -Xmx1024M

这次没有垃圾回收机制再回收。生成环境默认值为4G,根据服务器cup配置。


四、配置新生代与老年代调优参数

jvm参数调优:

2.设置新生代与老年代回收比例。

设置新生代与老年代优化参数
-Xmn 新生代大小,一般设为整个堆的1/3到1/4左右
-XX:SurvivorRatio 设置新生代中eden区和from/to空间比例关系 n/1

参数:-Xms20m -Xmx20m -Xmn1m -XX:SurvivorRatio=2 -Xlog:gc* -XX:+UseSerialGC

-XX:SurvivorRatio=2 表示eden区是from区/to区的两倍,这个参数配的不多,主要配新生代与老年代的关系。

-Xmn1m 表示设置新生代内存大小为1m

/**
 * Created by yz on 2018/03/26.
 */
public class JvmDemo {
    /**
     * 设置新生代与老年代优化参数
     * @param args
     */
    public static void main(String[] args){
        // -Xms20m -Xmx20m -Xmn1m -XX:SurvivorRatio=2 -Xlog:gc* -XX:+UseSerialGC
        byte[] bytes = null;
        for (int i = 0; i < 10; i++) {
            System.out.println("i:"+i);
            bytes = new byte[1*1024*1024];
        }
    }
}
[0.370s][info][gc,heap,exit ]    eden space 512K,  57% used [0x00000000fec00000, 0x00000000fec49448, 0x00000000fec80000)
[0.370s][info][gc,heap,exit ]    from space 256K,  16% used [0x00000000fec80000, 0x00000000fec8aaf8, 0x00000000fecc0000)

[0.370s][info][gc,heap,exit ]   to   space 256K,   0% used [0x00000000fecc0000, 0x00000000fecc0000, 0x00000000fed00000)


设置新生代与老年代分代关系的参数 

-Xms20m -Xmx20m -XX:NewRatio=2 -XX:SurvivorRatio=2 -Xlog:gc* -XX:+UseSerialGC

-XX:NewRatio=2 表示老年代是新生代的2倍 新生代与老年代比例 1/3或者1/4 目的是让垃圾回收机制尽量去新生代里去回收,尽量减少老年代进行回收。

/**
 * Created by yz on 2018/03/26.
 */
public class JvmDemo {
    /**
     * 设置新生代与老年代优化参数
     * @param args
     */
    public static void main(String[] args){
        // -Xms20m -Xmx20m -Xmn1m -XX:SurvivorRatio=2 -Xlog:gc* -XX:+UseSerialGC
        // -Xms20m -Xmx20m -XX:NewRatio=2 -XX:SurvivorRatio=2 -Xlog:gc* -XX:+UseSerialGC  使用这个参数
        byte[] bytes = null;
        for (int i = 0; i < 10; i++) {
            System.out.println("i:"+i);
            bytes = new byte[1*1024*1024];
        }
    }
}
[0.345s][info][gc,heap,exit ]    eden space 3456K,  92% used [0x00000000fec00000, 0x00000000fef1f7d0, 0x00000000fef60000)
[0.345s][info][gc,heap,exit ]    from space 1664K,  61% used [0x00000000fef60000, 0x00000000ff060120, 0x00000000ff100000)
[0.345s][info][gc,heap,exit ]    to   space 1664K,   0% used [0x00000000ff100000, 0x00000000ff100000, 0x00000000ff2a0000)
[0.345s][info][gc,heap,exit ]  tenured generation   total 13696K, used 2885K [0x00000000ff2a0000, 0x0000000100000000, 0x0000000100000000)

[0.345s][info][gc,heap,exit ]    the space 13696K,  21% used [0x00000000ff2a0000, 0x00000000ff5716f8, 0x00000000ff571800, 0x0000000100000000)


五、堆溢出解决办法 

设置堆内存大小 -Xms10m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError

-XX:+HeapDumpOnOutOfMemoryError 内存溢出提示报错信息

错误原因:java.lang.OutOfMemoryError: Java heap space 堆内存溢出

/**
 * Created by yz on 2018/03/26.
 */
public class JvmDemo {
    /**
     * 堆溢出解决办法 以下代码需要多少m堆内存
     * @param args
     */
    public static void main(String[] args){
        List<Object> list = new ArrayList<Object>();
        for (int i = 0; i < 10; i++) {
            System.out.println("i:"+i);
            list.add(new byte[1*1024*1024]);
        }
        System.out.println("创建完毕!");
    }
}

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

解决办法:将最大堆内存参数设大些 -Xmx40m

服务器端开发出现此错误时(Tomcat内存溢出),修改Tomcat catalina.sh文件 设置jvm堆内存大小
JAVA_OPTS="-server -Xms800m -Xmx800m -XX:PermSize=256m -XX:MaxPermSize=512m -XX:MaxNewSize=512m"


六、栈溢出解决办法

什么是栈溢出?就是无限的递归引用

栈溢出是在操作一个变量,方法在无限递归调用的时候,就会抛出栈溢出异常 java.lang.StackOverflowError

/**
 * Created by yz on 2018/03/26.
 */
public class JvmDemo {

    private static int count = 0;

    public static void getCount(){
        try {
            count++;
            getCount();
        } catch (Throwable e) {
            System.out.println("最大的深度..."+count);
            e.printStackTrace();
        }
    }

    public static void main(String[] args){
        getCount();
    }
}

解决办法:设置线程最大调用深度

-Xss5m

设置前

最大的深度...11859
java.lang.StackOverflowError

设置后:

最大的深度...243126

java.lang.StackOverflowError

注意:

栈溢出产生于递归调用,循环遍历是不会的,但是循环方法里面产生递归调用,也会发送栈溢出。

/**
 * Created by yz on 2018/03/26.
 */
public class JvmDemo {

    private static int count = 0;

    public static void getCount(){
        try {
            count++;
            //getCount();
            System.out.println(count);
        } catch (Throwable e) {
            System.out.println("最大的深度..."+count);
            e.printStackTrace();
        }
    }

    // 栈溢出是方法中递归调用,不是循环调用方法发生栈溢出。这里不会发生栈溢出
    public static void main(String[] args){
        for (int i = 0; i < 1000; i++) {
            getCount();
        }
    }
}


猜你喜欢

转载自blog.csdn.net/yz2015/article/details/79682061