day3-jvm+排序总结

今日总结

  1. 看了黑马jvm35-76p
  2. 写了桶排序和基数排序,今天总结一下所有的排序

JVM 今日学习

直接内存

是操作系统内存

  • 常见于NIO操作,用于数据缓冲区
  • 回收成本高,读写性能高
  • 不受JVM内存回收管理
    • 使用unsafe分配和释放内存(freeMemory)的
    • ByteBuffer实现内部(gc的时候ByteBuffer对象没了,所以这个引用就没了,间接调用后面的)虚引用+回调,用一个Cleaner创建需引用对象,虚引用关联的实际对象被回收以后,就会调用clean方法执行删除任务image.png

内存溢出:java.lang.OutOfMemoryError: Direct buffer memory
为什么快??image.png
image.png
在操作系统划出一个区域,java代码可以直接访问(java和系统都能用)
之前要走两次缓存,现在走一次就行了,少了一次缓冲区的复制操作

垃圾回收!!

判断是否可以被回收

  • 引用计数

会出现循环引用的问题

  • 可达性分析算法

根对象:肯定不会是垃圾的

  • 系统类
  • 操作系统方法(Native Stack)
  • 活动线程
  • 锁(monitor)

所以不会被根引用的都gg

1 四种引用

image.png

  • 强引用
    • new一个对象给一个变量,该变量就强引用了这个对象
    • 就是从gc root能找到
    • 所有gc root对他的链都断了,就gc
  • 软引用
    • 没有被强引用引用
    • 如果gc完了返现内存还是不够,且没有强引用引用时,就会gc软引用
    • 使用场景:
      • 内存敏感的情况下,创建对象时用SoftReference包裹,容易被释放。也可以用弱引用。
      • 软引用本身也占空间。用引用队列清除。在创建SoftReference和引用队列关联。
      • 软引用关联的对象被回收时,软引用自身就会进入队列。
      • 手动remove
  • 弱引用
    • 不管内存是否充足,都会gc
    • 软弱引用可以配合引用队列使用(释放通过引用队列,遍历释放)
    • 使用场景:
      • 用weakReference
  • ??什么事软弱引用没有说
  • 虚引用
    • 必须配合引用队列使用
    • 之前的ByteBffer就是
    • 虚引用引用的对象被垃圾回收时,会进入引用队列,有线程会去队列里找,然后会找,调用clean方法,去删掉(unsafe)
  • 终结器引用
    • 必须配合引用队列使用
    • 对象重写了终结方法没有强引时,虚拟机会创建终结器引用,并放入引用队列,会有线程(优先级很低)去队列里找,调用finallize()方法,并回收。
    • 太复杂不考虑

image.png

2 垃圾回收算法

  • 标记清除

优点:速度快
缺点:会产生碎片

  • 标记整理

优点:没有碎片
缺点:因为要移动,速度较慢

  • 复制

把存活的的放到to,清除from,然后交换from和to,to总是空闲的
优点:不会产生碎片
缺点:会占用双倍的内存空间

3 分代回收

区域划分:
image.png

  • 工作流程

对象一开始分配伊甸园中,新生代放不下时,会触发Minor GC。
gc之后会用到幸存区,使用复制算法活下来的放到幸存区to中,同时寿命+1。
之后在min GC的时候,对伊甸园和幸存区同时复制算法并修改寿命。
当对象寿命达到阈值(最大15,4 bit)时,放到老年代。
当老年代空间不足时,尝试min gc,空间仍然不足,会执行Full GC(时间更长),实在不行就outof Memory

  • tips
    • Minor gc 和Full GC会引发stop the world,暂停其他线程,gc线程先工作
    • 大对象(新生代不够了)直接晋升到老年代。
    • 一个java线程的内存溢出不会导致整个线程gg
    • 相关参数
      image.png

4 垃圾回收器

  1. 串行
    • 单线程
    • 堆内存较小,适合个人电脑

image.png,老年代时标记整理

  1. 吞吐量优先
    • 多线程
    • 堆内存较大,要求多核cpu支持,适合工作在服务器上
    • 单位时间内,stw最短,让整体stw大的事件尽可能小
    • 所有的线程都会暂停去回收垃圾

image.png(并行)新生代复制,老年代标记整理,这两个开关是连带开启的。
多个线程一起回收垃圾。几个核几个线程
image.png可以控制线程数
image.png可以自适应新生代(伊甸园、幸存区、阈值)大小
image.png:会改变堆的大小改变吞吐量的大小。
image.png:最大暂停毫秒数。和上面是冲突的,因为堆大了之后要回收的垃圾多了。

  1. 响应时间优先(cms)
    • 多线程单线
    • 堆内存较大,要求多核cpu支持,适合工作在服务器上
    • 尽可能让单次stw的时间最短,让每个用户体验都好。

image.png:并发标记清除。工作在老年代。
并发失败的时候会退化成image.png单线程,标记整理
image.png:配合上面使用,工作在新生代,复制算法
不需要stw ,可以和用户线程一起执行,工作在老年代
image.png并行(所有工作的)的是cpu核数,并发(处理垃圾的线程)的一般设置为1/4。
image.png:
浮动垃圾:垃圾清理时产生的垃圾,所以需要预留一点空间保留浮动垃圾。
这个值越小,开始gc越早
image.png:标记之前先对新生代做一次垃圾回收,因为新生代的可能会引用老年代,还要做可达性分析等等,但是他们大多有存活不了多久。所以多做了很多无用的查找工作。减少重新标记的压力。

  • 工作流程

image.png
先初始标记(很快),然后并发标记,不影响用户线程,不用stw。
然后重新标记(因为并发标记的时候可能会产生新的垃圾),这里需要stw。
然后开始并发清楚,循环往复

  • 降低了吞吐量,因为给了一核去处理垃圾,剩下的才去做工作,少了一核干事
  • 因为是标记清除,所以产生很多碎片,所以退化一下,到时候垃圾回收的时间就会突然增多。
G1

适用场景:

  • 同时注重吞吐量和低延迟
  • 超大的堆内存,划分为多个大小相等的Regin
  • 整体上是标记整理,区域之间用的是复制算法

image.png:打开

回收阶段

image.png

  • 新生代回收(Young Collection)

image.png
触发新生代垃圾回收,会stw
image.png
用复制算法放进幸存区
image.png
是young gc ,有的会进入老年代,年龄不够的会放在幸存区

  • 新生代回收+并发标记(Young Collection + CM)

Young Gc 进行GC Root的初始标记
老年代占用堆空间比例达到阈值时,进行并发标记(不会STW),由以下jvm参数决定
image.png
image.png

  • 混合收集(Mixed Collection)

队 E、S、O进行一个全面的垃圾回收

  • 进行最终标记:收集并发时没发现的垃圾,会STW
  • 拷贝存活:会STW,并不是将所有的垃圾都回收,因为要保证暂停时间尽可能短,所以会有选择的进行垃圾回收。回收垃圾最多的区域 (这也是为什么叫garbage first)

image.png

排序算法

● 冒泡排序:
每次两两互换,则每次都能挑出最大的放在尾部。可以增加一个标志位,一轮不需要交换的时候就已经排序完成了跳出循环。

private static void bubbleSort(int[] nums) {
    boolean flag = false;
    for (int i = nums.length - 1; i >= 0; i--) {
        for (int j = 0; j < i; j++) {
            if (nums[j] < nums[j + 1]) {
                swap(nums, j);
                flag = true;
            }
        }
        if (!flag) {
            break;
        }
        flag = false;
    }
}

● 选择排序
每次选择后面最小的放在当前位置。

  private static void selectSort(int[] nums) {
        for (int i = 0; i < nums.length - 1; i++) {
            int now = nums[i];
            int max = Integer.MIN_VALUE;
            int index = i;
            for (int j = i + 1; j < nums.length; j++) {
                if (nums[j] > max) {
                    max = nums[j];
                    index = j;
                }
            }
            if (max > now) {
                swap(nums, index, i);
            }
        }
    }

● 插入排序
每次选择一个进来放在他该在的地方。
//插入排序,每次插入一个,放在一个

private static void insertSort(int[] nums) {
    for (int i = 1; i < nums.length; i++) {
        insert(nums, i);
    }
}

private static void insert(int[] nums, int index) {

for (int i = index; i > 0; i--) {
    if (nums[i] > nums[i - 1]) {
        swap(nums, i, i - 1);
    } else {
        break;
    }
}

}

● 希尔排序
插入排序的优化,假如一个增量,因为插入排序在基本有序的情况下效率很高,所以先变成基本有序,效率会提高,但是基本无序的情况下效率会很差
最快可达O1.3

 private static void shellSort(int[] nums) {
        for (int i = nums.length / 2; i >= 1; i /= 2) {
            for (int j = i; j < nums.length; j++) {
                insert(nums, j, i);
            }
        }
    }

private static void insert(int[] nums, int start, int depth) {
    for (int i = start; i > 0; i -= depth) {
        if (i - depth < 0) {
            return;
        }
        if (nums[i] > nums[i - depth]) {
            swap(nums, i, i - depth);
        } else {
            break;
        }
     }
}

● 归并排序
递归,先分成小段,把小段排序了,再把大段排序。中间卡了一个问题,处理小段的时候没有把排序好的赋值给原来的数组,卡了很久。
一般用于数据量大,且稳定的情况下,时间复杂度为O(nlogn)

private static void mergeSort(int[] nums, int left, int right, int[] res) {
        if (left < right) {
            int middle = (left + right) / 2;
            mergeSort(nums, left, middle, res);
            mergeSort(nums, middle + 1, right, res);
            merge(nums, left, middle, right, res);
        }
    }

    private static void merge(int[] nums, int left, int middle, int right, int[] res) {
        int i = left;
        int j = middle + 1;
        int index = left;
        while (i <= middle && j <= right) {
            if (nums[i] > nums[j]) {
                res[index++] = nums[i++];
            } else {
                res[index++] = nums[j++];
            }
        }
        while (i <= middle) {
            res[index++] = nums[i++];
        }
        while (j <= right) {
            res[index++] = nums[j++];
        }
        //这里比较的还是原来的,实际上已经变化了。nums的在之后已经发生了变化了,所以要返回进去
        for (int k = left; k <= right; k++) {
            nums[k] = res[k];
        }
    }

● 快速排序
每次把一个放到其应该在的位置上,前面的比他小,后面的比他大。
然后在将基准前后继续进行找基准。
写的过程中出现了几个问题,第一个就是是和基准比,
第二个,比较写错了
快速排序在基本有序的情况下效率不高。

private static void quickSort(int[] nums, int low, int high) {
        if(low<high){
            int pivot = parition(nums,low,high);
            quickSort(nums,low,pivot-1);
            quickSort(nums,pivot+1,high);
        }
    }

    private static int parition(int[] nums, int low, int high) {
        int pivot =nums[low];
        while (low<high){
            //第一个问题,这里是和基准进行比较。
            while (low<high && nums[high]<=pivot) {
                --high;
            }
//            if(low<high){
                nums[low]=nums[high];
//            }
            //写错了..
            while (low<high && nums[low]>=pivot){
                ++low;
            }
//            if(low<high){
                nums[high]=nums[low];
//            }
        }
        nums[low] = pivot;
        return low;
    }

● 堆排序
以大根堆为例:堆排序用在大数据量下但是只要几个的情况,通过不断调整堆来找到结果。在建堆的时候,都是从叶子结点比较的(n/2),所以当前的节点只要比两个儿子大,一定是最大的的,之后的情况只要考虑如果我这个节点比较小,要放在的位置会在哪里。
这就是建调整的思想,而在取数的时候其实就是取出来放到最下面,然后交换。
前一种是迭代,后一种是递归(位置没有确认)。
时间复杂度为:O(nlog n + n) =O(nlog n)。是建堆和调整

private static int[] heapSort(int[] nums) {
        int[] arr = nums.clone();
        int len = nums.length;
        buildHeap(arr,len);
     for (int i = len-1; i >= 0 ; i--) {
            swap(arr,0,i);
            adjust(arr,0,i);
        }
        return arr;

    }

    private static void buildHeap(int[] arr, int len) {
//        为什么从n/2开始,因为下面的全是叶子节点,不会出现遗漏的情况
        for (int j = len/2; j >= 0; j--) {
            adjust(arr,j,len);
        }
    }
    //因为是从叶子节点开始调整的,所以不用担心孙子节点有比自己大的
    private static void adjust(int[] arr, int i, int len) {
        //当前的根节点
        //相当于是给max找下家
        int max = arr[i];
        int left = 2*i+1;
        for (int j = left; j < len; j= j*2+1) {
            if(j+1<len && arr[j+1]>arr[j]){
                j++;
            }
            //孩子都没我大,无需调整
            if(arr[j] < max){
                break;
            }
            //不是最大,需要调整
            //到下面区找最大的一个放上了
            else {
                arr[i] = arr[j];
                i=j;
            }
        }
        //最终交换的位置
        arr[i] =max;
    }
//    //不是常规思路
//    private static void adjust(int[] arr, int i, int len) {
//        int left = 2*i;
//        int right = 2*i+1;
//        int max = i;
//        if(left<len &&arr[left]>arr[max]){
//            max=left;
//        }
//        if(right<len && arr[right]>arr[max]){
//            max=right;
//        }
//        if(max !=i){
//            swap(arr,max,i);
//            //这里还要进行一次调整,继续向下调整,数值交换了,,所以看看下面有没有更大的
//            adjust(arr,max,len);//
//        }
//    }

● 桶排序
是一种空间换时间的策略,最好是那种分布比较均匀的数据。
将数据分散到n个桶中,再对这些桶内部进行排序。
时间复杂度n*(log n -log m),n == m时变成线性
先设置一个大小,假设均匀每个会有多少,然后计算需要的桶的数量,
然后放进桶里面,,最后将每个桶排序
时间复杂度为O(N+C),其中C=N*(logN-logM)
内部是快速排序。
https://www.cnblogs.com/bigsai/p/13396391.html
这里讲的很清楚

private static void bucketSort(int[] nums) {
        //设置桶的大小,每个桶放5个
        int bucketSize = 5;
        //然后按大小分配桶的个数
        int max = Integer.MIN_VALUE;
        int min = Integer.MAX_VALUE;
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] > max) {
                max = nums[i];
            }
            if (nums[i] < min) {
                min = nums[i];
            }
        }
        //这个是大小,所以要+1
        int bucketCount = (int) Math.floor((max - min) / bucketSize)+1;
        //用一个扩容机制
        int[][] buckets = new int[bucketCount][0];
        for (int num : nums) {
            int index = (int) Math.floor((num - min) / bucketSize);
                buckets[index] = ArrayAppend(buckets[index], num);

        }
        int k = 0;
        for (int i = 0; i < bucketCount; i++) {
            Arrays.sort(buckets[i]);
            for (int tp : buckets[i]) {
                nums[k++] = tp;
            }
        }
    }

    private static int[] ArrayAppend(int[] arr, int num) {
        arr = Arrays.copyOf(arr, arr.length + 1);
        arr[arr.length - 1] = num;
        return arr;
    }

● 基数排序
桶排序的优化每一位是一个桶,每次都要新建桶,时间复杂度是n*k,k是位数。

  private static void RadixSort(int[] nums) {
        //用于记录的的数组,负数0~9,正数10~10
        int max = Integer.MIN_VALUE;
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] > max) {
                max = nums[i];
            }
        }
        //这个记录要走几轮
        int maxDev=0;
        while (max>0){
            max/=10;
            maxDev++;
        }
        //除以10
//        int mod =1 ;
        //当前进行到哪里了
        int dev = 1;

        for (int i = 0; i < maxDev; i++) {
            //每次都不一样
            int [][] count = new int[20][0];
            for (int num : nums) {
                int nowDev =  getDev(num,dev);
                count[nowDev] = ArrayAppend(count[nowDev],num);
            }
            int k = 0;
            for (int j = 0; j < 20; j++) {
                for (int tp : count[j]) {
                    nums[k++] = tp;
                }
            }
            dev*=10;
        }


    }

    private static int getDev(int num, int dev) {
        int res = (num/dev)%10;
        return res+10;
    }

https://blog.51cto.com/u_13281972/4931526
这里总结的挺好,我就不赘述了。
有一部分参考了菜鸟教程,写了三天才写好…太菜了,代码格式在语雀上没问题,复制过来出了点问题。

你画我猜想法

今天找了一个源码试了试可以跑,接下来看懂源码再自己写就可以了。
明天想先把登陆功能简单实现一下,用mybatis试一试,之前都用的mp,面试被问到压根不会。

今日复盘

  1. 写两个排序先看懂思路再去做,显然快了很多,没有浪费太多时间

  2. 今天显然比前两天松懈了一点,需要继续坚持。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值