JVM垃圾回收

GC区域Eden Survivor(from,to), Old Gen和Perm Gen

VM区域总体分两类,heap区和非heap区。
heap区又分为:

  • Eden Space(伊甸园)、
  • Survivor Space(幸存者区)、
  • Old Gen(老年代)。

非heap区又分:

  • Code Cache(代码缓存区);
  • Perm Gen(永久代);
  • Jvm Stack(java虚拟机栈);
  • Local Method Statck(本地方法栈);

下面我们对每一个内存区域做详细介绍。
Eden Space字面意思是伊甸园,对象被创建的时候首先放到这个区域,进行垃圾回收后,不能被回收的对象被放入到空的survivor区域。

Survivor Space幸存者区,用于保存在eden space内存区域中经过垃圾回收后没有被回收的对象。Survivor有两个,分别为To Survivor、 From Survivor,这个两个区域的空间大小是一样的。执行垃圾回收的时候Eden区域不能被回收的对象被放入到空的survivor(也就是To Survivor,同时Eden区域的内存会在垃圾回收的过程中全部释放),另一个survivor(即From Survivor)里不能被回收的对象也会被放入这个survivor(即To Survivor),然后To Survivor 和 From Survivor的标记会互换,始终保证一个survivor是空的。

Eden Space和Survivor Space都属于新生代,新生代中执行的垃圾回收被称之为Minor GC(因为是对新生代进行垃圾回收,所以又被称为Young GC),每一次Young GC后留下来的对象age加1。

注:GC为Garbage Collection,垃圾回收。

Old Gen老年代,用于存放新生代中经过多次垃圾回收仍然存活的对象,也有可能是新生代分配不了内存的大对象会直接进入老年代。经过多次垃圾回收都没有被回收的对象,这些对象的年代已经足够old了,就会放入到老年代。

当老年代被放满的之后,虚拟机会进行垃圾回收,称之为Major GC。由于Major GC除并发GC外均需对整个堆进行扫描和回收,因此又称为Full GC。

heap区即堆内存,整个堆大小=年轻代大小 + 老年代大小。堆内存默认为物理内存的1/64(<1GB);默认空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制,可以通过MinHeapFreeRatio参数进行调整;默认空余堆内存大于70%时,JVM会减少堆直到-Xms的最小限制,可以通过MaxHeapFreeRatio参数进行调整。

下面我们来认识下非堆内存(非heap区)
Code Cache代码缓存区,它主要用于存放JIT所编译的代码。CodeCache代码缓冲区的大小在client模式下默认最大是32m,在server模式下默认是48m,这个值也是可以设置的,它所对应的JVM参数为ReservedCodeCacheSize 和 InitialCodeCacheSize,可以通过如下的方式来为Java程序设置。

-XX:ReservedCodeCacheSize=128m

CodeCache缓存区是可能被充满的,当CodeCache满时,后台会收到CodeCache is full的警告信息,如下所示:
“CompilerThread0” java.lang.OutOfMemoryError: requested 2854248 bytes for Chunk::new. Out of swap space?

注:JIT编译器是在程序运行期间,将Java字节码编译成平台相关的二进制代码。正因为此编译行为发生在程序运行期间,所以该编译器被称为Just-In-Time编译器。

Perm Gen全称是Permanent Generation space,是指内存的永久保存区域,因而称之为永久代。这个内存区域用于存放Class和Meta的信息,Class在被 Load的时候被放入这个区域。因为Perm里存储的东西永远不会被JVM垃圾回收的,所以如果你的应用程序LOAD很多CLASS的话,就很可能出现PermGen space错误。默认大小为物理内存的1/64。

https://blog.csdn.net/shiyong1949/article/details/52585256/

Java最大线程数

https://www.cnblogs.com/princessd8251/articles/3914434.html
JVM中可以生成的最大数量由JVM的堆内存大小、Thread的Stack内存大小、系统最大可创建的线程数量(Java线程的实现是基于底层系统的线程机制来实现的,Windows下_beginthreadex,Linux下pthread_create)三个方面影响。具体数量可以根据Java进程可以访问的最大内存(32位系统上一般2G)、堆内存、Thread的Stack内存来估算。
ThreadStackSize JVMMemory 能创建的线程数
默认的325K -Xms1024m -Xmx1024m i = 2655
默认的325K -Xms1224m -Xmx1224m i = 2072
默认的325K -Xms1324m -Xmx1324m i = 1753
默认的325K -Xms1424m -Xmx1424m i = 1435
-Xss1024k -Xms1424m -Xmx1424m i = 452
增大堆内存(-Xms,-Xmx)会减少可创建的线程数量,增大线程栈内存(-Xss,32位系统中此参数值最小为60K)也会减少可创建的线程数量

JVM参数调优

https://blog.csdn.net/rickyit/article/details/53895060

  1. 堆大小设置 JVM 中最大堆大小有三方面限制:相关操作系统的数据模型(32-bt还是64-bit)限制;系统的可用虚拟内存限制;系统的可用物理内存限制。32位系统下,一般限制在1.5G~2G;64为操作系统对内存无限制。我在Windows Server 2003 系统,3.5G物理内存,JDK5.0下测试,最大可设置为1478m。
    典型设置:
    • java **-Xmx3550m -Xms3550m -Xmn2g -Xss128k
      • Xmx3550m** :设置JVM最大可用内存为3550M。
        -Xms3550m :设置JVM促使内存为3550m。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。
        -Xmn2g :设置年轻代大小为2G。整个堆大小=年轻代大小 + 年老代大小 + 持久代大小 。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。
        -Xss128k :设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。更具应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。
    • java -Xmx3550m -Xms3550m -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxPermSize=16m -XX:MaxTenuringThreshold=0
      -XX:NewRatio=4
      :设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。设置为4,则年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5
      -XX:SurvivorRatio=4 :设置年轻代中Eden区与Survivor区的大小比值。设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代的1/6
      -XX:MaxPermSize=16m :设置持久代大小为16m。
      -XX:MaxTenuringThreshold=0 :设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代 。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间 ,增加在年轻代即被回收的概论。
  2. 回收器选择 JVM给了三种选择:串行收集器、并行收集器、并发收集器 ,但是串行收集器只适用于小数据量的情况,所以这里的选择主要针对并行收集器和并发收集器。默认情况下,JDK5.0以前都是使用串行收集器,如果想使用其他收集器需要在启动时加入相应参数。JDK5.0以后,JVM会根据当前系统配置 进行判断。
    1. 吞吐量优先 的并行收集器
      如上文所述,并行收集器主要以到达一定的吞吐量为目标,适用于科学技术和后台处理等。
      典型配置
      • java -Xmx3800m -Xms3800m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20
        -XX:+UseParallelGC
        :选择垃圾收集器为并行收集器。 此配置仅对年轻代有效。即上述配置下,年轻代使用并发收集,而年老代仍旧使用串行收集。
        -XX:ParallelGCThreads=20
        :配置并行收集器的线程数,即:同时多少个线程一起进行垃圾回收。此值最好配置与处理器数目相等。
      • java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20 -XX:+UseParallelOldGC
        -XX:+UseParallelOldGC
        :配置年老代垃圾收集方式为并行收集。JDK6.0支持对年老代并行收集。
      • java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:MaxGCPauseMillis=100
        -XX:MaxGCPauseMillis=100 :
        设置每次年轻代垃圾回收的最长时间,如果无法满足此时间,JVM会自动调整年轻代大小,以满足此值。
      • java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:MaxGCPauseMillis=100 -XX:+UseAdaptiveSizePolicy
        -XX:+UseAdaptiveSizePolicy
        :设置此选项后,并行收集器会自动选择年轻代区大小和相应的Survivor区比例,以达到目标系统规定的最低相应时间或者收集频率等,此值建议使用并行收集器时,一直打开。
    2. 响应时间优先 的并发收集器
      如上文所述,并发收集器主要是保证系统的响应时间,减少垃圾收集时的停顿时间。适用于应用服务器、电信领域等。
      典型配置
      • java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:ParallelGCThreads=20 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC
        -XX:+UseConcMarkSweepGC
        :设置年老代为并发收集。测试中配置这个以后,-XX:NewRatio=4的配置失效了,原因不明。所以,此时年轻代大小最好用-Xmn设置。
        -XX:+UseParNewGC :设置年轻代为并行收集。可与CMS收集同时使用。JDK5.0以上,JVM会根据系统配置自行设置,所以无需再设置此值。
      • java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseConcMarkSweepGC -XX:CMSFullGCsBeforeCompaction=5 -XX:+UseCMSCompactAtFullCollection
        -XX:CMSFullGCsBeforeCompaction
        :由于并发收集器不对内存空间进行压缩、整理,所以运行一段时间以后会产生“碎片”,使得运行效率降低。此值设置运行多少次GC以后对内存空间进行压缩、整理。
        -XX:+UseCMSCompactAtFullCollection :打开对年老代的压缩。可能会影响性能,但是可以消除碎片

堆栈溢出和内存泄露

  • 永久方法区溢出
    因此持久带溢出有可能是运行时常量池溢出,也有可能是方法区中保存的class对象没有被及时回收掉或者class信息占用的内存超过了我们配置。
    当持久带溢出的时候抛出java.lang.OutOfMemoryError: PermGen space
List list = new ArrayList();    
                while(true){    
                        list.add(UUID.randomUUID().toString().intern());    
                }    
        }

作者:GhostStories
链接:https://www.jianshu.com/p/cd705f88cf2a
  • 堆溢出
static class OOMError{} 
public static void main(String[] args) { 
List list = new ArrayList(); 
while (true) { list.add(new OOMError()); } 
}

如何解决发生在Java堆内存的OutOfMemoryError异常呢?

首先我们要分清楚产生OutOfMemoryError异常的原因是内存泄露还是内存溢出,如果内存中的对象确实都必须存活着而不像上面那样不断地创建对象实例却不使用该对象,则是内存溢出,而像上面代码中的情况则是内存泄露。

如果是内存泄露,我们可以通过一些内存查看工具来查看泄露对象到GC Roots的引用链,找到泄露对象是通过怎样的路径与GC Roots相关联并导致GC无法自动回收这些泄露对象,掌握了这些信息,我们就能比较准确地定位出泄露代码的位置。

如果不是内存泄露,也就是说内存中的对象确实都还必须存活,那么应该检查虚拟机的堆参数,看看是否还可以将机器物理内存调大,同时在代码上检查是否存在某些对象生命周期过长、持有状态时间过长的情况。

链接:http://www.imooc.com/article/18073

  • 线程栈溢出
    栈(JVM Stack)存放主要是栈帧( 局部变量表, 操作数栈 , 动态链接 , 方法出口信息 )的地方。注意区分栈和栈帧:栈里包含栈帧。
    与线程栈相关的内存异常有两个:
    a)、StackOverflowError(方法调用层次太深,内存不够新建栈帧)
    b)、OutOfMemoryError(线程太多,内存不够新建线程)
    public void stackOverFlowMethod(){
        stackOverFlowMethod();
    }

http://www.imooc.com/article/18073
https://blog.csdn.net/hu1991die/article/details/43052281

GC触发的时机

Minor GC = Young Gc
Major GC = Full Gc
https://blog.csdn.net/yhyr_ycy/article/details/52566105
https://blog.csdn.net/qq_33808060/article/details/60478794
要准确理解Java的垃圾回收机制,就要从:“什么时候”,“对什么东西”,“做了什么”三个方面来具体分析。

第一:“什么时候”即就是GC触发的条件。GC触发的条件有两种。(1)程序调用System.gc时可以触发;(2)系统自身来决定GC触发的时机。
系统判断GC触发的依据:根据Eden区和From Space区的内存大小来决定。当内存大小不足时,则会启动GC线程并停止应用线程。

第二:“对什么东西”笼统的认为是Java对象并没有错。但是准确来讲,GC操作的对象分为:通过可达性分析法无法搜索到的对象和可以搜索到的对象。对于搜索不到的方法进行标记。

第三:“做了什么”最浅显的理解为释放对象。但是从GC的底层机制可以看出,对于可以搜索到的对象进行复制操作,对于搜索不到的对象,调用finalize()方法进行释放。

具体过程:当GC线程启动时,会通过可达性分析法把Eden区和From Space区的存活对象复制到To Space区,然后把Eden Space和From Space区的对象释放掉。当GC轮训扫描To Space区一定次数后,把依然存活的对象复制到老年代,然后释放To Space区的对象。

GC将堆里的对象分为新生代,老年代,持久代。新生代有一个Eden区,两个survivor区。新生成的对象直接放在Eden区,Eden区满了就放进survivor1,当survivor1满了就会出发一次Minor GC:将存活的对象放入survivor2,然后清空Eden和survivor1,再将survivor区的交换,保证survivor2为空。当survivor2不足以存放Eden和survivor1的存活对象时,就会放入老年区。较大的对象和长期存活的对象直接进入老年区。当即将进入老年区的对象超过老年区剩余大小时,触发一次full GC。
频率上说,Minor GC较频繁,full GC不频繁。
持久代会存放一些静态值和方法。

Minor GC ,Full GC 触发条件
Minor GC触发条件:当Eden区满时,触发Minor GC。
Full GC触发条件:
(1)调用System.gc时,系统建议执行Full GC,但是不必然执行
(2)老年代空间不足
(3)方法去空间不足
(4)通过Minor GC后进入老年代的平均大小大于老年代的可用内存
(5)由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小

GC的收集方法的原理与特点,分别用在什么地方,如果让你优化收集方法,有什么思路?

  • 标记清理:首先标记所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象,它的标记的对象。缺点是效率低,且存在内存碎片。主要用于老生代垃圾回收。
  • 标记整理:首先标记所有需要回收的对象,在标记完成后让所有存活的对象都向一端移动,然后直接清理掉端边界意外的内存。用于老年代。
  • 复制算法:将内存按容量划分为大小相等的一块,每次只用其中一块。当内存用完了,将还存活的对象复制到另一块内存,然后把已使用过的内存空间一次清理掉。实现简单,高效。一般用于新生代。一般是将内存分为一块较大的Eden空间和两块较小的Survivor空间。HotSpot虚拟机默认比例是8:1,。每次使用Eden和一块Survivor,当回收时将这两块内存中还存活的对象复制到Survivor然后清理掉刚才Eden和Survivor的空间。如果复制过程内存不够使用则向老年代分配担保。
  • 分代收集算法:根据对象的生存周期将内存划分为新生代和老年代,根据年代的特点采用最适当的收集算法。

GC收集器有哪些?CMS收集器与G1收集器的特点。

  • Serial: 单线程收集器,只会使用一个CPU或一条收集器线程去完成,垃圾回收工作,更重要的是在进行垃圾回收时,必须暂停其他所有的工作线程。(Stop the world)。简单高效,用于新生代。
  • ParNew: 是Serial收集器的多线程版本,垃圾回收时采用多线程方式进行回收。默认情况下使用的线程数是cpu数量。除了serial收集器,目前只有它能和CMS收集器配合工作。是server模式下首选的新生代收集器。
  • Parallel Scavenge: 使用复制算法收集器,也是一个并行的多线程收集器。Parallel Scavenge收集器与其他收集器关注点不同,其它收集器主要关注缩短垃圾回收时用户线程的停顿时间。而它关心吞吐量,即运行用户代码时间/(运行用户代码时间 + 垃圾收集时间)。停顿时间越短越适合需要与用户交互的程序,高吞吐量则可以最高效率的利用CPU时间。
  • Serial Old: 老年代,单线程收集器,使用标记整理算法。主要有两个用途,一是和Parallel Scavenge 收集器配合使用,二是作为CMS的后备方案在并发收集器发生Concurrent Mode Failure时候使用。
  • Parallel Old:并行的老年代版本收集器,使用标记整理算法。主要与Parallel Scavenge配合使用。
  • CMS:是以获得最短回收停顿时间为目标的收集器,使用标记清除算法。整个过程包括4个:
  1. 初始标记: 标记Gc ROOTS能直接关联到的对象
  2. 并发标记:进行Roots Traceing的过程
  3. 重新标记:修正并发标记期间因用户继续工作导致标记产生变动
  4. 并发清除:并发清除数据。 初始标记和重新标记需要stop the world. 并发标记和并发清除过程用户线程和收集器线程可以并行执行。
  • G1(Garbage First): 基于标记-整理算法的收集器,不会产生空间碎片.它可以精确控制停顿,能够让使用者明确指定一个长度为M毫秒的时间片段内,消耗集上的时间不超过N秒.是不牺牲吞吐量的前提下完成低停顿的.G1将整个java堆(新生和老生)划分为大小相同的区,并跟踪这些区上发生的变化.在后台维护一个优先列表,每次根据允许的收集时间优先回收垃圾最多的区域.

现在公司中很多都采用了G1 垃圾回收期,建议大家多深入了解下G1,更多参考: G1垃圾回收器

GC的两种判定方法:引用计数与根搜索算法

  • 引用计数: 给对象添加一个引用计数器,每当有一个地方引用该对象时,计数器值加1,当引用失效时,计数器值减1,。任何时候计数器都为0的对象就是不可能再被使用的。它很难解决对象之间相互循环引用问题。
  • 根搜索算法(GC Roots Traceing): 通过一系列名为“GC Roots”的对象作为起点,从这些节点开始向下搜索,搜索走过的路径成为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明此对象不可用。

GC Roots对象一般是:

虚拟机栈中的引用对象,方法区中类静态属性引用的对象,方法区常量引用的对象等。

Java中的四种引用

Java中提供这四种引用类型主要有两个目的:第一是可以让程序员通过代码的方式决定某些对象的生命周期;第二是有利于JVM进行垃圾回收。

  • 强引用:程序代码中的普通引用。如Object obj = new Object(),只要强引用存在,垃圾回收器就不会回收。在不使用对象时应及时将引用设置为null,便于垃圾回收。
  • 软引用:描述一些有用但并非必须的对象。对于软引用关联的对象在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。SoftRefence
  • 弱引用:描述非必须对象,比软引用弱一些。被弱引用关联的对象只能生存到下一次垃圾收集发生之前。无论当前内存是否足够,都会回收掉只被弱引用关联的对象。WeakRefence
  • 虚引用:最弱的引用,不管是否有虚引用存在,完全不会对对象生存时间构成影响,也无法通过虚引用来取得一个对象实例。唯一目的是希望能够在这个对象被垃圾回收器之前收到系统通知。PhantomReference

相关参考:Java 如何有效地避免OOM:善于利用软引用和弱引用

OOM你遇到过哪些情况

  • java.lang.OutOfMemoryError: Java heap space ------>java堆内存溢出,此种情况最常见,一般由于内存泄露或者堆的大小设置不当引起。
  • java.lang.OutOfMemoryError: PermGen space ------>java永久代溢出,即方法区溢出了,一般出现于大量Class或者jsp页面,或者采用cglib等反射机制的情况,因为上述情况会产生大量的Class信息存储于方法区。
  • java.lang.StackOverflowError ------> 不会抛OOM error,但也是比较常见的Java内存溢出。JAVA虚拟机栈溢出,一般是由于程序中存在死循环或者深度递归调用造成的,栈大小设置太小也会出现此种溢出。可以通过虚拟机参数-Xss来设置栈的大小。

你可能感兴趣的:(JVM垃圾回收)