Java内存分析1 - 从两个程序说起
这次看一些关于JVM内存分析的内容。
两个程序
程序一
首先来看两个程序,这里是程序一:JVMStackTest,看下代码:
package com.zhyea.robin.jvm;
public class JVMStackTest {
private static int count = 0;
private void recur() {
++count;
recur();
System.out.println("recur()方法执行结束");
}
public static void main(String[] args) {
try {
new JVMStackTest().recur();
} catch (Throwable e) {
e.printStackTrace();
} finally {
System.out.println("程序递归了 " + count + "次");
}
}
}
这个类很简单了。JVMStackTest的主体是一个recur()方法。recur()方法是一个递归的方法。所谓递归的方法就是在方法体中又调用了方法自身。在recur()方法中引用了一个类成员变量count来记录递归的次数,并且做了一段输出来提示方法执行结束。JVMStackTest的main方法内容也很少,就是创建了一个JVMStackTest的实例并调用recur()方法,同时使用try{}catch(){}来捕获可能发生的异常或错误,最后在程序执行结束前输出recur()方法递归的次数。
这个程序是有些问题的。问题出在recur()方法这里:recur()是一个递归的方法,但是却没有跳出的语句,这意味着recur()方法将无限的递归下去。我们需要认识到一点:资源是有限的,如果某个服务无限次的占用资源却始终不释放资源就必然会导致问题。每次调用recur()方法都会占用一些资源,资源占用得多了就会导致可用资源不足产生异常或错误。运行下这个程序,看看会报什么样的错误:

在递归了1862次以后报了StackOverflowError,即栈溢出错误。请大家先记住这个错误信息,稍后我们会解释为什么会报这个错。
程序二
再来看看程序二,JVMHeapTest:
package com.zhyea.robin.jvm; import java.util.LinkedList;
import java.util.List; public class JVMHeapTest { private static List<byte[]> list = new LinkedList<>(); private void loop() {
while (true) {
list.add(new byte[1024 * 1024]);
}
} public static void main(String[] args) {
try {
new JVMHeapTest().loop();
} catch (Throwable t) {
t.printStackTrace();
} finally {
System.out.println("list 的长度是:" + list.size());
}
}
}
这个JVMHeapTest也是很简单了,就是定义了一个List列表型的类成员变量list,通过泛型指定list中存储的单元必须是数组,而后通过loop()方法向list中不停地添加数组实例。每个数组的大小为1024,也就是1KB大小。main方法也和JVMStackTest差不多,就是创建一个类实例来调用loop方法,通过try/catch捕获可能会发生的异常,最后在程序执行结束的时候输出list的长度。
看看loop()方法,很明显地可以看出这个类也是存在问题的:这个类中的loop()方法有一个死循环,尤其是在这个死循环中还在不停向一个类成员列表添加元素。当添加的元素大小总和超过了某个上限后就会报错。具体是什么错误呢,运行一下看看:

正如预料,发生了错误。发生错误时列表的长度是7,报的错是OutOfMemoryError,根据错误信息“Java heap space”可以看出OutOfMemory错误是发生在heap上,也就是堆上。
通过这两个程序我们演示了两种常见的内存错误:StackOverflow和OutOfMemory,即栈溢出和内存溢出。这两个错误的发生有一个共同点:无限制的占用系统某一部分资源(程序一的无限递归,程序二死循环),以至于超过了某个上限。我们需要弄明白的就是这里说的资源是什么资源,而上限又具体是多少。
JVM内存概述
关于资源,我们前面反复提到过几次,主要就是内存。不过相较于我们通常提到的内存,这里说的内存还分得更细一些。我们通常说笔记本有4G内存、8G内存,就是笔记本的机器内存,这个内存被称作是直接内存或物理内存。而我们启动一个Java程序就会从直接内存中获取一块让JVM独立使用的空间,这块空间就可以被称为是JVM内存。而JVM内存又可以被粗略地分成两大类:栈内存和堆内存。如下图:

注意我们这里将内存分成栈内存和堆内存是一种很粗略的分法,至于准确的分法我们稍后会单独说。
看一下上图,左侧的细长条标识栈内存,右侧的椭圆标识内存。通常栈内存要比堆内存小得多。
栈内存
在栈内存中存储的是方法执行的信息,包括局部变量表、操作栈、动态链接、方法出口等信息。不知道有没有注意到栈内存的标识图中还有更细小的小长方形。这个小长方形标识的是栈帧。栈帧是栈内存的最小单位。每执行一个方法的时候都会创建一个栈帧。每调用一个方法至执行完成的过程都对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
说到这里可以回过头来看看我们的第一个程序了。我们现在清楚这个程序是占用了哪些资源导致StackOverflow了。在JVMStackTest中的recur()方法中存在一个无限的递归,这样就会为recur()方法无限地的创建栈帧。当虚拟机无法再创建栈帧时,就会产生StackOverflow错误。
这里还有一点需要说明,某些书中提到过这样的说法:如果线程请求的栈深度大 于虚拟机所允许的深度,将抛出StackOverflowError 异常;如果虚拟机栈可以动态扩展 ,当扩展时无法申请到足够的内存时会抛出OutOfMemoryError 异常。这样的说法是没有错的,但是却是存在一些重叠:因为栈是可以动态扩展的,虚拟机无法继续创建栈帧,到底是超过了栈深度还是栈内存不足呢。这个问题没有定论,不过我测试过多次,都是抛出StackOverflow错误。但是不要忘了,理论上也是可能抛出OutOfMemory错误。
堆内存
通常,可以认为堆内存是JVM内存中最大的一部分,在这里存储着几乎所有的对象实例。堆内存也是垃圾回收管理的主要区域。
我们的第二个程序通过一个死循环不停地向list中添加长度很长的数组占用了大量的堆空间,最后没有足够的空间来存储新的实例就会产生OutOfMemory错误。为什么在这个程序里没有发生垃圾回收呢?是因为我们的对象实例,就是那些数组,是保存在一个类成员list中。这个类成员list始终被引用,其中的内容就无法被回收。关于垃圾回收的内容我们以后会详细解说,这里就不提了。
在堆内存这个区域只会发生OutOfMemory错误。
JVM的一些配置
前面我们说过,产生StackOverflow和OutOfMemory是因为“太多的占用系统资源,超过了一个上限”。我们已经知道系统资源是哪些资源了,那个这个上限是什么呢。这里有三个配置项:

Xss的默认值为1M;
Xms的默认值为直接内存的1/64;
Xmx的默认值为直接内存的1/4。
在IDE里这个配置可以在如下位置完成:

大家可以试着调整这些配置,看看运行的结果是否有些不同。
好了,今天就是这些内容,再见!
##########################
Java内存分析1 - 从两个程序说起的更多相关文章
- Java内存分析--栈--堆
Java内存分析--栈--堆 JVM的内存分析: 1.栈内存 1.连续的存储空间,遵循后进先出的原则. 2.每个线程包含一个栈区,栈区只保存基础数据类型的对象和自定义对象的引用. 3.每个栈中的数据都 ...
- Java内存分析简单介绍
原创:转载需注明原创地址 https://www.cnblogs.com/fanerwei222/p/11904422.html Java内存分析简单介绍: 1. # 设置内存溢出时自动生成堆内存快照 ...
- 13 数组 Java内存分析 三种初始化
Java内存分析 三种初始化 静态初始化 //静态初始化 创建+赋值 int[] a = {1,2,3}; Man[] mans = {new Man(1,1),new Man(2,2)}; 动态初始 ...
- [转载]JAVA内存分析——栈、堆、方法区 程序执行变化过程
面向对象的内存分析 参考:http://www.sxt.cn/Java_jQuery_in_action/object-oriented.html :尚学堂JAVA300集-064内存分析详解_栈_堆 ...
- Java内存分析工具MAT
MAT是一个强大的内存分析工具,可以快捷.有效地帮助我们找到内存泄露,减少内存消耗分析工具.内存中堆的使用情况是应用性能监测的重点,而对于堆的快照,可以dump出来进一步分析,总的来说,一般我们对于堆 ...
- Java 内存分析之mat安装
有三款内存分析的工具 免费 VisualVM MAT 下载地址https://www.eclipse.org/mat/ mac 下面 under the current working directo ...
- 菜鸟学Java(二十三)——Java内存分析
我们常说的Java内存主要分为四大块(寄存器不在考虑之内,我们无法用代码来操控它):stack(栈).heap(堆).data segment(数据区).code segment(代码区).它们的主要 ...
- java 内存分析之static
源码: 内存分析: 源码: 静态方法: 用static 声明的方法为静态方法,在调用该方法时,不会将对象的引用传递给它,所以在static 方法中不可访问非static 的成员. 可以通过对象 ...
- java 内存分析之this
package Demo; /** * this 的值是当前对象的引用 * @author Aaron * */ public class Boy { private int age; public ...
随机推荐
- C# .net 数组倒序排序
1.数组方法 Array.Sort(Array Array); 此方法为数组的排序(正序)方法 Array.Reverse(Array Array); 此方法可以将数组中的值颠倒 两个方法结合使用 ...
- Openstack深入了解虚拟机
续Openstack虚拟机创建流程 在控制节点也安装一个计算服务也变成一个计算节点 yum -y install openstack-nova-compute 启动并且设置开机启动 systemctl ...
- SQL架构信息读取
--架构: select * from information_schema.SCHEMATA --表: select table_name from information_schema.table ...
- js parseInt()函数中的问题。。
今天在看<javascript 高级程序设计>时, 与我的输出结果不符合, <!DOCTYPE html> <html lang="en"> & ...
- jmeter断言之BeanShell断言
1.添加BeanShell断言,在Script下面写断言脚本,常用的BeanShell脚本如下: if(!SampleResult.getResponseCode().equals("200 ...
- 使用Xcode改动iOS项目project名和路径名
对,好.错.改正. ------ 前言 系统 10.9 开发平台 xcode 5.0 旧project名 MyProject-iPad 改动之后 新project名 FjSk-iPad 点击项目,进入 ...
- mongo distinct 指定条件
db.Article.distinct("字段名称",{"Comment.Reply.email" : "xxx"})
- Apache和Nigix
Apache , Nginx 是开源的HTTP服务器软件 HTTP服务器本质上也是一种应用程序--它通常运行在服务器之上,绑定服务器的IP地址并监听某一个tcp端口来接收并处理HTTP请 ...
- Python(函数式编程)
函数是Python内建支持的一种封装,我们通过把大段代码拆成函数,通过一层一层的函数调用,就可以把复杂任务分解成简单的任务,这种分解可以称之为面向过程的程序设计.函数就是面向过程的程序设计的基本单元. ...
- redis的一些命令
字符串操作 EX在设置值的时候设置过期时间,ttl查看过期时间 expire能单独设置过期时间 查看所有的key key * 列表操作 lpush从列表左边添加值,rpush从列表右边添加值 lran ...