一、JVM垃圾回收的时候怎样确定垃圾
1、引用计数法
Java中,引用和对象是有关联的。如果要操作对象则必须引用进行。因此,简单的办法是通过引用计数来判断一个对象是否可以回收。简单的说,给对象中添加一个引用计数,每当有一个引用失效时,计数器值减1,任何时刻计数器值为0的对象就是不可能再被利用的,那么这个对象就是可回收对象。那么为什么主流的Java虚拟机里面都没有选择这种算法呢?主要的原因是它很难解决对象之间相互循环引用的问题。
2、可达性分析(根搜索路径)
为了解决引用计数法的循环引用问题,Java使用了可达性分析的方法。
所谓“GC roots”或者tracing GC的“根集合”就是一组必须活跃的引用。
基本思路就是通过一系列名为“GC Roots”的对象作为起点,从这个被称为GC Roots的对象开始向下搜索,如果一个对象到GC Roots没有任何引用链相连时,则说明此对象不可用。即给定一个集合的引用作为根出发,通过引用关系遍历对象图,能被遍历到的(可达性的)对象就被判定为存活,没有被遍历到的就被判断为死亡。
二、java中哪些可以作为GC Roots对象
1、虚拟机栈(栈帧中的局部变量区,也叫做局部变量表)中引用的对象。
2、方法区中的类静态属性引用的对象。
3、方法区中常量引用的对象。
4、本地方法栈中JNI(Native方法)引用的对象。
三、JVM的集中参数类型
1、标配参数
java -version
java -help
java -showversion
2、x参数(了解)
-Xint:解释执行
-Xcomp:第一次使用就编译成本地代码
-Xmixed:混合模式
3、xx参数
a、Boolean类型
-XX: 或者-某个属性值, 表示开启 -表示关闭。
例:是否打印GC收集细节,-XX: PrintGCDetails , -XX: PrintGCDetails
b、KV设值类型
-XX:属性key=属性值value
例:-XX:MetaspaceSize=128m
四、怎样查看JVM的各种参数
1、运行程序中通过进程id查看
使用 jps -l 命令查看运行的进程
然后使用 jinfo -flag 查看的参数 进程id
也可也直接使用 jinfo -flags 进程id
2、使用 java -XX: PrintFlagsInitial 查看出厂默认参数
3、java -XX: PrintFlagsFinal 查看修改更新 (= 没有修改过 := 人为修改过)
4、java -XX: PrintCommandLineFlags -version --打印命令行参数(可以看默认垃圾回收器)
五、JVM常用参数
1、-Xms
初始堆内存大小,默认为物理内存的1/64,等价于 -XX:InitialHeapSize
2、-Xmx
最大堆内存大小,默认为物理内存的1/4,等价于 -XX:MaxHeadSize
3、-Xss
设置单个线程栈的大小,默认为512k~1024k,等价于 -XX:ThreadStackSize
4、-XX:MetaSpaceSize
设置元空间大小,元空间不在虚拟机中,而是在本地内存,默认情况下仅受本地内存限制
5、-XX: PrintGCDetails
打印GC回收信息
六、引用类型
1、强引用
当内存不足,JVM开始垃圾回收,对于强引用的对象,就算是出现了OOM也不会对该对象进行回收,死都不收。
强引用是最常见的普通对象引用,只要还有前引用指向一个对象,就能表明对象还“活着”,垃圾收集器不会碰到这种对象。在Java中最常见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引用。当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾机制回收的,即使该对象以后永远都不能被用到,JVM也不会回收。因此强引用是造成Java内存泄露的主要原因之一。
对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显示地将相应(强)引用赋值为null,一般认为就是可以被垃圾收集的了(当然具体回收时还要看垃圾收集策略)。
2、软引用
软引用是一种相对强化了一些的引用,需要用java.lang.ref.SoftReference类来实现,可以让对象豁免一些垃圾收集。
当系统内存充足时,它不会被回收
当系统内存不足时,它会被回收
软引用通常用在对内存敏感的程序中,比如高速缓存就有用到软引用,内存够用的时候就保留,不够用就回收。
3、弱引用
弱引用需要用java.lang.ref.WeakReference类来实现,它比软引用的生存期更短。
对于软引用对象来说,只要垃圾回收机制一运行,不管JVM的内存空间是否足够,都会回收该对象占用的内存。
一般用在加载大量图片。
4、虚引用
虚引用需要java.lang.refPhantonReference类来实现。
顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。
如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收,它不能单独使用也不能通过它访问对象,虚引用必须和队列(ReferenceQueue)联合使用。
虚引用的主要作用是跟踪对象被垃圾回收的状态。仅仅是提供了一种确保对象被finalize以后,做某些事情的机制。
PhantomReference的get方法总是返回null,因此无法访问对应的引用对象。其意义在于说明一个对象那个已经进入finalization阶段,可以被gc回收,用来实现比finalization机制更灵活的回收操作。
换句话说,设置虚引用关联的唯一目的,就是在这个对象被收集器回收的时候收到一个系统通知或者后续添加进一步的处理。
Java技术允许使用finalize()方法在垃圾收集器将对象从内存中清楚之前做必要的清理工作。
七、OOM
1、java.lang.StackOverFlowError
当递归太深会报这种错误
2、java.lang.OutOfMemoryError:Java heap space
无限循环去 new 对象会出现
3、java.lang.OutOfMemoryError:GC overhead limit exceeded
GC回收时间长时会抛出OutOfMemoryError。过长的定义是,超过98%的时间用来做GC并且回收了不到2%的堆内存,连续多次GC都只回收了不到2%的极端情况下才会抛出。
假设不抛出GC overhead limit错误会发生什么情况呢?
那就是GC清理的这么点内存很快会再次填满,迫使GC再次执行,这样就形成恶性循环,CPU使用率一直是100%,而GC却没有任何成果。
4、java.lang.OutOfMemoryError:Direct buffer memory
导致原因:
写NIO程序经常使用ByteBuffer来读取或者写入数据,这是一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在java堆和Native堆中来回复制数据。
ByteBuffer.allocate(capability)第一种方式是分配JVM堆内存,属于GC管辖范围,由于需要拷贝所以速度相对较慢。
ByteBuffer.allocateDirect(capability)第一种方式是分配OS本地内存,不属于GC管辖范围,由于不需要内存拷贝,所以速度相对较快。
但如果不断分配内存,堆内存很少使用,那么JVM就不需要执行GC,DirectByteBuffer对象们就不会被回收,这时候堆内存充足,但本地内存可能已经使用光了,再次尝试分配本地内存就会出现OutOfMemoryError,那程序就直接崩溃了。
5、java.langOutOfMemoryError:Metaspace
使用java -XX: PrintFlagsInitial命令查看本机的初始化参数,-XX:Metaspacesize为218103768(大约为20.8M),元空间放不下会报这种错误。
6、java.lang.OutOfMemoryError:unable to create new native thread
高并发请求服务器时,经常出现如下异常:java.lang.OutOfMemoryError:unbale to create new native thread
准确的将该native thread异常与对应的平台有关。
导致原因:
- 应用创建了太多线程,一个应用进程创建多个线程,超过系统承载极限。
- 服务器并不允许应用程序创建那么多线程,linux系统默认允许单个进程可以创建的线程数是1024个,如果应用创建超过这个数量,就会报java.lang.OutOfMemoryError:unable to create new native thread
解决办法:
- 想办法降低应用程序创建线程的数量,分析应用是否真的需要创建那么多线程,如果不是,改代码将线程数降到最低。
- 对于有的应用,确实需要创建多个线程,远超过linux系统默认的1024个线程的限制,可以通过修改linux服务器配置,扩大linux默认限制。