在默认情况下,通过System.gc()或者Runtime.getRuntime().gc()的调用(其实System.gc()的底层实现方法就是Runtime.getRuntime().gc()),会显示触发Full GC,同时对老年代和新生代进行回收,尝试释放被丢弃对象占用的内存。
然而System.gc()调用附带一个免责声明,无法保证对垃圾收集器的调用。
JVM实现者可以通过System.gc()调用来决定JVM的GC行为。而一般情况下,垃圾回收应该是自动进行的,无须手动触发,否则就太过于麻烦了。在一些特殊情况下,如我们正在编写一个性能基准,我们可以在运行之间调用System.gc()。
举例说明
首先理解一下概念
内存溢出(OOM)
注意上一段蓝色字体隐藏的意思:在抛出OutOfMemoryError之前,通常垃圾收集器会被处罚,尽其所能去清理出空间。
内存泄漏(Memory Leak)
也称作“内存渗漏”。严格来说,只有对象不会再被程序用到了,但是GC又不能回收他们的情况,才叫内存泄漏。
举例内存泄漏的情况:
1.单例模式
单例的声明周期和应用程序是一样长的,所以单例程序中,如果持有对外部对象的引用的话,那么这个外部对象是不能被回收的,则会导致内存泄漏的产生。
2.一些提供close的资源未关闭导致的内存泄漏
数据库连接(dataSource.getConnection()),网络连接(socket)和IO连接必须手动close,负责是不能被回收的。
简称STW,指的是GC事件发生过程中,会产生应用程序的停顿。停顿产生时整个应用程序线程都会被暂停,没有任何响应,有点像卡死的感觉,这个停顿成为STW。
被STW中断的应用程序线程会在完成GC之后恢复,频繁中断会让用户感觉像是网速不快造成电影卡带一样,所以我们需要减少STW的发生。
STW事件和采用哪款GC无关,所有的GC都有这个事件。哪怕是G1也不能完全避免STW的情况发生,只能说垃圾回收器越来越优秀,回收效率越来越高,尽可能地缩短了暂停时间。
STW是JVM在后台自动发起和自动完成的,在用户不可见的情况下,把用户正常的工作线程全部停掉。
开发中不要用System.gc(),会导致STW的发生。
首先回忆一下程序中的并行与并发。
并行:指在同一时刻,有多条指令在多个处理器上同时执行。所以无论从微观还是从宏观来看,二者都是一起执行的。决定并行的因素不是CPU的数量,而是CPU的核心数量,比如一个CPU多个核也可以并行。
并发:指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干段,使多个进程快速交替的执行。
二者对比
并发,指的是多个事情,在同一时间段内同时发生了。
并行,指的是多个事情,在同一时间点上同时发生了。
并发的多个任务之间是互相抢占资源的。
并行的多个任务之间是不互相抢占资源的。
只有在多CPU或者CPU多核的情况下,才会发生并行。否则,看似同时发生的事情,其实都是并发执行的。
并发和并行,在谈论垃圾收集器的上下文语境中,它可以解释如下:
并行:指多条垃圾收集线程并行工作,但此时用户线程仍处于等待状态。
串行:相较于并行的概念,单线程执行。如果内存不够,则程序暂停,启动JVM垃圾回收器进行垃圾回收。回收完,再启动程序的线程。
并发:指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),垃圾回收线程在执行时不会停顿用户程序的运行。如CMS、G1
安全点
程序执行时并非所有地方都能停下来开始GC,只有在特定的位置才能停顿下来开始GC,这些位置成为“安全点”。
如何在GC发生时,检查所有线程都跑到最近的安全点停顿下来呢?
安全区域
安全区域是指在一段代码片段中,对象的引用关系不会发生变化,在这个区域中的任何位置开始GC都是安全的。我们也可以吧Safe Region看做是被扩展了的Safepoint。
实际执行时:
再谈引用
基本概念
在Java程序中,最常见的引用类型是强引用(普通系统99%以上都是强引用),也就是我们最常见的普通对象引用,也是默认的引用类型。
当在Java语言中使用new操作符创建一个新对象,并将其赋值给一个变量的时候,这个变量就成为指向该对象的一个强引用。
强引用的对象是可触及的,垃圾回收器就永远不会回收掉被引用的对象。
对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显式地将相应引用复制为null,就是可以当做垃圾被收集了,当然具体回收时机还是要看垃圾收集策略。
相对的,软引用,弱引用和虚引用是软可触及、弱可触及和虚可触及的,在一定条件下都是可以被回收的。所以,强引用是造成Java内存泄漏的主要原因之一。
软引用是用来描述一些还有用,但非必须的对象。只被软引用引用着的对象,在系统将于发生内存溢出异常之前,会把这些对象列进回收范围之中进行第二次回收,如果这次回收还没有足够的内存,才会抛出内存溢出异常。
在JDK1.2版之后提供java.lang.ref.SoftReference类来实现软引用。
弱引用也是用来描述那些非必须对象,被弱引用关联的对象只能生存到下一次垃圾收集发生为止。在系统GC时,只要发现弱引用,不管系统堆空间使用是否充足,都会回收掉只被弱引用关联的对象。
但是由于垃圾回收器的线程通常优先级很低,因此并不一定能很快地发现持有弱引用的对象,在这种情况下,弱引用对象可以存在较长时间。
弱引用对象与软引用对象最大的不同就在于,当GC在进行回收时,需要通过算法检查是否回收软引用对象,而对于弱引用对象,GC总是进行回收。弱引用对象更容易、更快被回收。
面试题:你开发中使用过WakHashMap吗?底层是使用了WeakReference。
为一个对象设置虚引用关联的唯一目的在于跟踪垃圾回收过程。比如:能在这个对象被收集器回收时收到一个系统通知。
虚引用必须和引用队列一起使用。虚引用在创建时必须提供一个引用队列作为参数。大概垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象后,将这个虚引用加入引用队列,以通知应用程序对象的回收情况。
由于虚引用可以跟着对象的回收时间,因此,可以将一些资源释放操作放置在虚引用中执行和记录。