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 ...
随机推荐
- 记录--关于Jquery uploadify 不能动态传值的问题(java)
动态传值纠结多时后无效, 后得下面一番代码,依旧无效~~ 纳了几个闷,心灰意冷下 清理了 tomcat 一次 再出运行 可以了 真心纠结很久很久 无奈之下还是得 清理清理tomcat: ...
- 巨蟒python全栈开发-第16天 核能来袭-初识面向对象
一.今日内容总览(上帝视角,大象自己进冰箱,控制时机) #转换思想(从面向过程到面向对象) 1.初识面向对象 面向过程: 一切以事物的发展流程为中心. 面向对象: 一切以对象为中心,一切皆为对象,具体 ...
- 接口测试工具 — jmeter(header与cookie的添加)
1.header的添加 添加HTTP信息头管理器 填写header 2.添加cookie 添加HTTP Cookie管理器 添加cookie值
- Apache配置HTTPS的过程小记
一.HTTPS的summery,综述,它的基本原理,扫肓. http://www.codeceo.com/article/https-knowledge.html 读过后,就明白https怎么加密的, ...
- java 内存空间
堆:new 出的对象在堆上 java栈:java程序.线程运行数据.内存数据 每个方法都有自己的栈.运行时需要的数据存在自己的栈中 每个线程对立的是图中浅蓝色的部分(java栈.本地方法栈.程序计数器 ...
- Mysql学习笔记—索引
一.什么是索引 一般的应用系统,读写比例在10:1左右,而且插入操作和一般的更新操作很少出现性能问题,遇到最多的,也是最容易出问题的,还是一些复杂的查询操作,所以查询语句的优化显然是重中之重. 在数据 ...
- Eclipse ftp插件
一个比较好的插件:esftp 下载地址http://sourceforge.net/projects/esftp/ 解压后放到plugins目录下重启即可. 配置estfp, 右击项目crm-> ...
- BLOG总结
1.登录:http://www.cnblogs.com/shaojiafeng/p/7868195.html 2.注册 - urls -前端页面中写 username ,password,passwo ...
- mysql完整备份与恢复
1.备份单个数据库 mysql数据库自带了一个很好用的备份命令,就是mysqldump,他的基本使用如下: 语法:mysqldump -u 用户名 -p 数据库名 > 备份的文件名 备份一 1. ...
- 学员管理系统(简单的Django设计)
学员管理系统(简单的Django设计) 学员管理系统 项目规划阶段 项目背景 近年来老男孩教育的入学学员数量稳步快速增长,传统的excel统计管理学员信息的方式已经无法满足日渐增长的业务需求.因此公司 ...