这次看一些关于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 - 从两个程序说起的更多相关文章

  1. Java内存分析--栈--堆

    Java内存分析--栈--堆 JVM的内存分析: 1.栈内存 1.连续的存储空间,遵循后进先出的原则. 2.每个线程包含一个栈区,栈区只保存基础数据类型的对象和自定义对象的引用. 3.每个栈中的数据都 ...

  2. Java内存分析简单介绍

    原创:转载需注明原创地址 https://www.cnblogs.com/fanerwei222/p/11904422.html Java内存分析简单介绍: 1. # 设置内存溢出时自动生成堆内存快照 ...

  3. 13 数组 Java内存分析 三种初始化

    Java内存分析 三种初始化 静态初始化 //静态初始化 创建+赋值 int[] a = {1,2,3}; Man[] mans = {new Man(1,1),new Man(2,2)}; 动态初始 ...

  4. [转载]JAVA内存分析——栈、堆、方法区 程序执行变化过程

    面向对象的内存分析 参考:http://www.sxt.cn/Java_jQuery_in_action/object-oriented.html :尚学堂JAVA300集-064内存分析详解_栈_堆 ...

  5. Java内存分析工具MAT

    MAT是一个强大的内存分析工具,可以快捷.有效地帮助我们找到内存泄露,减少内存消耗分析工具.内存中堆的使用情况是应用性能监测的重点,而对于堆的快照,可以dump出来进一步分析,总的来说,一般我们对于堆 ...

  6. Java 内存分析之mat安装

    有三款内存分析的工具 免费 VisualVM MAT 下载地址https://www.eclipse.org/mat/ mac 下面 under the current working directo ...

  7. 菜鸟学Java(二十三)——Java内存分析

    我们常说的Java内存主要分为四大块(寄存器不在考虑之内,我们无法用代码来操控它):stack(栈).heap(堆).data segment(数据区).code segment(代码区).它们的主要 ...

  8. java 内存分析之static

    源码: 内存分析: 源码: 静态方法:   用static 声明的方法为静态方法,在调用该方法时,不会将对象的引用传递给它,所以在static 方法中不可访问非static 的成员.   可以通过对象 ...

  9. java 内存分析之this

    package Demo; /** * this 的值是当前对象的引用 * @author Aaron * */ public class Boy { private int age; public ...

随机推荐

  1. 用angular做的模糊搜索

    今天大家来试一试用angular做一下简单的搜索功能吧: 首先我们需要写html的部分,我们需要设置几个条件,比如按什么来排序,按升序还是降序搜索,和一个文本框来设置模糊搜索: <nav> ...

  2. iOS开发——生命周期

    为了处理好应用程序的挂起.暂停等情况下的数据保存,或对应添加所需处理,我们必须了解ios生命周期. 但是不要去背去记,做个实验就好. - (BOOL)application:(UIApplicatio ...

  3. c#读取excel到dataset

    public DataSet TransExcelToDataSet(string fileName, List<string> sheetNames) { OleDbConnection ...

  4. Spring MVC http请求地址映射(三)

    Spring MVC框架通过扫描将带有@Controller的类中的@RequestMapping的方法进行映射,然后调用映射的方法处理请求,这个分发过程默认是由DispaterServlet处理的. ...

  5. atitit.client连接oracle数据库的方式总结

    client连接oracle数据库的方式总结 文件夹 Java程序连接一般使用jar驱动连接.. ... 桌面GUI一般採取c语言驱动oci.dll 直接连接... 间接连接(须要配置tns及其env ...

  6. weal woe

    He is worth no weal that can bide no woe. 禁不起吃苦的人不配得到幸福 有句谚语叫No weal without woe 福兮祸所伏 ; 祸兮福所倚 weal和 ...

  7. MySQL按时间查找

    RecentMutations表的结构如图,现在的需求是需要查找到2017年09月08日前10天的变体总数: SQL语句:SELECT SUM(MutantNumber) FROM RecentMut ...

  8. EasySQLMAIL使用实践系列

    原文:http://blog.sina.com.cn/s/articlelist_5713986487_0_1.html 通过sql语句发送微信消息(转) 使用EasySQLMAIL的外部接口功能实现 ...

  9. eclipse导入项目,项目名出现红叉的情况(修改版)

    转至:http://blog.csdn.net/niu_hao/article/details/17440247 今天用eclipse导入同事发给我的一个项目之后,项目名称上面出现红叉,但是其他地方都 ...

  10. loadrunner winsocket sent buffer 乱码

    data.ws里手写的xml参数,调试脚本时一直显示乱码,解决方法如下: tools-recording options--sockets--winsock下: EBCID--translation ...