部分内容来自以下博客:

https://blog.csdn.net/baidu_22254181/article/details/82555485

https://blog.csdn.net/know9163/article/details/80574488

1 垃圾回收机制

1.1 是什么

垃圾回收机制是由垃圾收集器GC(Garbage Collection)来实现的,GC是后台的守护进程。

GC的特别之处是它是一个低优先级进程,但是可以根据内存的使用情况动态的调整他的优先级。因此,它是在内存中低到一定限度时才会自动运行,从而实现对内存的回收。这就是垃圾回收的时间不确定的原因。

1.2 发生位置

JVM的内存结构包括五大区域:程序计数器、虚拟机栈、本地方法栈、堆区、方法区。

其中程序计数器、虚拟机栈、本地方法栈三个区域随线程而生、随线程而灭,因此这几个区域的内存分配和回收都具备确定性,就不需要过多考虑回收的问题,因为方法结束或者线程结束时,内存自然就跟随着回收了。

而堆区和方法区则不一样,这部分内存的分配和回收是动态的,正是垃圾收集器所需关注的部分。

1.3 内存泄漏

内存泄漏指的是内存中没有空闲空间,并且垃圾收集器也无法提供更多的内存。

产生内存泄漏的原因,可能是虚拟机的堆内存设置的大小不够,也可能是代码中创建了大量的大对象,并且长时间不能被垃圾收集器收集。

内存泄漏的八种情况:

1)单例模式,单例模式中的对象生命周期和应用程序是一样长的,如果单例程序中持有外部对象的引用,这个外部引用就是不可以被回收的,就有可能导致内存泄漏。

2)资源未被关闭,数据库连接和网络连接以及输入输出流都需要手动关闭,否则是不能被回收的。

3)静态集合类,如果这些容器为静态的,那么它们的生命周期与应用程序一致,容器中的对象在程序结束之前将不能被释放,从而造成内存泄漏。

4)内部类持有外部类,如果一个外部类的实例对象的方法返回了一个内部类的实例对象,这个内部类对象被长期引用了,即使那个外部类实例对象不再被使用,但由于内部类持有外部类的实例对象,这个外部类对象将不会被垃圾回收,这也会造成内存泄露。

5)改变哈希值,当对象被存入HashSet中后,就不能再修改这个对象的哈希值了,否则就会导致无法从HashSet中检索到该对象,导致内存泄漏。

6)不合理的作用域,变量定义的作用范围大于其使用范围,可能会导致内存泄漏,另外,如果没有及时将对象置空,也可能导致内存泄漏。

7)缓存泄漏,一旦将对象放入到缓存中,就会很容易遗忘缓存对象,造成内存泄漏。

8)监听器和回调,如果客户端在接口中注册回调,但没有显示取消,就可能会导致内存泄漏。

1.4 STW(Stop The World)

STW,全称是Stop the World,指的是GC事件发生过程中,会产生应用程序的停顿。停顿产生时整个应用程序线程都会被暂停,没有任何响应,有点像卡死的感觉,这个停顿称为STW。

产生STW的几种情况:

1)可达性算法中枚举根节点会导致停顿。

2)调用System.gc()方法会导致Full GC,从而导致停顿。

3)调用finalize()方法会导致停顿。

1.5 安全点和安全区域

1.5.1 安全点(Safe Point)

从线程角度看,安全点可以理解成是在代码执行过程中的一些特殊位置,在线程执行到这些位置时,说明虚拟机当前的状态是安全的。

安全点的选择很重要,太少会导致等待进入安全点的时间过长,太多会导致性能问题,可以将执行时间较长的程序作为安全点,比如方法调用,循环跳转,异常跳转等。

对于一些需要暂停的操作,比如STW,需要等线程进入安全点才能执行,线程进入安全点的方式有两种:

1)抢先式中断:首先中断所有线程,如果还有线程不在安全点,就恢复线程,让线程跑到安全点。过时,目前没有虚拟机采用。

2)主动式中断:设置一个中断标志,各个线程运行到安全点的时候主动轮询这个标志,如果中断标志为真,则将自己进行中断挂起。

1.5.2 安全区域(Safe Region)

当需要暂停线程时,如果线程正在执行可以等待进入安全点,但如果线程处于休眠状态或者阻塞状态,等待时间就会变得很长。

为了解决这个问题,引入了安全区域的概念。

安全区域是指在一段代码片中,引用关系不会发生改变,在这个区域中的任何位置开始GC都是安全的,可以看做是安全点的扩展。

当线程进入安全区域时,会标识已经进入安全区域,此时发生GC会忽略进入安全区域的线程。

当线程离开安全区域时,会检查是否完成GC,只有完成GC线程才可以离开,否则需要等待GC完成才可以离开。

2 对象存活判断

2.1 堆的存活判断

判断对象是否存活一般有两种方式:引用计数算法和可达性算法。

2.1.1 引用计数算法(Reference Counting)

每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收。

此方法简单,但无法解决对象相互循环引用的问题,如图:

public class DemoTest {public static void main(String[] args) {DemoGC demoA = new DemoGC();// step 1DemoGC demoB = new DemoGC();// step 2// 相互引用demoA.instance = demoB;// step 3demoB.instance = demoA;// step 4// 释放对象demoA = null;// step 5demoB = null;// step 6// 发生CGSystem.gc();}
}class DemoGC {public Object instance = null;
}

在第一步和第二步执行后,在堆中创建了两个实例对象:

demoA引用实例对象A(引用变为1),demoB引用实例对象B(引用变为1)。

在第三步和第四步执行后:

demoB的instance属性引用实例对象A(引用变为2),demoA的instance属性引用实例对象B(引用变为2)。

在第五步和第六步执行后:

demoA不再引用实例对象A(引用变为1),demoB不再引用实例对象B(引用变为1)。

此时如果发生GC:

虽然demoA和demoB均已经不再引用实例对象了,但是其内部的instance属性还在引用实例对象,所以此时实例对象的引用不为0,不能被GC回收。

2.1.2 可达性算法(Reachability Analysis)

从GC Roots开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,可以回收,但不一定会被回收,原因在于虚拟机的二次标记机制。

可以作为GC Roots的对象:

1)虚拟机栈的栈帧中的局部变量表引用的对象,比如各个线程中被调用的参数和局部变量等。

2)本地方法栈中JNI(Native方法)引用的对象,比如线程中的start()方法中使用的对象。

3)静态属性引用的对象,比如引用类型的静态变量。

4)方法区中常量引用的对象,比如在方法区中使用字符串常量池中的对象。

5)被synchronized所持有的对象。

6)虚拟机内部的引用,比如基本类型对应的Class对象,常驻异常对象,系统类加载器等。

7)本地代码缓存。

8)除了固定的对象外,根据用户选用的垃圾回收器和当前回收的内存区域,还可以有临时对象加入,比如分代收集和局部收集。

再回到相互循环引用的问题上:

demoA和demoB是方法中的局部变量,其存储位置是虚拟机栈的栈帧中的局部变量表,可以作为GC Roots对象。

instance属性是类中的成员属性,其存储位置是堆,不可以作为GC Roots对象。

所以,当demoA和demoB不再引用实例对象后,从GC Roots向下搜索,会发现实例对象没有引用链相连,可以被GC回收。

2.1.3 二次标记

Object类有一个finalize()方法,该方法会在该对象被回收之前调用,并且任何一个对象的fianlize()方法都只会被系统自动调用一次。

在被标记后,如果重写了finalize()方法,并且在方法里将该对象重新加入到了引用链中,此时,虽然已经被标记了,但并不会被回收。

原因在于虚拟机的二次标记机制:

1)第一次标记,标记不在引用链的对象,判断是否需要执行finalize()方法。如果已经被执行或者没有被重写,就表示不需要执行,否则表示需要执行。

2)将需要执行finalize()方法的对象放在F-Queue的队列中,由一个虚拟机自动创建的、低优先级的Finalizer线程去执行。但虚拟机并不承诺会等待fianlize()方法执行完,也就是说,即便重写了finalize()方法也不一定会被执行。

3)第二次标记,遍历F-Queue队列中的对象,判断是否存在引用链。如果存在引用链,表示该对象不需要被回收,否则标记不存在引用链的对象,等待回收。

测试代码:

public class DemoTest {public static void main(String[] args) throws Exception {DemoGC.instance = new DemoGC();DemoGC.demo();DemoGC.demo();}
}class DemoGC {// 静态变量保持引用public static DemoGC instance = null;public static void demo() throws Exception {// 移除引用instance = null;// 通知GCSystem.gc();// 等待GCThread.sleep(1000);// 判断是否可用if (instance == null) {System.out.println("被移除了");} else {System.out.println("还可以用");}}@Overrideprotected void finalize() throws Throwable {super.finalize();instance = this;System.out.println("被捞回了");}
}

结果如下:

被捞回了
还可以用
被移除了

2.2 方法区的存活判断

方法区主要回收的内容有:废弃常量和无用的类。对于废弃常量也可通过引用的可达性来判断,但是对于无用的类则需要同时满足下面三个条件:

1)该类所有的实例都已经被回收,也就是堆中不存在该类的任何实例。

2)加载该类的ClassLoader已经被回收。

3)该类对应的Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

3 对象的引用

3.1 说明

在JDK1.2以前的版本中,若一个对象不被任何变量引用,那么程序就无法再使用这个对象。也就是说,只有对象处于可达状态,程序才能使用它。

从JDK1.2版本开始,对象的引用被划分为四种级别,从而使程序能更加灵活地控制对象的生命周期。这四种级别由高到低依次为:强引用、软引用、弱引用和虚引用。

java.lang.ref包中提供了几个类:SoftReference类、WeakReference类和PhantomReference类,它们分别代表软引用、弱引用和虚引用。

无论引用计数算法还是可达性分析算法都是基于强引用而言的。

3.2 强引用(StrongReference)

3.2.1 回收机制

强引用是使用最普遍的引用,如果一个对象具有强引用,那垃圾回收器绝不会回收它。

当内存空间不足时,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。

3.2.2 使用举例

获取强引用:

Object strongReference = new Object();

3.3 软引用(SoftReference)

3.3.1 回收机制

如果一个对象只具有软引用,只有在内存空间不足时才会回收这些对象的内存,如果内存充足垃圾回收器就不会回收它。只要垃圾回收器没有回收它,该对象就可以被程序使用。

软引用可以和一个引用队列(ReferenceQueue)联合使用。如果软引用所引用对象被垃圾回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。

可以通过软引用的get()方法获取实例对象,也可以调用引用队列的poll()方法来检测是否有它所关心的对象被回收。如果队列为空,将返回一个null,否则该方法返回队列中前面的那个引用对象(Reference)。

3.3.2 使用举例

获取软引用:

String str = new String("abc");// 强引用
SoftReference<String> softReference = new  SoftReference<String>(str);// 软引用

软引用可以和引用队列联合使用:

String str = new String("abc");
ReferenceQueue<String> referenceQueue = new ReferenceQueue<>();
SoftReference<String> softReference = new SoftReference<>(str, referenceQueue);
str = null;
// Notify GC
System.gc();System.out.println(softReference.get()); // abcReference<? extends String> reference = referenceQueue.poll();
System.out.println(reference); // null

软引用可用来实现内存敏感的高速缓存,比如浏览器的后退按钮:

Browser browser = new Browser();
SoftReference<BrowserPage> softReference;// 首次浏览页面
public BrowserPage loadPage() {// 从后台程序加载浏览页面BrowserPage page = browser.getPage();// 构建软引用softReference = new SoftReference<BrowserPage>(page);// 返回浏览页面return page;
}// 回退或者再次浏览页面
public BrowserPage backPage() {BrowserPage page = null;// 判断软引用的对象是否被回收if ((page = softReference.get()) == null) {// 重新从后台程序加载浏览页面page = browser.getPage();// 重新构建软引用softReference = new SoftReference<BrowserPage>(page);}// 返回浏览页面return page;
}

3.4 弱引用(WeakReference)

3.4.1 回收机制

只具有弱引用的对象拥有更短暂的生命周期,在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。

同样,弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。

3.4.2 使用举例

获取弱引用:

String str = new String("abc");// 强引用
WeakReference<String> weakReference = new WeakReference<>(str);// 弱引用

3.5 虚引用(PhantomReference)

3.5.1 回收机制

与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它在任何时候都可能被垃圾回收器回收。

虚引用必须和引用队列(ReferenceQueue)联合使用,当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。

3.5.2 使用举例

获取虚引用:

String str = new String("abc");
ReferenceQueue<String> referenceQueue = new  ReferenceQueue<String>();
PhantomReference<String> phantomReference = new  PhantomReference<String>(str, referenceQueue);

虚引用主要用来跟踪对象被垃圾回收器回收的活动。

4 垃圾回收算法

4.1 标记-清除(Mark-Sweep)算法

4.1.1 原理

标记-清除算法分为两个阶段:标记阶段和清除阶段。标记阶段的任务是标记出所有需要被回收的对象,清除阶段就是回收被标记的对象所占用的空间。

4.1.2 解释

回收前:

回收后:

4.1.3 缺点

一个是效率问题,标记和清除过程的效率都不高。

另一个是空间问题,标记清除之后会产生大量不连续的内存碎片,当程序需要分配较大对象时,无法找到足够的连续内存,不得不提前触发另一次垃圾收集动作。

4.2 复制(Copying)算法

4.2.1 原理

为了解决Mark-Sweep算法的缺陷,Copying算法就被提了出来。

它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用的内存空间一次清理掉,这样一来就不容易出现内存碎片的问题。

很显然,Copying算法的效率跟存活对象的数目多少有很大的关系,如果存活对象很多,那么Copying算法的效率将会大大降低。

4.2.2 解释

回收前:

回收后:

4.2.3 缺点

这种算法虽然实现简单,运行高效且不容易产生内存碎片,但是却对内存空间的使用做出了高昂的代价,因为能够使用的内存缩减到原来的一半。

4.3 标记-整理(Mark-Compact)算法

4.3.1 原理

为了解决Copying算法的缺陷,充分利用内存空间,提出了Mark-Compact算法。

该算法标记阶段和Mark-Sweep一样,但是在完成标记之后,它不是直接清理可回收对象,而是将存活对象都向一端移动,然后清理掉端边界以外的内存。

4.3.2 解释

回收前:

回收后:

4.3.3 缺点

效率低,并且在移动过程中,需要全面暂停应用程序,即会触发STW(Stop The World)。

4.4 分代收集(Generational Collection)算法

4.4.1 原理

分代收集算法是目前大部分JVM的垃圾收集器采用的算法。它的核心思想是根据对象存活的生命周期将内存划分为若干个不同的区域。

一般情况下将堆区划分为新生代(Young Generation)和老年代(Tenured Generation),有的虚拟机将方法区看做是永久代(Permanet Generation)。

年老年代的特点是每次垃圾收集时只有少量对象需要被回收,新生代的特点是每次垃圾回收时都有大量的对象需要被回收,永久代的回收主要回收废弃常量和无用的类,那么就可以根据不同代的特点采取最适合的收集算法。

4.4.2 解释

年轻代由Minor GC进行回收,采用复制算法。Major GC主要回收老年代区域,采用标记清除算法。Full GC回收整个堆。

5 分代收集细说

5.1 新生代

在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,所以选用复制算法。

5.1.1 内存分配

因为大部分新生成的对象的生命周期都很短,所以将新生代分为一块较大的Eden区和两块较小的Survivor区。一块较大的Eden区用来存放新生成的对象,两块较小的Survivor区用来存放在多次GC存活下来的对象,一块称为S0区,另一块称为S1区。

5.1.2 触发时机

新生代发生的GC也叫做Minor GC,Minor GC发生频率比较高:

当Eden区满了一定会触发GC。

在Major GC的时候会先触发Minor GC。

其他情况。

5.1.3 GC机制

当第一次发生GC时,先将垃圾对象清除,然后将Eden区还存活的对象一次性复制到任意一个Survivor区,最后清空Eden区。为了区分方便,将使用的Survivor区称为From区,将空闲的Survivor区称为To区。

当再次发生GC时,先将垃圾对象清除,然后将Eden区和From区还存活的对象一次性复制到To区,最后清空Eden区和From区。每次GC完成之后,将正在使用的To区称为From区,将空闲的From区称为To区。

对象在放到Survivor区时都会设置一个年龄,并且每经过一次GC后都会将年龄加一,当对象的年龄超过虚拟机设置的阈值之后,会将对象放到老年代。

5.2 老年代

因为老年代中对象存活率高、没有额外空间对它进行分配担保,所以使用标记清除算法或标记整理算法来进行回收。

5.2.1 进入老年代的途径

大对象直接进入老年代。

经过多次Minor GC后仍在Survivor区的对象。

动态年龄判断,计算某个年龄的对象,大于Survivor区的一半,大于或等于这个年龄的对象进入老年代。

空间分配担保,经过Minor GC后Survivor区不足以存放对象,进入老年代。

5.2.2 触发时机

老年代发生的GC也叫做Major GC,Major GC发生频率比较低,当老年代内存满时触发。

6 垃圾收集器

6.1 Serial

新生代垃圾收集器,串行运行,采用复制算法,响应速度优先。

对于单个CPU环境而言,Serial收集器由于没有线程交互开销,可以获取最高的单线程收集效率。但需要STW(Stop The World),停顿时间长。

Serial收集器是Client模式下默认的垃圾收集器,可以通过-XX:+UseSerialGC来强制指定。

6.2 SerialOld

老年代垃圾收集器,串行运行,采用标记-整理算法,响应速度优先,是Serial收集器的老年代版本。

6.3 ParNew

新生代垃圾收集器,并行运行,采用复制算法,响应速度优先,是Serial收集器的多线程版本,在多核CPU环境下有着比Serial更好的表现。

除了Serial收集器外,目前只有它能与CMS收集器配合工作。

ParNew收集器是Server模式下首选的垃圾收集器,可以使用-XX:ParallelGCThreads来限制垃圾收集的线程数。

6.4 Parallel

新生代垃圾收集器,并行运行,采用复制算法,吞吐量优先。

追求高吞吐量,高效利用CPU,主要是为了达到一个可控的吞吐量。

Parallel收集器是Server模式下默认的垃圾收集器,可以通过-XX:+UseParallelGC来强制指定,可以使用-XX:ParallelGCThreads来限制垃圾收集的线程数。

6.5 ParallelOld

老年代垃圾收集器,并行运行,采用标记-整理算法,吞吐量优先。

6.6 CMS(Current Mark Sweep)

JDK1.5推出,JDK1.9废弃,JDK1.14移除。

老年代垃圾收集器,并发运行,采用标记-清除算法,响应速度优先。

以获取最短回收停顿时间为目标,高并发、低停顿,CPU占用比较高,响应时间快,停顿时间短。

收集过程分为如下四步:

1)初始标记,标记GCRoots能直接关联到的对象,有STW现象,暂停时间非常短。

2)并发标记,进行可达性分析过程,时间很长,不需要暂停用户线程,可与其他垃圾收集线程并发运行。在这个阶段使用了三色标记。

3)重新标记,修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,时间较长,不需要暂停用户线程。

4)并发清除,回收内存空间,时间很长,不需要暂停用户线程。

其中,并发标记与并发清除两个阶段耗时最长,但是可以与用户线程并发执行。

CMS优点:

并发收集,低延迟。

CMS缺点:

产生内存碎片,对CPU资源非常敏感,无法处理浮动垃圾。

6.7 G1(Garbage First)

JDK1.7推出,JDK1.9默认。

新生代和老年代垃圾收集器,并发、并行运行,采用复制算法和标记-整理算法,响应速度优先,同时注重吞吐量。

G1的目标是在延迟可控的情况下获得尽可能高的吞吐量。

使用G1收集器时,将整个堆划分为多个大小相等的独立区域(Region),每个Region都按照分代收集算法代表一种分区,分区有伊甸园区,幸存者0区,幸存者1区,老年代等分类。

G1收集器有以下特点:

1)并行和并发。使用多个CPU来缩短STW停顿时间,与用户线程并发执行。

2)分代收集。独立管理整个堆,但是能够采用不同的方式处理新对象和旧对象。

3)空间整合。Region之间是复制算法,整体上可以看作是标记-整理算法,这两种算法都能避免产生内存碎片。

4)可预测的停顿。能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒。

7 GC调优

7.1 查看日志

输出GC日志:

-XX:+PrintGC

输出GC的详细日志:

-XX:+PrintGCDetails

输出GC的时间戳(以基准时间的形式):

-XX:+PrintGCTimeStamps

输出GC的时间戳(以日期的形式):

-XX:+PrintGCDateStamps

在进行GC的前后打印出堆的信息:

-XX:+PrintHeapAtGC

设置日志文件的输出路径:

-Xloggc:../logs/gc.log

7.2 代大小优化

7.2.1 -Xms、-Xmx

-Xms设置堆内存初始内存大小,默认为物理内存的1/64。

-Xmx设置堆内存的最大内存,默认为物理内存的1/4。

这两个参数通常设置为相同的值,在清理完堆后就不需要重新分隔计算堆的大小,从而提升了性能。

7.2.2 -Xmn、-XX:SurvivorRatio、-XX:NewRatio

-Xmn决定了新生代空间的大小。

-XX:SurvivorRatio用来控制新生代Eden、S0、S1三个区域的比率,假如值为4则表示:Eden:S0:S1=4:3:3。

-XX:NewRatio用来控制新生代和老年代的比率,假如为2则表示:老年代:新生代=2:1。

7.2.3 -XX:MaxTenuringThreshold

-XX:MaxTenuringThreshold控制对象在经过多少次Minor GC之后进入老年代,此参数只有在Serial串行GC时有效。

7.2.4 -XX:PermSize、-XX:MaxPermSize

-XX:PermSize、-XX:MaxPermSize用来控制方法区的大小,通常设置为相同的值。

在JDK1.8之后,废弃了永久代(也就是方法区),如果同时使用这两个设置,会产生警告。

8 通过程序理解JVM

8.1 查看堆内存分配

-Xms设置堆内存初始内存大小,默认为物理内存的1/64。-Xmx设置堆内存的最大内存,默认为物理内存的1/4。

代码如下:

public static void main(String[] args) {// 机器内存大小的4分之1System.out.println("max memory: " + (Runtime.getRuntime().maxMemory() / 1024 / 1024) + "MB");// 机器内存大小的64分之1System.out.println("total memory: " + (Runtime.getRuntime().totalMemory() / 1024 / 1024) + "MB");
}

结果如下:

max memory: 3620MB
total memory: 245MB

结果说明:

本机电脑是16G,去掉一些自身的占用后,堆内存的最大值约为机器内存的1/4,堆内存的初始值为机器内存的1/64。

8.2 修改堆内存分配

在Eclipse中修改堆内存分配:

然后点击“Apply”,运行程序:

public static void main(String[] args) {// 机器内存大小的4分之1System.out.println("max memory: " +  (Runtime.getRuntime().maxMemory() / 1024 / 1024) + "MB");// 机器内存大小的64分之1System.out.println("total memory: " +  (Runtime.getRuntime().totalMemory() / 1024 / 1024) +  "MB");
}

发现结果变为:

max memory: 18MB
total memory: 5MB
Heap
PSYoungGen      total 1536K, used 838K [0x00000000ff980000, 0x00000000ffb80000, 0x0000000100000000)eden space 1024K, 81% used [0x00000000ff980000,0x00000000ffa51ae0,0x00000000ffa80000)from space 512K, 0% used [0x00000000ffb00000,0x00000000ffb00000,0x00000000ffb80000)to   space 512K, 0% used [0x00000000ffa80000,0x00000000ffa80000,0x00000000ffb00000)
ParOldGen       total 4096K, used 0K [0x00000000fec00000, 0x00000000ff000000, 0x00000000ff980000)object space 4096K, 0% used [0x00000000fec00000,0x00000000fec00000,0x00000000ff000000)
Metaspace       used 2699K, capacity 4486K, committed 4864K, reserved 1056768Kclass space    used 289K, capacity 386K, committed 512K, reserved 1048576K

说明堆内存已经被修改了。

8.3 占用内存后再次查看内存分配

修改代码如下:

public static void main(String[] args) {// 查看GC信息System.out.println("初始内存");System.out.println("max memory: " + Runtime.getRuntime().maxMemory());System.out.println("free memory: " + Runtime.getRuntime().freeMemory());System.out.println("total memory: " + Runtime.getRuntime().totalMemory());byte[] b1 = new byte[1 * 1024 * 1024];System.out.println("分配了1M");System.out.println("max memory: " + Runtime.getRuntime().maxMemory());System.out.println("free memory: " + Runtime.getRuntime().freeMemory());System.out.println("total memory: " + Runtime.getRuntime().totalMemory());
}

结果如下:

初始内存
max memory: 18874368
free memory: 4929688
total memory: 5767168
分配了1M
max memory: 18874368
free memory: 3881096
total memory: 5767168
Heap
PSYoungGen      total 1536K, used 838K  [0x00000000ff980000, 0x00000000ffb80000,  0x0000000100000000)eden space 1024K, 81% used  [0x00000000ff980000,0x00000000ffa51ac8,0x00000000ffa80000)from space 512K, 0% used  [0x00000000ffb00000,0x00000000ffb00000,0x00000000ffb80000)to   space 512K, 0% used  [0x00000000ffa80000,0x00000000ffa80000,0x00000000ffb00000)
ParOldGen       total 4096K, used 1024K  [0x00000000fec00000, 0x00000000ff000000,  0x00000000ff980000)object space 4096K, 25% used  [0x00000000fec00000,0x00000000fed00010,0x00000000ff000000)
Metaspace       used 2699K, capacity 4486K, committed  4864K, reserved 1056768Kclass space    used 289K, capacity 386K, committed 512K,  reserved 1048576K

结果说明:

初始时,最大内存约为20M,空闲内存约为5M,初始内存约为6M。

在分配了1M的内存后,最大内存约为20M,空闲内存约为4M,初始内存约为6M。

再次修改代码如下:

public static void main(String[] args) {// 查看GC信息System.out.println("初始内存");System.out.println("max memory: " +  Runtime.getRuntime().maxMemory());System.out.println("free memory: " +  Runtime.getRuntime().freeMemory());System.out.println("total memory: " +  Runtime.getRuntime().totalMemory());byte[] b1 = new byte[1 * 1024 * 1024];System.out.println("分配了1M");System.out.println("max memory: " +  Runtime.getRuntime().maxMemory());System.out.println("free memory: " +  Runtime.getRuntime().freeMemory());System.out.println("total memory: " +  Runtime.getRuntime().totalMemory());byte[] b2 = new byte[6 * 1024 * 1024];System.out.println("分配了6M");System.out.println("max memory: " +  Runtime.getRuntime().maxMemory());System.out.println("free memory: " +  Runtime.getRuntime().freeMemory());System.out.println("total memory: " +  Runtime.getRuntime().totalMemory());
}

结果如下:

初始内存
max memory: 18874368
free memory: 4929672
total memory: 5767168
分配了1M
max memory: 18874368
free memory: 3881080
total memory: 5767168
分配了6M
max memory: 18874368
free memory: 4405352
total memory: 12582912
Heap
PSYoungGen      total 1536K, used 838K  [0x00000000ff980000, 0x00000000ffb80000,  0x0000000100000000)eden space 1024K, 81% used  [0x00000000ff980000,0x00000000ffa51ad8,0x00000000ffa80000)from space 512K, 0% used  [0x00000000ffb00000,0x00000000ffb00000,0x00000000ffb80000)to   space 512K, 0% used  [0x00000000ffa80000,0x00000000ffa80000,0x00000000ffb00000)
ParOldGen       total 10752K, used 7168K  [0x00000000fec00000, 0x00000000ff680000,  0x00000000ff980000)object space 10752K, 66% used  [0x00000000fec00000,0x00000000ff300020,0x00000000ff680000)
Metaspace       used 2699K, capacity 4486K, committed  4864K, reserved 1056768Kclass space    used 289K, capacity 386K, committed 512K,  reserved 1048576K

结果说明:

再次分配6M之后,原空闲内存大小不能满足,所以重新分配了空间。

8.4 OOM异常

修改JVM参数:

-Xms8m -Xmx8m -XX:+PrintGCDetails

运行代码:

public static void main(String[] args) {// 查看GC信息System.out.println("初始内存");System.out.println("max memory: " +  Runtime.getRuntime().maxMemory());System.out.println("free memory: " +  Runtime.getRuntime().freeMemory());System.out.println("total memory: " +  Runtime.getRuntime().totalMemory());byte[] b1 = new byte[3 * 1024 * 1024];System.out.println("分配了1M");System.out.println("max memory: " +  Runtime.getRuntime().maxMemory());System.out.println("free memory: " +  Runtime.getRuntime().freeMemory());System.out.println("total memory: " +  Runtime.getRuntime().totalMemory());byte[] b2 = new byte[6 * 1024 * 1024];System.out.println("分配了6M");System.out.println("max memory: " +  Runtime.getRuntime().maxMemory());System.out.println("free memory: " +  Runtime.getRuntime().freeMemory());System.out.println("total memory: " +  Runtime.getRuntime().totalMemory());
}

结果如下:

初始内存
max memory: 7864320
free memory: 6998920
total memory: 7864320
分配了1M
max memory: 7864320
free memory: 3853176
total memory: 7864320
[GC (Allocation Failure) [PSYoungGen: ...] 3917K->3704K(7680K), 0.0004714 secs] ...
[GC (Allocation Failure) [PSYoungGen: ...] 3704K->3712K(7680K), 0.0003756 secs] ...
[Full GC (Allocation Failure) [PSYoungGen: ...] [ParOldGen: ...] 3712K->3614K(7680K), [Metaspace: ...], 0.0042747 secs] ...
[GC (Allocation Failure) [PSYoungGen: ...] 3614K->3614K(7680K), 0.0001818 secs] ...
[Full GC (Allocation Failure) [PSYoungGen: ...] [ParOldGen: ...] 3614K->3602K(7680K), [Metaspace: ...], 0.0033216 secs] ...
Exception in thread "main" java.lang.OutOfMemoryError: Java heap spaceat com.demo.test.DemoTest.main(DemoTest.java:18)
Heap
PSYoungGen      total 2048K, used 46K [0x00000000ffd80000, 0x0000000100000000, 0x0000000100000000)eden space 1536K, 3% used [0x00000000ffd80000,0x00000000ffd8b9e0,0x00000000fff00000)from space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)to   space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
ParOldGen       total 5632K, used 3602K [0x00000000ff800000, 0x00000000ffd80000, 0x00000000ffd80000)object space 5632K, 63% used [0x00000000ff800000,0x00000000ffb84878,0x00000000ffd80000)
Metaspace       used 2724K, capacity 4486K, committed 4864K, reserved 1056768Kclass space    used 292K, capacity 386K, committed 512K, reserved 1048576K

结果说明:

当新生代Eden区内存不足时,触发Minor GC回收新生代,当老年代内存不足时,触发Major GC,当GC后内存仍不足时,触发OOM异常。

8.5 查看内存分配占比

修改JVM参数:

-Xms20m -Xmx20m -Xmn1m -XX:SurvivorRatio=8 -XX:+PrintGCDetails -XX:+UseSerialGC

代码如下:

public static void main(String[] args) {byte[] b = null;// 连续向系统申请10MB空间for (int i = 0; i < 10; i++) {b = new byte[1 * 1024 * 1024];}
}

结果如下:

Heap
def new generation   total 960K, used 805K  [0x00000000fec00000, 0x00000000fed00000,  0x00000000fed00000)eden space 896K,  89% used [0x00000000fec00000,  0x00000000fecc96e0, 0x00000000fece0000)from space 64K,   0% used [0x00000000fece0000,  0x00000000fece0000, 0x00000000fecf0000)to   space 64K,   0% used [0x00000000fecf0000,  0x00000000fecf0000, 0x00000000fed00000)
tenured generation   total 19456K, used 10240K  [0x00000000fed00000, 0x0000000100000000,  0x0000000100000000)the space 19456K,  52% used [0x00000000fed00000,  0x00000000ff7000a0, 0x00000000ff700200,  0x0000000100000000)
Metaspace       used 2661K, capacity 4486K, committed  4864K, reserved 1056768Kclass space    used 288K, capacity 386K, committed 512K,  reserved 1048576K

结果说明:

可以看到,在新生代中的total大小约为1M,并且eden:from:to约为8:1:1。

再次修改配置:

-Xms21m -Xmx21m -XX:NewRatio=2 -XX:+PrintGCDetails -XX:+UseSerialGC

代码如下:

public static void main(String[] args) {byte[] b = null;// 连续向系统申请10MB空间for (int i = 0; i < 10; i++) {b = new byte[1 * 1024 * 1024];}
}

结果如下:

[GC (Allocation Failure) [DefNew: ...] 6048K->1562K(21824K), 0.0029985 secs] ...
Heap
def new generation   total 6784K, used 5839K  [0x00000000fea00000, 0x00000000ff150000,  0x00000000ff150000)eden space 6080K,  87% used [0x00000000fea00000,  0x00000000fef2d188, 0x00000000feff0000)from space 704K,  76% used [0x00000000ff0a0000,  0x00000000ff126b40, 0x00000000ff150000)to   space 704K,   0% used [0x00000000feff0000,  0x00000000feff0000, 0x00000000ff0a0000)
tenured generation   total 15040K, used 1024K  [0x00000000ff150000, 0x0000000100000000,  0x0000000100000000)the space 15040K,   6% used [0x00000000ff150000,  0x00000000ff250010, 0x00000000ff250200,  0x0000000100000000)
Metaspace       used 2661K, capacity 4486K, committed  4864K, reserved 1056768Kclass space    used 288K, capacity 386K, committed 512K,  reserved 1048576K

结果说明:

可以看到,新生代约为7M,老年代约为14M,说明占比为1:2。

012Java虚拟机005垃圾的回收相关推荐

  1. 垃圾回收器的基本原理是什么?垃圾回收器可以马上回收内存吗?有什么办法主动通知虚拟机进行垃圾回收?...

    一.垃圾回收器的基本原理是什么?垃圾回收器可以马上回收内存吗?有什么办法主动通知虚拟机进行垃圾回收?   1.对于GC来说,当程序员创建对象时,GC就开始监控这个对象的地址.大小以及使用情况. 通常, ...

  2. 学习笔记【Java 虚拟机②】垃圾回收

    若文章内容或图片失效,请留言反馈.部分素材来自网络,若不小心影响到您的利益,请联系博主删除. 总目录 学习笔记[Java 虚拟机①]内存结构 学习笔记[Java 虚拟机②]垃圾回收 学习笔记[Java ...

  3. java虚拟机多久触发垃圾回收_每日一问:讲讲 Java 虚拟机的垃圾回收

    昨天我们用比较精简的文字讲了 Java 虚拟机结构,没看过的可以直接从这里查看: 每日一问:你了解 Java 虚拟机结构么? 今天我们必须来看看 Java 虚拟机的垃圾回收算法是怎样的.不过在开始之前 ...

  4. Java虚拟机之垃圾回收详解一

    Java虚拟机之垃圾回收详解一 Java技术和JVM(Java虚拟机) 一.Java技术概述: Java是一门编程语言,是一种计算平台,是SUN公司于1995年首次发布.它是Java程序的技术基础,这 ...

  5. 深入理解Java虚拟机——JVM垃圾回收机制和垃圾收集器详解

    一:概述 说起垃圾回收(Garbage Collection,GC),很多人就会自然而然地把它和Java联系起来.在Java中,程序员不需要去关心内存动态分配和垃圾回收的问题,顾名思义,垃圾回收就是释 ...

  6. 深入理解JVM虚拟机之垃圾回收

    深入理解JVM虚拟机之垃圾回收 什么叫做垃圾? 没有引用指向得对象都称为垃圾,好比如我们放风筝,哪些断了线得风筝都称之为垃圾. JVM怎么查找这些垃圾 一般又两种算法,1.可达性分析.2.引用计数 引 ...

  7. 浅析Java虚拟机的垃圾回收机制(GC)

    目录 一.垃圾回收机制(Garbage Collection) 二.对象回收的时机 引用计数法 可达性分析算法 三.垃圾回收算法 标记-清除算法 标记-复制算法 标记-整理算法 新生代.老年代.永久代 ...

  8. 了解java虚拟机mdash;垃圾回收算法(5)

    引用计数器法(Reference Counting) 引用计数器的实现很简单,对于一个对象A,只要有任何一个对象引用了A,则A的引用计数器就加1,当引用失效时,引用计数器减1.只要对象A的引用计数器的 ...

  9. 存储器的分配与回收算法实现_垃圾内存回收算法

    (给算法爱好者加星标,修炼编程内功) 来源:施懿民 https://zhuanlan.zhihu.com/p/20712073 常见的垃圾回收算法有引用计数法(Reference Counting). ...

  10. Android进阶——性能优化之内存管理机制和垃圾采集回收机制(六)

    文章大纲 引言 一.内存泄漏和内存溢出概述 二.Java运行时内存模型 1.线程私有数据区 1.1.程序计数器PC 1.2.虚拟机栈 1.3 本地方法栈 2.所有线程共享数据区 2.1.Java堆 2 ...

最新文章

  1. 9成P2P平台面临出局,千亿资本何去何从?
  2. aitken插值方法的c++代码_无人驾驶路径规划技术-三次样条插值曲线及Python代码实现...
  3. debian安vs_debian下使用vs code
  4. P7295-[USACO21JAN]Paint by Letters P【平面图欧拉公式】
  5. 第三方登录 人人php,人人网第三方登录接口方案
  6. 提示cannot instantiate abstract class due to following members?
  7. 网络编程 之osi七层协议
  8. Visual Studio中删除所有空行
  9. Linux 部分命令无法使用-bash: /usr/bin/*: Permission denied
  10. 单播、多播(组播)和广播的区别
  11. php地图找房代码,vue-baidu-map简单实现地图找房
  12. 当前服务器更新维护公告,【已开服】1月17日全部服务器更新维护公告
  13. 软件测试的十六种测试类型
  14. 分布式系统下的幂等性问题如何解决?
  15. 基于Android的上位软件,基于Android的电子套结机上位机软件设计
  16. 百度人脸识别测试环境配置教程
  17. 数据库中的层次模型是什么(树形结构)
  18. 愤怒的小鸟4只编外鸟_愤怒的小鸟2编外怎么得
  19. ESP32 单片机学习笔记 - 08 - WebSocket客户端
  20. SQL Server 索引基础知识(7)----Indexing for AND(转自蝈蝈俊.net)

热门文章

  1. find命令查找包含指定内容的文件
  2. docker下安装wekan看板工具
  3. Linux 媒体框架(Media Framework)一
  4. 【Matlab综合设计】开环Buck-Boost升压-降压式变换器Simulink仿真(含仿真模块选择和参数计算过程)
  5. 移动端一倍图,二倍图尺寸
  6. mysql重复查询最后一条数据_sql查询表里重复记录现取重复最后一条记录方法
  7. 娓娓道来图模型、图查询、图计算和图学习知识
  8. 青岛科技大学计算机历年真题,青岛科技大学计算机组成原理补考试卷(计算机)...
  9. 全球与中国乳制品替代杏仁制品市场深度研究分析报告
  10. 纸飞机飞行曲线matlab,从小到大只会做个纸飞机?关于折纸的「高端」技巧通通告诉你...