这次看一些关于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. pip install selenium==版本号 报错

    安装selenium是注意不要带版本号直接用如下命令: pip install selenium

  2. Office word中去掉首页的页眉

    1.首先将光标位置移动到第二页的开始,然后点击页面布局命令. 2.页面布局里面找到分隔符,找到下一页的分隔符.(分页符分页) 3.双击第二页的页眉,打开页眉编辑菜单.将连接到前一条页眉的命令去掉. 4 ...

  3. JDK源码分析之concurrent包(二) -- 线程池ThreadPoolExecutor

    上一篇我们简单描述了Executor框架的结构,本篇正式开始并发包中部分源码的解读. 我们知道,目前主流的商用虚拟机在线程的实现上可能会有所差别.但不管如何实现,在开启和关闭线程时一定会耗费很多CPU ...

  4. 巨蟒python全栈开发-第16天 核能来袭-初识面向对象

    一.今日内容总览(上帝视角,大象自己进冰箱,控制时机) #转换思想(从面向过程到面向对象) 1.初识面向对象 面向过程: 一切以事物的发展流程为中心. 面向对象: 一切以对象为中心,一切皆为对象,具体 ...

  5. Powershell Get Domain Group的几种方法

    Group常见属性介绍: 一.Get-ADGroup获取群组(如下例循环获取群组的发送权限) #群组的发送权限info $groups=Get-ADGroup -filter * -SearchSco ...

  6. ubuntu14.04 编译安装CPU版caffe

      本文,试图中一个干净的ubuntu14.04机器上安装caffe的cpu版本. http://blog.csdn.net/sinat_35188997/article/details/735304 ...

  7. 简单的 H5 视频推流解决方案

    导语 随着直播平台爆发式增长,直播平台从 PC 端转战移动端,紧跟着直播的潮流,自己学习实现了一套简单的 H5 视频推流的解决方案,下面就给小伙伴们分享一下自己学习过程中的经验. 环境部署 1. 配置 ...

  8. MySQL-5.7密码策略及用户资源限制

    1.密码策略 在mysql 5.6对密码的强度进行了加强,推出了validate_password 插件.支持密码的强度要求. (1)安装插件 [root@localhost ~]# ll /usr/ ...

  9. Linux下Mysql 操作命令

    Linux下Mysql 操作命令 一.连接MySQL 格式: mysql -h主机地址 -u用户名 -p用户密码 1.例1:连接到本机上的MYSQL. 首先在打开DOS窗口,然后进入目录 mysqlb ...

  10. AMBA总线基础知识简介

    AMBA:Advanced Microcontroller Bus Architecture,是ARMA公司的片内互联总线协议. 1995 - AMBA1.0 APB外设总线及ASB系统总线发布. 1 ...