Java虚拟机之内存区域
1、Java有5个运行时数据区域
方法区、堆、虚拟机栈、本地方法栈和程序计数器。
-
方法区
线程共享区域,存放已被虚拟机加载的类信息、常量信息、静态信息等,而常量信息会存放在方法区的运行时常量池中。
-
堆
线程共享区域,存放虚拟机创建的类实例对象。
-
虚拟机栈
线程私有区域,每当调用一个方法便会在虚拟机栈中创建一个方法栈,包含局部变量表、操作数栈、动态链接和方法出口等。
-
本地方法栈
虚拟机本身所需的native方法栈区域。
-
程序计数器
线程私有的区域,当前程序的行号指示器。
-
相关虚拟机参数:
-Xmx:内存最大值
-Xms:内存最小值
-XX:+HeapDumpOnOutOfMemoryError:产生内存溢出异常时Dump出当前的内存堆转储快照
-Xss:唯一设定栈大小的参数
-XX:PermSize:方法区最小值
-XX:MaxPermSize:方法区最大值
-XX:MaxDirectMemorySize:直接内存最大值
另外Java的总内存除了上述的5个数据区域之外还有一个直接内存,直接内存并非虚拟机运行数据时必要的区域,但也会被经常使用,如NIO(new input/output)类引入了一种基于通道与缓存区的I/O方式,可以使用native方法直接分配堆外内存,以提高性能。因此忽略直接内存很可能会引起OutOfMemoryError异常。
2、内存溢出与内存泄漏
内存溢出指虚拟机存放的数据超出设定的内存大小,而内存泄漏是指本来应该被GC清除的对象没被清除而导致的内存溢出。产生内存泄漏时需要借助工具查看泄漏对象到GC Roots的引用链从而定位泄漏代码的位置。
使各个数据区域产生内存溢出的方法:
-
堆和虚拟机栈产生内存溢出的方法很简单,不断创建新的实例对象和执行没有出口的迭代函数即可实现
-
运行时常量池产生异常的方法:利用String类的特点,使用String.inter()这个native方法不断加入新的字符串,并将新的字符串加入某个集合内保证该字符串不被回收。
-
方法区产生异常的方法:不断使用CGLIB字节码技术动态生成class加进内存中
-
直接内存溢出:使用Unsafe类绕过虚拟机直接分配直接内存中的内存
3、Java对象访问
主流的对象访问有两种:句柄和直接指针。
- 句柄
在堆中划分出一个句柄池,对象引用reference指向某一个句柄,而该句柄包括了对象实例数据(只属于该对象的数据)和对象类型数据(只属于该类型的数据),使用句柄的好处在于对象被移动时只需更改句柄中的指针而不需更改reference,但由于访问对象数据需要两次指针定位,增加了时间开销。
Java采用了直接指针访问对象形式,节省一次指针定位会非常可观地减少频繁访问对象带来的时间开销。
4、虚拟机性能监控工具
java自带了性能监控工具,放置在bin目录之下
I.命令行工具
-
JPS:JVM Process Status 进程监测工具
-
Jstat: JVM Statistics 虚拟机统计信息工具,主要监测内存和GC信息
-
JInfo:JVM Configuration Info 虚拟机参数配置工具
-
JMap:JVM Memery Map 虚拟机堆转储快照
-
JStack:JVM Stack Stool 调用堆栈快照
II.可视化工具
-
JConsole:监控台
-
VisualVM:多合一故障处理工具