java基础

Java 内存管理

2016-11-21  本文已影响107人  小草凡

弄清JVM(Java Virtual Machine)的内存管理模型对了解java GC工作原理是很有必要的。最近正好看到一篇文档写的不错,介绍了Java内存管理的处理方式,包括JVM内存分配各个区域的含义,以及如何监测协调GC工作。翻译后在此记录。
原文传送门

内存分配区域

图片来源网络

如上图所示,JVM内存被分成了几个区域,粗略的看JVM的堆内存分成了两块区域----新生域,年老域。

新生域内存管理

刚创建的新对象会被分配到新生域。当新生域使用殆尽,GC就开始工作了。这种状况下促发的GC被称作次级GC(Minor GC)。而新生域又被分成三个部分

新生域的主要特性:

年老域内存管理

年老域的内存存放的是一些长生命周期和被次级GC多次扫描依然幸存下来的对象。GC在年老域内存吃紧的情况下开始工作,这种GC称为主级GC(Major GC),通常主级GC消耗的时间更长。

世界暂停事件(Stop the World Event)

次级GC和主级GC开始工作时,应用线程就会停下来,因此整个Java程序也就会处于停止状态,这种情况就是"Stop the World Event"。

新生域存放的是短生命周期的对象,因而次级GC的这个过程会很快结束,在程序看来几乎不受影响。

主级GC需要扫描所有的幸存对象,因而它花费的时间会较长。一旦主级GC工作,应用程序就会暂停下来,直观感受就是程序不够流畅,无法快速响应业务事件处理,主级GC运行次数过多,甚至会引发程序的超时错误。要想应用程序跑的流畅,就得少去促发主级GC工作,要少促发主级GC工作,就得尽可能的保证年老域的内存空间不被填充满。所以这提醒我们一定要珍惜内存空间,尤其在Android移动设备上。

不同的GC策略会影响GC工作消耗的时长。为了让应用程序运行流畅度最优,就有必要根据应用程序的运行场景运用不同的GC策略。

持久域(Permanent Generation)

JVM通过元数据(metadata)来记录应用程序的类和方法,这些元数据就被放在持久域就,另外Java公共的库文件和方法也被放在这里,持久域不属于堆内存。在内存满时该块内存中的对象也可能被回收。Java8中已经删除该区域。

方法域(Method Area)

方法域属于持久域中的一块,它被用来存放定义方法的代码和类文件结构。

内存池(Memory Pool)

内存池由JVM创建出来存放不可变对象,比如String,它可能在堆内存分配也可能在持久域分配,这取决于JVM的内存管理机制。

运行时常量池( Runtime Constant Pool)

运行时常量池用于存放编译期生成的各种字面量和符号引用以及静态方法,这部分内容将在类加载后存放,它属于方法域的一部分。

栈内存(Stack Memory)

栈内存被执行线程所用。它用来存放方法内的变量,这些变量通常是一些指向堆内存对象的引用,它们生命周期短。

堆内存调节器(Heap Memory Switches)

VM SWITCH VM SWITCH DESCRIPTION
-Xms 初始堆内存大小
-Xmx 最大堆内存
-Xmn 新生域大小,余下空间就是年老域
-XX:PermGen 设置持久域(Permanent Generation)大小
-XX:MaxPermGen 最大持久域
-XX:SurvivorRatio 伊甸园(Eden space)和幸存者(Survivor Space)比值, 比如新生域共分配了10M,-XX:SurvivorRatio=2,那么Eden Space就占5M,余下的5M被两个Survivor spaces均分。默认值为8。
-XX:NewRatio 年老域和新生域比值,默认为2,也就是说年老域大小是新生域大小的2倍

Java提供了许多设置内存大小以及各个内存域占比大小的调节器。常用的调节器如下:

VM SWITCH VM SWITCH DESCRIPTION
-Xms 初始堆内存大小
-Xmx 最大堆内存
-Xmn 新生域大小,余下空间就是年老域
-XX:PermGen 设置持久域(Permanent Generation)大小
-XX:MaxPermGen 最大持久域
-XX:SurvivorRatio 伊甸园(Eden space)和幸存者(Survivor Space)比值, 比如新生域共分配了10M,-XX:SurvivorRatio=2,那么Eden Space就占5M,余下的5M被两个Survivor spaces均分。默认值为8。
-XX:NewRatio 年老域和新生域比值,默认为2,也就是说年老域大小是新生域大小的2倍

更详细的调节器配置信息请查看JVM Options Official Page

垃圾回收器

GC(Garbage Collection)是一个进程,它专注于标示和清理没有引用的对象,释放内存空间给新分配的对象腾地方住。其他某些语言这个过程都是程序猿自己实现的,而Java自动完成了这个过程。

GC作为一个后台进程,一直默默监察着应用程序的运行过程,寻找--->标记-->释放那些没有引用到的对象,为新对象腾空间。
典型的GC处理涉及到的过程如下:

  1. 标记:GC标记哪些对象在使用,哪些对象没有地方使用
  2. 正常删除:GC删除无用的对象所占有的空间,这些空间可以被其他存活的对象所使用
  3. 删除后汇集:为了提升性能,删除无用对象后,所有幸存对象被汇集在一起,如此新对象分配内存时效率会更高。

上述过程可能存在如下问题:

正是为了规避上述问题,Java将堆内存划分成了不同的区域,也就是之前提到的新生域和年老域。

GC类型

垃圾回收策略共有5种类型,根据应用程序业务场景的不同,可以设置差异化的内存调节器。

GC监测

可以用命令行或者可视化工具监测应用程序背后的GC运行情况。这里我用自己写的一段简单测试代码,用命令行来进行GC监控.
先看测试代码,在一个10次的循环结构中,每次去申请一个10M大小的空间,这里忽略掉string申请的空间。

package com.azhengye.test;

import java.util.concurrent.TimeUnit;

public class Test {
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            String string = "i="+i;
            byte[] bt = new byte[1024*1024*10];
            System.out.print(string);
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

我们在命令行里编译运行.


这里写图片描述

可以看到程序运行正常。
接下来参考之前介绍的内存调节器,我们在运行时加上一些参数。


这里写图片描述
问题出来了。
前两次我们配置Xms为2m,运行时爆出错误提示了初始堆内存过小。在最后一次将Xms设置为10m该错误消失,但却爆出了我们常见的OOM问题。

这个简单的例子就说明Java提供的这种内存调节器作用,在某些应用程序运行时,我们要根据不同的场景,配置不同的运行参数。

jstat命令行监测内存

JDK提供了jstat命令用了监测JVM内存使用情况以及GC运行状态。
其使用规则如下:

jstat -gc <processid> <time>

进程id可以通过ps命令查看到

192:~/Documents/eclipse_workspace/DemoTestJava $ ps -eaf | grep java
501 2538 1956 0 11:41下午 ttys000 0:02.50 /usr/bin/java -Xmx20m -Xms10m -Xmn3m -XX:PermSize=2m -XX:MaxPermSize=4m -XX:+UseSerialGC -cp bin/ com.azhengye.test.Test
501 2540 1613 0 11:41下午 ttys001 0:00.00 grep java

有了进程号,在用jstat命令查看详细的内存使用情况,每隔1s打印一次内存情况。

jstat -gc 2538 1s

这里写图片描述

对照上图介绍下每一栏的含义。

jvisualvm可视化监测

JDK同样提供了jvisualvm可视化的监测工具,首次打开需要先安装Visual GC 插件。下图是我截取的界面,功能比较多,这里不做过多介绍了。


这里写图片描述

内存和GC调整

这一步轻易不要走,程序运行的优化更多的注意了应该放在软件实现上。除非很明显的定位到程序运行受到了GC的拖累,或者确实需要调整内存的分配情况才能让程序运行起来最佳。
下面是几点调整建议:

参考链接

了解CMS(Concurrent Mark-Sweep)垃圾回收器
Java 8新特性探究(9):跟OOM:Permgen说再见吧
Java8 Demos and Samples

上一篇 下一篇

猜你喜欢

热点阅读