JVM运行原理及优化之 jstat -gc

2023-10-27

 

  • 我们写好的代码,是要通过JVM才能运行的
  • JVM 想要执行一个类,首先要加载类,在加载类之前,需要先编译成字节码class 文件
  • 然后就执行类的加载过程,JVM 加载类的话,需要类加载器
  • 类加载器是分层级的,遵循双亲委派机制,
    • 最上层是Bootstrap ClassLoder,加载java的核心类库,加载java安装目录下的lib目录的class文件
    • 第二层是Ext ClassLoder,加载一些java的其他类库,加载java安装目录下的lib/ext目录下的class
    • 第三层是Application ClassLoder ,应该程序类加载器,这个类加载器是加载我们写的类
    • 如果我们自定义类加载器的话,那就是第四层
    • 类加载器遵循双亲委派机制,就是说,如果要加载一个类,先去为他的父类能不能加载,如果父类上面还有父类,就继续问,直到顶层。然后顶层说加载不了,就下派到子类,如果所有父类都加载不了,那就自己加载。这么做的好处是,不会重复加载一个类
  • 然后说一下类加载的过程,分这么几步,加载,验证,准备,解析,初始化。 
    • 加载的话,就是刚才说的类加载器去加载类
    • 验证阶段,主要是验证加载的字节码是否符合JVM规范,不然随便瞎写JVM也执行不了
    • 准备阶段,主要是给对象申请内存,然后给变量设置初始值,该设置0的设置0,该设置null的设置null
    • 解析阶段,主要是给符号引用变成直接引用,就是把一些变量什么temp,直接换成物理地址,不然执行的时候JVM也不认识temp是啥
    • 初始化阶段:主要是给变量赋值,准备阶段只是设置了初始值,这个是核心阶段,执行类的初始化,如果发现这个类的父类没有初始化,会先暂停,然后去初始化父类,也是走类加载的一套流程,直到父类加载完了,再执行子类的初始化
  • 这是类加载的过程,加载的类是放到了JVM的元数据空间,也就是永久代。

 

 

 

  • 永久代的话,我们JVM参数一般会给设置个256M,这个一定要设置,绝对不能不设置JVM参数,使用默认JVM参数可能就给新生代分配一两百兆,永久代分配的可能也很少,一旦并发量上来,系统扛不住,永久代一般就放点类和常量池,一般给256M够了,如果给小了,可能导致频繁的Full GC,因为永久代如果满了,会触发Full GC,这个是很坑的
  • 类加载到永久代后,会把类交给字节码执行引擎去执行,画图
  • 执行这个操作是线程去执行的,每个线程都配有一个程序计数器Java虚拟机栈

 

  • 因为Java是支持多线程的,所以必须要有程序计数器记录这个线程执行到哪了
  • Java虚拟机栈在执行每个方法的时候,都会创建一个栈帧,main方法也一样
  • 局部变量都放到这个栈帧中,如果这个方法执行完了,局部变量也就失效了
  • 这里的栈帧如果没有执行完时,其实都是GC Root,垃圾回收时,就是根据这里的局部变量的引用和永久代的引用来判断对象是否存活
  • 我们设置JVM参数的时候,一般都会给Java虚拟机栈 1M的大小,一个系统运行最多几百个线程,不用设置太大,浪费内存,也不能设置太小,容易溢出,特别是递归调用的时候
  • 然后局部变量保存的都是对象的地址,地址指向了JVM堆内存

 

  • 如果是使用ParNew + CMS 垃圾回收器的话,堆内存分年轻代老年代是很明确的,不像G1
  • 然后说一下ParNew垃圾回收器
    • 这个垃圾回收器是回收年轻代的,使用的是多线程回收,不像之前的Serial回收器使用单线程回收。
    • 然后ParNew使用的是复制清除算法,把年轻代分为Eden区 和两个Survivor,JVM 参数默认的占比是 8:1:1,系统运行会把对象创建到Eden区,每次YoungGC 会标记存活对象复制到Survivor0中,再次YoungGC时,再把存活对象复制到Survivor1中。系统运行期间会保证一直有一个Survivor是空着的
    • Eden区的占比有时是可以调优的,如果条件有限,没有大内存的机器,然后对象创建的还特别频繁,存活的对象比较多,那就建议把Eden区比例调低一些,让Survivor大一点,宁可Young GC多一些,也不要让Survivor触发了动态年龄审核或者放不下存活对象。如果放不下那就把这批对象扔到老年代了,Full GC是很慢的。如果是调低Eden,YoungGC会很频繁,但是YoungGC特别快,我通过jstat 看,回收100M垃圾大概也就1ms,所以,如果内存实在不够,降低Eden去比例也不是不可以。但是如果有条件的话最好的话还是加大新生代内存,毕竟YoungGC也是要Stop the World的。
  • 然后继续说ParNew ,它非常适合回收年轻代内存。因为年轻代一般存活的对象是很少的,大多数都是刚创建出1毫秒就变成了垃圾,所以把极少数存活的对象标记出来,复制成本还是很低的,如果像老年代那样采用标记清除算法,那就太慢了

 

  • 然后说一下老年代的垃圾回收器CMS,这个垃圾回收器是使用的 标记-清除 + 整理 算法,我们一般在JVM参数会指定这算法和整理的频率,JVM参数默认是,标记-清除,5次之后,才会去整理内存空间,让对象整齐排列。但是这个默认参数不太好,这样做会有大量的内存碎片,如果某一次从年轻代晋升一个大对象,老年代居然找不到一块连续的内存,就会触发Full GC,那就坑了。我们会把那个值调成0,就是每次CMS垃圾回收后,都会整理内存,虽然每次的回收时间会多一些,但是不会出现内存碎片。

 

  • CMS 垃圾回收分为4个步骤
    • 第一步是初始标记:初始标记的话,只标记GC root直接引用的对象,只有很少一部分,这个阶段需要STW,但是影响不大,这个过程特别快。这个过程也可以优化,JVM 有个参数是初始标记阶段多线程标记,减少STW时间,正常是单线程标记的。
    • 第二步是并发标记:这个阶段是不需要STW的,是和系统并行的处理,系统继续运行,然后垃圾回收线程去追踪第一步标记的GC root,这一步是很耗时的,但是不影响程序执行。因为在垃圾回收时是允许系统继续创建对象的,所以这个过程会有新的对象进来,也会有标记存活的但是现在变成垃圾,这些有改动的对象JVM都会记下来,等待下一步处理。这一步有一个缺点,并发清理时也有这个问题,就是会占用CPU资源。如果是一个4核的机器,那会占用一个CPU去垃圾回收,公式是(cpu核数 + 3)/4。所以一般CPU资源负载特别高的时候,就俩情况,要不是程序的线程太多了。要不就是频繁FullGC,导致的。
    • 第三步是重新标记:重新标记阶段,会把并发标记阶段有改动的对象重新标记,这一步需要STW,不过也是比较快的,因为改动的对象不会特别多,但是要比第一步慢因为要重新判断找个对象是否GC可达。这里也可以通过JVM参数优化,可以通过参数控制,让CMS在重新标记阶段之前尽量触发一次Young GC(尽量YoungGC是因为可能新生代可能刚刚YoungGC不久,那此时就没必要再一次YoungGC了)这样做的好处是,改动的对象中从存活变为垃圾的那部分,就被清理掉了,缩短STW时间。虽然YoungGC也会造成停顿,但是YoungGC一般频率是比较快的,早晚都要执行,现在执行一举两得。
    • 第四步是并发清理,并发清理是和系统并行的,不需要STW。这个阶段是清理前几个阶段标记好的垃圾。
    • 最后,我们通过JVM参数设置,每次Old GC后都重新整理内存,整理阶段会把老年代零零散散的对象排列到一起,减少内存碎片。
  • 在说ParNew + CMS调优之前,我们先说下JVM的几种GC,Young GC,Old GC,Full GC
    • Young GC 和 Old GC 我上面都已经说过了
    • 再说下 Full GC 吧,Full GC就是全面回收整个堆内存,包括新生代、老年代、永久带。整个过程极其的慢,一定要减少Full GC的次数,一般出现频繁的Full GC有几种情况,我们要避免出现这几种情况
      • 第一种是,内存分配不合理,导致Survivor放不下,或者触发了动态年龄审核机制,频繁的往老年代放对象。
      • 第二种,有内存泄漏问题,导致老年代大部分空间被占用,回收都回收不掉,导致每次新生代晋升一点点对象,就放不下了,触发Full GC
      • 第三种,大对象,一般是代码层面的问题,创建了太多的大对象,大对象是直接放入老年代的, 大对象过多会导致频繁触发Full GC
      • 第四种,永久代满了,触发Full GC,我们JVM参数设置256M基本够了,如果不出现代码层面的bug ,一般不会出现这种情况
      • 第五种,有人在代码里误调用了System.gc(),写了这个方法后,如果有机会,JVM就会发生一次Full GC。不过JVM参数可以禁止这种情况,不允许主动调用,我们要加上
  • 一般什么情况下我们要警觉是不是频繁的Full GC了
    • 第一种情况,CPU负载折线上升,特别高
    • 第二种情况,系统卡死了,或者系统处理请求极慢
    • 第三种情况,如果公司有监控系统,会报警。。
  • 然后再说一下ParNew + CMS 调优的问题吧。如果一个系统需要JVM调优,那其实说白了就是Stop the World 太久了,导致系统太卡了。我们说的调优,其实就是减少STW的时间,让系统没有明显的卡顿现象。
  • 然后分析下,需要STW的有几个地方。YoungGC,和Old GC的两个阶段。但是YoungGC一般STW时间特别短,Old GC时间一般会是Young GC的几倍到几十倍,而且占用CPU资源严重。所以,我们优化的重点是让系统减少Old GC的次数。最好让系统只有YoungGC,没有Old GC,更没有Full GC
  • 所以,优化的重点就是尽量不要让对象进入老年代。如果对象进不去老年代,想Full GC都难。这是JVM调优的重点,对象进入老年代的情况也有几种
    • 第一种,对象经过15次YoungGC,依然是存活的,那晋升老年代
      • 这个其实我们是可以优化一下的,因为如果系统1分钟或者30秒一次YoungGC,那没必要非得让对象存活十几分钟才进入老年代,一般存活个两三分钟,这个对象大概率就是要存活很久的了。所以,我们当时是调低了这个参数的,设置了5。不然这个对象一直存活,然后在两个Survivor里来回复制,如果这个对象小一点还好,如果这个对象挺大的,那容易触发Survivor的动态年龄审核机制,让一大批对象进入老年代。所以,该进入老年代的对象,就让他赶紧进去。
    • 第二种,Young GC后存活的对象大小超过Survivor 的50%,那就会触发动态年龄审核机制,如:1岁、2岁、3岁、4岁的对象加起来大于Survivor 的50%,那大于等于4岁的对象全部进入老年代。
    • 第三种,Young GC后存活的对象大于Survivor的大小,那这一批对象直接全部进入老年代,特别坑。
    • 第四种,大对象直接进入老年代,这个JVM参数里是可以设置的,一般我们都设置1M,大于1M的对象进入老年代,一般很少有1M的对象,一般都是个大数组,或者map。
  • 第一种情况和第四种情况,一般是可控的。所以想要优化的话,主要是要在Survivor的大小这块下功夫。我们要避免动态年龄审核和Survivor放不下的情况。要想保证这点,我们就要知道,我们系统的高峰时期,JVM中每秒有多少对象新增,每次YoungGC存活了多少对象。这就需要用 jstat 了。
  • 首先要使用 jstat -gc PID 1000 1000
    • 找到JVM的PID,然后每秒打印一次JVM的内存情况,如果系统访问量比较小,每秒的增长不是很明显,那就把每次的间隔时间调大一点,比如一分钟打印一次
    • 通过这行命令,我们可以看到当时的内存使用情况,有几个列比较重要的数据
    • S0C:Survivor0 的大小
    • S1C:Survivor1 的大小
    • S0U:Survivor0 使用了多少
    • S1U:Survivor1 使用了多少
    • EC:Eden 区的大小
    • EU:Eden 区使用了多少
    • OC:老年代的大小
    • OU:老年代使用了多少
    • MC:永久代的大小
    • MU:永久代使用了多少
    • YGC:YoungGC次数
    • YGCT:YoungGC的总耗时
    • FGC:Full GC次数
    • FGCT:Full GC的总耗时
  • 一般使用 jstat 优化,重点观察这几个指标
    • Eden 区对象的增长速度
      • 上面的几列,通过一行数据是看不出来Eden 区每秒增长多少数据的,所以我们才每秒打印一次,通过上一秒和下一秒EU的数据就可以推断出每秒增长了多少。这个数据进来多打印几行,取个平均值。
    • Young GC 频率
      • 我们我们知道系统启动时间,用YGC的大小除也能算,但是谁没事记得系统什么时候启动的。而且如果我想看高峰时期某一段时间的呢,就看不了了。看几十天的平均值也没什么意义。所以这个高峰时段YoungGC的频率是通过,Eden的大小,除以Eden区对象的增长速度来算的,Eden区对象增长速度,我们已经知道了。
    • Young GC 耗时
      • 这个YoungGC耗时,我们取平均值就行,用YGCT除以YCG,时间除以次数就是每次的耗时。如果说就像看高峰时段的,因为CPU等使用率比较高,可能会影响回收时间,也可以单独看几次的YoungGC,算出时间。
    • Young GC 后多少对象存活
      • 这个指标还是比较重要的,我们要确定每次存活的对象Survovir到底能不能放得下。我们要保证每次存活的对象要小于Survivor的50%,否则就会触发动态年龄审核机制。
    • 老年代对象增量速度
      • 老年代对象增长速度,决定了Old GC的频率。发生Old GC后,FGC那一列也会增长,FGC那一列其实是FullGC 和Old GC的总和。经过优化后的JVM,每次YoungGC不应该进入太多的对象,不进入或者每次进入几兆是比较好的。这个指标我们也要分多次观察,因为只看一次YoungGC晋升的大小是片面的。我们现在已经知道了YoungGC的频率,如果是3分钟一次,那我们就3分钟打印一次内存情况。jstat -gc PID 180000 100,取多次晋升大小的平均值就行。如果晋升的对象特别多,我们需要分析这些对象为什么会进入老年代,上面我说了有四种情况会晋升老年代,到底是哪种情况。是Survivor不够大,还是大对象太多了,或者有内存泄漏导致对象回收不掉,进入了老年代。这个还要具体分析一下的。如果是Survivor太小,我们很轻易就能看出来,如果每次Young GC后S区都是0,那说明存活的对象太多,S区放不下,都进入了老年代。如果S区不是0,有一部分,但是每次回收进入老年代都很多,就有可能是触发动态年龄审核,这个最好再通过GC日志看一下,通过JVM参数可以让系统打印每次GC的日志。如果出现内存泄漏,数据一般是这样的,发现每次FGC次数加1后,老年代并没有多少数据被回收掉,占用了很多。这就大概率是内存泄漏,导致老年代回收不掉。如果是大对象,数据会这样显示,发现及时没有Young GC,OU也会一直在涨,因为大对象是不用经过年轻代的直接进入老年代。如果内存泄漏和大对象的情况,我们可以用 jmap 打印一份内存快照,用MAT工具分析一下到底是什么对象特别大,通过分析出来的堆栈信息就可以定位到代码的位置。
    • Full GC 频率多高
      • 看这个频率和看YoungGC的频率是一样的,可以看高峰时期某几次的平均值。这个Full GC是很耗时的,Full GC的频率我们最好控制在一天1次或者几天一次的范围。特别是对时效性要求比较高的系统,一定要减少Full GC次数。
    • 一次Full GC 的耗时
      • 这个可以取平均值,也可以取某一段的。我们会发现这个Full GC的耗时是YoungGC的好多倍
  • ParNew + CMS 的原理和优化大概就是这么样的,下面我说一下现在比较流行的G1回收器
  • 有人说G1比ParNew +CMS好,可以全面取代,没必要用ParNew和CMS了,我觉得不是这样的。G1有G1的优点,但是也有缺点。我们选择垃圾回收器还是要根据系统的实际情况来看。但是ParNew+CMS的确有不足的地方,如果某些系统使用的机器是大内存,16G、32G,那每次GC都要等Eden区放满了才执行垃圾回收,一次回收好几G的垃圾,那太慢了,可能停顿时间几十上百毫秒,Full GC甚至要几秒。那就太坑了,不可以接收。这个时候就必须用G1了。
  • 那什么情况下用ParNew+CMS呢,它又什么优点
    • 它的优点就是我们可以优化到极致,极致到没有Full GC只有YoungGC。但是G1不行,我们对G1的优化只能是尽可能的优化预定的停顿时间,其他的我们没法参与太多,因为它什么时候YoungGC我们都不确定。
    • G1的内存使用率是没有ParNew +CMS高的,G1有这么一个机制,如果G1的某一个Region存活对象达到了85%,那就不会去回收这个Region,但是那15%呢。如果是垃圾也回收不掉了。
    • G1的掌控性没有ParNew + CMS好,说白了就是心里没底。我们使用ParNew + CMS可以很确定多久YoungGC,对象增长速度等等等等吧,我们都能看到。但是G1什么时候垃圾回收我们都不知道,如果出现了内存泄漏,如果不是几个G的内存泄漏,我们也很难察觉出来。使用ParNew + CMS可以放心一些,不用搞个活动心惊胆战的。
  • 所以总结来说,如果是4核8G的机器,尽量还是用ParNew + CMS垃圾回收器,如果是大内存机器,就是用G1。
  • 然后说一下G1的原理吧,G1把堆内存平均分成了多个大小相同的Region,我们首先要设置堆内存的大小,然后G1会根据堆大小除以2048,分成2048个大小相同的Region。G1也是有年轻代、老年代的概念,但是只是概念。没有ParNew+CMS分的那么清楚。G1里的年轻代和老年代都是基于Region的,某些Region属于年轻代,某些Region属于老年代,由G1动态控制。但是现在属于年轻代的Region并不永远都是年轻代,如果年轻代的Region被回收了,下次这个Region可能就存放老年代的数据了。所以,G1的年轻代和老年代都是动态的,但是也有个上限。系统刚开始运行时,会给年轻代分配5%的Region来存放对象,年轻代最多可以占用60%的Region,这60%可以通过JVM参数指定,默认是60%,不过这个一般默认就好,如果达到了目标值,就会强制触发YoungGC
  • G1的年轻代也是分Eden和Survivor的,因为G1整体使用的都是复制回收算法。只是某些Region属于Eden,某些Region属于Survivor,系统新创建的对象会被分配到属于Eden的Region,如果垃圾回收就把存活对象复制到Survivor中。
  • G1的一个特点就是我们可以设置一个预期的停顿时间,也就是STW的时间,比如,某个系统的时效性要就特别高,每次GC我只允许STW的5ms,那我们就可以通过JVM参数设置成5ms的停顿,这样G1在垃圾回收时,就会把时间控制在5ms以内
  • G1的垃圾回收不一定是年轻代满了,或者老年代满了才去回收。如果是那样,就和ParNew+CMS没区别了,大内存机器也要STW好久。G1是基于每个Region的性价比去回收的,比如,Region1里有20M对象,回收2ms,Region2里有50兆对象回收要4ms。如果我们设置系统停顿时间为5ms,那G1会在要求的时间内,尽可能回收更多的对象,它会选择Region2,因为性价比更高。所以,我们系统运行,一直往Eden放对象,如果G1觉得,此时回收一下垃圾,差不多要5ms,那可能G1就回去回收,不会等到年轻代占用60%才去回收。
  • G1中年轻代的对象什么情况下会进入老年代
    • 其实和ParNew + CMS整体上是差不多的,只有大对象的处理不一样
    • 1、YoungGC存活的对象Survivor放不下
    • 2、YoungGC存活的对象达到Survivor的50%,触发动态年龄审核
    • 3、对象到达了15岁,进入老年代
    • G1中大对象不会进入老年代,而是专门有一部分Region存放大对象用。如果一个Region放不下大对象,那就会横跨几个Region来存放。
  • G1的Old G也不是我们能控制的,G1会根据自己的判断觉得该回收的时候就会回收,不过也是基于复制算法的
  • G1的混合回收,如果老年代占比45%,就会触发混合回收,回收整个堆内存,但是混合回收也是会控制在我们设置的停顿时间的范围内的,如果时间不够,就会分多次回收。混合回收有点和CMS的回收类似
    • 第一步,初始标记
      • 初始标记需要STW,这一步只标记GC Root直接应用的对象,速度很快
    • 第二步,并发标记
      • 和系统并行,深入的追踪GC Root,标记所有存活的对象,此时系统新创建的对象会被JVM记录,这一步不需要STW
    • 第三步,重新标记,重新标记第二步有改动的对象,要STW。因为只有一小部分改动,速度很快
    • 第四步,混合回收,只有这一步和CMS不一样,CMS这里的回收时和系统并行的。但是G1的混合回收需要STW。混合回收不仅会回收老年代,还会回收新生代和大对象。如果一次性全回收掉,那时间就太久了,可能达不到我们设置的预期停顿时间,所以G1这里是分几批来回收的,回收一次,系统运行一会,然后再回收一次。JVM参数可以设置这个值,分几次去回收,默认值是8次,分8次回收。混合回收还有一个参数我们可以设置,就是空闲的Region达到百分之多少,停止回收,默认是5%
  • G1何时会触发Full GC,其实G1的混合回收就相当于ParNew + CMS的Full GC了,因为回收了所有的区域,只不过回收时间可以控制在我们指定的范围内。但是G1的Full GC就没法控制了,可能要卡顿特别久才能回收完。什么情况下会出现呢,因为G1的整体是基于复制算法的,如果回收的过程中,发现存活对象找不到可以复制的Region,放不下了。那就Full GC,开始单线程标记、清理、整理空闲出一批Region,这个过程很慢
  • 然后说一下G1的优化,G1比较智能,我们可以参与优化的点很少,我们只能合理的设置停顿时间,不要太小也不要太大,太小GC会太频繁,每秒都在GC。太大的话,停顿时间太久了也不好。
  • 平时我们选择垃圾回收器要根据不同的场景具体去分析,该使用那个。没有绝对的好坏。优化也没有一个统一的标准。比如YoungGC和Full GC多久一次好,YoungGC、Full GC耗时多久比较好。这个还是看系统的,只要不影响系统使用,没有卡顿感,我觉得都是好的。而且有些系统内部使用的,即使卡顿一会也无所谓,如果优化的话,用大内存机器成本也在那呢,不用做没必要的优化。

 

 

  • 说说平时工作怎么JVM调优的
    • 如果开发一个新系统,JVM的调优不是一次性就调完的,要分几次去看
      • 第一步,系统开发完需要自己预估一个JVM参数,也就是你预估每秒大概会有多少的对象进入,然后选几台机器,把内存比例设置的合理一些就好了,一般公司都会有一套公司级的通用JVM参数模板,如果是刚开发完,可以直接使用通用模板,反正测试环境还要压测
      • 第二步,测试环境系统压测,使用工具模拟1000人或几千人同时使用,造成每秒几百上千的请求压力,响应时间要控制在200ms。然后压测期间我们需要通过 jstat 去看下内存使用情况,就是我之前说的那些,什么Eden区增长速度,各个GC的频率啊,有没有内存泄漏情况等等吧。如果观察YoungGC和Full GC频率没什么问题,系统没有卡顿现象,就可以上线了。
      • 第三步,如果公司有监控系统,就持续监控,如果没有就每天高峰时期,通过jstat查看一下机器的JVM运行状态,如果需要优化,就继续优化

 jstat -gc PID 1000 1000

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

JVM运行原理及优化之 jstat -gc 的相关文章

  • Golang笔记:使用exec包执行外部程序与Shell命令

    文章目录 目的 使用演示 Cmd结构体 总结 目的 程序中执行外部程序是比较常用的功能 Golang执行外部程序可以使用标准库中的 os exec https pkg go dev os exec 这个包默认是用来执行外部程序的 可以通过调
  • 3 分钟掌握 Node.js 版本的区别

    在我们日常开发中 Node js 使用场景越来越多 大到服务端项目 小到开发工具脚本 所以掌握 Node js 一些基础知识是非常有必要的 今天主要聊一下 Node js 中 LTS 和 Current 的区别和如何选择合适的版本 一 版本
  • 前端在线预览word,excel,pdf

    前端在线预览word excel pdf 预览Word 预览pdf 预览Excel 预览Word 微软的在线预览功能 可以预览word ppt Excel PDF 局限 需要外网能访问文件 如果是只能内网用的系统就不适合了 XDOX预览 局
  • Linux组成员共享目录

    背景 在实际工作中常常会遇到这样的问题 一台服务器上的多个用户需要合作完成一个项目 他们都有自己的home目录和初始私有组 而他们需要同时再同一个目录下做开发 这时就会用到组成员共享目录 下面介绍如何让组成员再同一目录协同工作 以下操作默认
  • ./darknet: error while loading shared libraries: libcudart.so.8.0: cannot open shared object file:

    libcudart so 8 0 不能找到 是库文件路径引发的问题 可以到 etc ld so conf d目录下 修改其中任意一份conf文件 可以自建conf 以方便识别 将lib所在目录写进去 然后在终端输入 ldconfig 更新缓
  • Android客户端连接SSM(Spring+SpringMVC+Mybatis)框架Java服务器端

    Android客户端开发越来越流行 但是 Android客户端对应的服务器端采用什么框架呢 对于功能较为简单的应用 我建议 直接采用java web开发最简单的MVC框架即可 很多Android应用的服务器端开发框架 我都是采用这种 但是
  • Python安装及建立虚拟环境

    文章目录 前言 一 python安装 二 建立虚拟环境 三 安装jupyter notebook 总结 前言 Python由荷兰数学和计算机科学研究学会的吉多 范罗苏姆 于1990 年代初设计 作为一门叫做ABC语言的替代品 Python提
  • MongoDB 系统管理与操作详解

    一 MongoDB 启动与关闭 1 启动MongoDB 执行mongod命令即可启动MongoDB服务器 mongod在启动时可使用许多可配置选项 在命令行中运行mongod help可列出这些选项 下列选项十分常用 需着重注意 dbpat
  • JVM——垃圾回收

    垃圾回收 文章目录 垃圾回收 垃圾回收概述 什么是垃圾 为什么要回收垃圾 内存溢出和内存泄漏 java垃圾回收机制 自动内存管理 应该关心哪些区域的回收 垃圾回收相关算法 垃圾标记阶段算法 如何标记一个垃圾对象 什么样的对象能被标记为垃圾对
  • IntelliJ IDEA 高级调试技巧

    一 条件断点 循环中经常用到这个技巧 比如 遍历1个大List的过程中 想让断点停在某个特定值 参考上图 在断点的位置 右击断点旁边的小红点 会出来一个界面 在Condition这里填入断点条件即可 这样调试时 就会自动停在i 10的位置
  • WinPcap实战(一)——发送ARP包

    ARP包的结构 ARP包格式 物理帧头 14B ARP帧结构 28B 填充数据 18B CRC 4B 这里给出一张图 图中没有18字节的填充数据和4字节的校验位 物理帧头 14B 目的MAC 6B 源MAC 6B 类型 2B ARP帧 0x
  • 小程序通过webview实现本地任意文件上传

    微信小程序做文件上传的时候 只能选择相册的图片或者视频 没办法选择手机内存卡里的文件 比如 word pdf文件等等 下面可以通过 webview 的方式 借用 h5 的方式即可实现上面的功能 添加业务域名 webview里面打开的地址 首
  • Mybatis操作数据库--通用

    选中 xml 右键 Junite 测试的时候 Test位于这个包中 import org junit jupiter api Test 根据条件进行查询总记录数 条件如果涉及到两个表的内容 你也就只写一个表里面的 select count
  • 简易版python爬虫--通过关键字爬取网页

    背景 帮同学写了个爬虫程序 特此记录 怕以后忘了 这里是爬取百度https www baidu com 不为什么 主要就是百度老实 能爬 爬着简单 爬着不犯法 关键字爬取基本模板 import requests from bs4 impor
  • Flutter学习四:Flutter开发基础(五)资源管理

    目录 0 引言 1 资源管理 1 1 指定 assets 1 2 Asset 变体 variant 1 3 加载 assets 1 3 1 加载文本 1 3 2 加载图片 1 3 2 1 声明分辨率相关的图片 1 3 2 2 加载图片 1
  • SIFT特征提取分析

    SIFT Scale invariant feature transform 是一种检测局部特征的算法 该算法通过求一幅图中的特征点 interest points or corner points 及其有关scale 和 orientat
  • 音视频开发(13)---视频监控系统必须知道的一些基础知识

    视频监控系统必须知道的一些基础知识 前言 在视频监控系统中 视频文件的传输带宽很重要 那视频文件的传输带宽怎么计算呢 首先给大家介绍几个名词 正文 1 比特率 比特率是指每秒传送的比特 bit 数 单位为bps BitPerSecond 比
  • XSS靶场第三关秘籍

    查看页面源代码 直接插入我们的代码进行尝试 没有弹窗 查看页面源代码 发现我们的 被实体编码了 这里把 改成
  • DBUtils工具类的使用方法详解

    DBUtils使用方法详解 目录 DBUtils使用方法详解 一 前言 二 JDBC介绍 1 基本概念 2 JDBC访问数据库的流程 三 DBUtils介绍 1 基本概念 2 配置文件 3 创建JDBCUtils类 4 实现对数据表的增删改
  • springboot整合knife4j,从此告别手写接口文档

    关于knife4j Knife4j的前身是swagger bootstrap ui 前身swagger bootstrap ui是一个纯swagger ui的ui皮肤项目 一开始项目初衷是为了写一个增强版本的swagger的前端ui 但是随

随机推荐

  • 递归求和

    用阶乘求1到100的和 思路 递归结束条件 n 1 递归公式 n sum n 1 include
  • 泛型T E K V ?

    Java泛型中E T K V等的含义 Java泛型中的标记符含义 E Element 在集合中使用 因为集合中存放的是元素 T Type Java 类 K Key 键 V Value 值 N Number 数值类型 表示不确定的java类型
  • 蓝牙模块调试总结(BLE\HC-05\HC-06\HC-01\MLT-BT05)

    最近在使用蓝牙模块与手机进行联合调试 遇到了一些问题 在此总结一下 1 蓝牙模块分类 MLT BT05 4 0蓝牙模块 指令集详细说明 MLT BT05 4 0 蓝牙串口模块指令为 Command 指令集 注 发 AT 指令时必须回车换行
  • 针对三角形问题,使用边界值分析法设计测试用例

    一 测试问题描述 输入三个整数a b c 分别作为三角形的三条边 通过程序判断这三条边是否能构成三角形 如果能构成三角形 则判断三角形的类型 等边三角形 等腰三角形 一般三角形 要求输入三个整数a b c 必须满足以下条件 1 a 200
  • Satck与Queue要点

    Satck Stack定义 核心接口 stack源码 栈的实现 静态数组实现栈 动态数组实现栈 链表实现栈 queue queue与stack queue的实现 Satck Stack定义 stack的特点是先进先出 与queue正好相反
  • 一个局域网中,不同网段的服务器能通信吗?

    文章目录 起步 环境说明 B ping A A ping B 子网的误判 感谢 起步 在一个局域网中存在两台服务器 A B A IP 192 168 211 110 NETMASK 255 255 255 0 B IP 192 168 21
  • QT 新手小白USBCAN 学习

    一 介绍CAN总线 CAN总线介绍 二 USBCAN总线 2 1 产品介绍 USBCAN 转换器模块实现了将 CAN 总线接口与 USB 接口进行相互转换 可以 简单方便的通过电脑监视 CAN 总线网络 同时可以实现工业现场数据稳定的双 向
  • php xml数据类型,PHP实现XML与数据格式进行转换类实例

    xml2array will convert the given XML text to an array in the XML structure Link http www bin co com php scripts xml2arra
  • Java架构师成长升级历程

    目录 前言 一 学习途径整理 目录 前言 一 学习途径整理 二 如何高效的学习 三 书籍推荐 MQ相关 云原生相关 Redis相关 架构相关 Mybaits 四 优秀博文整理 五 极客时间 六 硬核公众号推荐 七 宝藏学习网站 八 架构设计
  • just4fun:神奇的代码生成好看的图片

    原文链接 用三段 140 字符以内的代码生成一张 1024 1024 的图片 在VS2015下 试了一下生成下图的代码 生成ppm格式图片的代码如下 include
  • 论文笔记:TIMESNET: TEMPORAL 2D-VARIATION MODELINGFOR GENERAL TIME SERIES ANALYSIS

    ICLR 2023 1 intro 时间序列一般是连续记录的 每个时刻只会记录一些标量 之前的很多工作着眼于时间维度的变化 以捕捉时间依赖关系 gt 可以反映出 提取出时间序列的很多内在特征 比如连续性 趋势 周期性等 但是现实时间序列数据
  • 服务器运行tomcat报错误java.security.UnrecoverableKeyException: Cannot recover key

    问题 项目部署在阿里云服务器上 一直都是正常运行 因业务需要重启服务器 之后就启动tomcat 然后就发现启动不了了 报错 java security UnrecoverableKeyException Cannot recover key
  • MVS、SFM的区别和联系

    文章目录 一 双目相机重建步骤 二 SFM重建步骤 三 MVS重建步骤 四 SFM 和MVS 各自的目的 五 传统MVS和深度学习MVS对比 一 双目三维重建 binocular stereo 输入左右两张图片 二 SFM Structur
  • 法律法规

    法律体系 我国法律体系基本框架 由宪法和宪法相关法 民法商法 行政法 经济法 社会法 刑法 诉讼及非诉讼程序法构成 法的形式 法的形式的4个含义 1 法律规范的创制机关的性质及级别 2 法律规范的外部表现形式 3 法律规范的效力等级 4 法
  • 平时都用Python绘制二维图,如果用来绘制三维图会有什么效果?

    前言 本文的文字及图片来源于网络 仅供学习 交流使用 不具有任何商业用途 版权归原作者所有 如有问题请及时联系我们以作处理 通常我们用 Python 绘制的都是二维平面图 但有时也需要绘制三维场景图 比如像下面这样的 这些图怎么做出来呢 今
  • 备赛电赛学习STM32篇(九):ADC

    目录 一 ADC的简介 二 逐次逼近型ADC 2 1 逐次逼近型ADC框图 2 2 STM32 ADC内部介绍 2 2 1 STM32ADC的通道以及存储数据的寄存器 2 2 2 触发方式 2 2 3 STM32ADC时钟部分 2 2 4
  • 机器学习——聚类——距离聚类法——K-means

    目录 理论部分 1 1 聚类概念 1 1 1 定义 1 1 2 与分类的区别 1 2 相似度测量 1 2 1 欧式距离 1 2 2 马氏距离 1 3 聚类准则 1 3 1 试探方法 1 3 2 聚类准则法 1 4 常见聚类方法 1 5 K均
  • 判断(if)语句

    先说一下if语句的应用场景 生活中我们会有这样那样的如果 发工资为例 我们将它转化成代码 if 今天发工资 先还信用卡的钱 四个空格或tap键 不能混用 if 有剩余 就要买买买 else 难受ing else 期待下个月 正是有了判断 我
  • C#使用protobuf简述

    编译依赖项 首先 创建一个C 4 5以上project 因为最新的protobuf依赖于C 5 0的语言特性 然后 我们通过nuget 为项目添加对protobuf的引用 搜索protobuf就可以开始安装了 安装完成后 可以编译一次工程
  • JVM运行原理及优化之 jstat -gc

    我们写好的代码 是要通过JVM才能运行的 JVM 想要执行一个类 首先要加载类 在加载类之前 需要先编译成字节码class 文件 然后就执行类的加载过程 JVM 加载类的话 需要类加载器 类加载器是分层级的 遵循双亲委派机制 最上层是Boo