jvm内存结构回顾:

.8同1.7比,最大的差别就是:元数据区取代了永久代。
元空间的本质和永久代类似,都是对JVM规范中方法区的实现。 不过元空间与永久代之间最大的区别在于:元数据空间并不在虚拟机中,而是使用本地内存。 jdk1.4引入了NIO,它可以使用Native函数库直接分配堆外内存。(这也是为什么nio效率高)

1 . 程序计数器

程序计数器(Program Counter Register)是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。

JVM的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的

每条线程拥有一个独立的程序计数器,各线程之间计数器互不影响
此内存区域是唯一一个在Java虚拟机规范中没有规定任何 OutOfMemoryError情况的区域

2 . Java虚拟机栈

1,同程序计数器相同,Java虚拟机栈(Java Virtual Machine Stacks)也是线程私有的
2,虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
3,每个方法对应一个栈帧在虚拟机栈中入栈到出栈的过程。

局部变量表中存放了编译期可知的类型。

八大数据类型(boolean、byte、char、short、int、float、long、double)。
对象引用(reference类型,它不等于对象本身,可能是一个指向对象起始地址的指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)
returnAddress类型(指向了一条字节码指令的地址)(ps:应该就是一个方法)

因为类型可知,帧中分配多大的局部变量空间是完全确定的,在方法运行期间局部变量表的大小也不变。

出现的异常:

,方法嵌套栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常。
,动态扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常。

3 . 本地方法栈

本地方法栈(Native Method Stack)与虚拟机栈所发挥的作用类似,
区别是:虚拟机栈执行Java方法(也就是字节码),而本地方法栈使用Native方法。

甚至有的虚拟机(Sun HotSpot虚拟机)直接把本地方法栈和虚拟机栈合二为一。

同样抛出:

StackOverflowError和OutOfMemoryError异常。

4 . Java堆

被所有线程共享的一块内存区域,在虚拟机启动时创建。几乎所有的对象实例都在这里分配内存。

Java堆是垃圾回收器管理的主要区域,因此被称为“GC堆”(Garbage Collected Heap)。

从内存回收角度看,由于目前收集器基本采用分代收集算法,所以Java堆可细分为:新生代和老年代。
从内存分配角度看,线程共享的Java堆中可能划分出多个线程私有的分配缓冲区(TLAB:Thread Local Allocation Buffer)。

堆中没有内存完成实例分配,并且堆也无法扩展时,将会抛出OutOfMemoryError异常。

5 . 方法区

方法区(Method Area)与堆一样,是各个线程共享的,用于存储类信息、常量、静态变量、
内存回收目标主要是针对常量池的回收和对类型的卸载

方法无法满足内存需求时,将会抛出OutOfMemoryError异常。

6 . 运行时常量池

运行时常量池(Runtime Constant Pool)是方法区的一部分。
Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有常量池(Constant Pool Table,各种字面量和符号引用)类加载后存放在方法区的运行时常量池。
除了保存Class文件中的描述符号引用外,还会把翻译出的直接引用也存储在运行时常量池中。(也就是动态解析时添加)

运行时常量池相对于Class文件常量池的另外一个重要特征是具备动态性:

运行期间也可能将新的常量放入池中,比如:String类的intern() 方法。

由于运行时常量池是方法区的一部分,所以会受到方法区内存的限制,当常量池无法再申请到内存时会抛出OutOfMemoryError: PermGen space异常

(Java 8以后没有方法区,由本地元空间代替,溢出会抛出OutOfMemoryError: Metaspace异常)。

7 . 直接内存

这部分的数据是在JVM之外的,因此它不会占用应用的内存。

本机直接内存的分配不会受到Java堆大小的限制,还是会受到本机总内存(包括RAM以及SWAP区或分页文件)大小以及处理器寻址空间的限制。
服务器管理员在配置虚拟机参数时,会根据实际内存设置-Xmx等参数信息,但经常忽略直接内存,
使得各个内存区域总和大于物理内存限制(包括物理的和操作系统的限制),从而导致动态扩展时出现OutOfMemoryError异常。

简要说明一下内存泄露和内存溢出的区别:

内存泄露是指:因为内存被无用的资源占用,导致内存不够。

分配出去的内存没有被回收回来,由于失去了对该内存区域的控制,因而造成了资源的浪费。
Java中一般不会产生内存泄露,因为有垃圾回收器自动回收垃圾,但这也不绝对,
当我们new了对象,并保存了其引用,但是后面一直没用它,而垃圾回收器又不会去回收它,这边会造成内存泄露,

内存溢出是指:程序所需要的内存超出了系统所能分配的内存(包括动态扩展)的上限。

补充:JVM内存参数( -Xms -Xmx -Xmn -Xss 直接内存)

以下基于《深入理解Java虚拟机》

重现OutOfMemoryError异常

除了程序计数器外,虚拟机内存的其它区域都有发生OOM异常的可能

目的:根据异常信息判断出是那个区域,定位到错误原因

1 . Java堆溢出

Java堆用于存储对象实例,只要不断地创建对象,并且避免垃圾回收机制清除这些对象,那么在对象数量到达最大堆的容量限制后就会产生内存溢出异常。

/**
* VM Args:-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
*/
public class HeapOOM { static class OOMObject {
} public static void main(String[] args) {
List<OOMObject> list = new ArrayList<OOMObject>(); while (true) {
list.add(new OOMObject());
}}}
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid1820.hprof ...
Heap dump file created [24787111 bytes in 0.346 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

当出现异常时,堆栈消息“java.lang.OutOfMemoryError”会进一步提示“Java heap space”,而问题原因很明显,对象数量过多,到达最大堆的容量限制。

2 . 虚拟机栈和本地方法栈溢出

如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError
如果虚拟机在扩展栈时无法申请到足够的内存空间,将抛出OutOfMemoryError。

异常实质上存在着重叠:当栈空间无法继续分配时,是内存太小还是已使用的栈空间过大。

【虚拟机栈和本地方法栈OOM测试(仅作第一点测试)】
/**
* 如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError
* 如果虚拟机在扩展栈时无法申请到足够的内存空间,将抛出OutOfMemoryError
* VM Args:-Xss128k
*/
public class JavaVMStackSOF { private int stackLength = 1; public void stackLeak() {
stackLength++;
stackLeak();
} public static void main(String[] args) throws Throwable {
JavaVMStackSOF oom = new JavaVMStackSOF();
try {
oom.stackLeak();
} catch (Throwable e) {
System.out.println("stack length:" + oom.stackLength);
throw e;
} } }
stack length:2403
Exception in thread "main" java.lang.StackOverflowError
at baby.oom.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:11)
at baby.oom.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:12)
at baby.oom.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:12) 默认情况下,即不加Xss限制,输出的length为8956,加了Xss128k length位2403

在单线程下,无论是由于栈帧太大还是虚拟机栈容量太小,当内存无法分配的时候,虚拟机抛出的都是StackOverflowError。

通过不断地建立线程的方式可以产生内存溢出异常,

但是这样产生的内存溢出异常与栈空间是否足够大并不存在任何联系,为每个线程的栈分配内存越大,越容易产生内存溢出的异常。

简述操作系统内存分配

由于操作系统分配给每个进程的内存是有限的,
虚拟机提供了参数来控制Java堆和方法区域(Xmx(最大堆容量),MaxPermSize(最大方法区容量))。
其余内存主要由虚拟机栈和本地方法瓜分。

每个线程分配到的栈容量越大,可以建立的线程数量自然越少,建立线程越容易耗尽剩下内存。

【创建线程导致内存溢出异常】

/**
* VM Args:-Xss2M (这时候不妨设大些)
*/
public class JavaVMStackOOM { private void dontStop() {
while (true) {
}
} public void stackLeakByThread() {
while (true) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
dontStop();
}
});
thread.start();
}
} public static void main(String[] args) throws Throwable {
JavaVMStackOOM oom = new JavaVMStackOOM();
oom.stackLeakByThread();
}
Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread  

以上测试只是说明线程太多可能导致栈内存不够。

3 . 方法区和运行常量池溢出

由于运行时常量池是方法区的一部分,因此两者测试放在一起。

测试中涉及到String.intern()方法,

它是一个Native方法,作用为:如果字符串常量池中已经包含一个等于此String对象的字符串,则返回String对象;
否则将此String对象包含的字符串添加到常量池中,并返回String对象的引用。
【运行时常量池导致的内存溢出异常】

/**
* VM Args:-XX:PermSize=10M -XX:MaxPermSize=10M
*/
public class RuntimeConstantPoolOOM { public static void main(String[] args) {
// 使用List保持着常量池引用,避免Full GC回收常量池行为
List<String> list = new ArrayList<String>();
// 10MB的PermSize在integer范围内足够产生OOM了
int i = 0;
while (true) {
list.add(String.valueOf(i++).intern());
}
}
}
Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
at java.lang.String.intern(Native Method)
at baby.oom.RuntimeConstantPoolOOM.main(RuntimeConstantPoolOOM.java:18)

在OutOfMemoryError后面跟随的提示信息时“PermGen space”(Permanent Generation space)是指内存的永久保存区域,也就方法区。

4. 本机直接内存溢出

DirectMemory容量可通过 -XX: MaxDirectMemorySize指定,若不指定则默认与Java堆最大值(-Xmx指定)相同。

以下代码越过了DerictByteBuffer类,直接通过反射获取Unsafe 实例进行内存分配。

虽然使用DerictByteBuffer分配内存也会抛出内存溢出异常,但它抛出异常时并没有真正向操作系统申请分配,

而是通过计算得知内存无法分配,于是手动抛出异常,真正申请分配内存的方法是unsafe.allocateMemory()

使用unsafe 分配本机内存】

/**
* VM Args:-Xmx20M -XX:MaxDirectMemorySize=10M
*/
public class DirectMemoryOOM { private static final int _1MB = 1024 * 1024; public static void main(String[] args) throws Exception {
Field unsafeField = Unsafe.class.getDeclaredFields()[0];
unsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) unsafeField.get(null); //获取类变量
while (true) {
unsafe.allocateMemory(_1MB);
}
}
}
Exception in thread "main" java.lang.OutOfMemoryError
at sun.misc.Unsafe.allocateMemory(Native Method)
at baby.oom.DirectMemoryOOM.main(DirectMemoryOOM.java:20)

由DirectMemory导致的内存溢出,明显的特征就是在 Heap Dump文件中不会看见明显的异常,

如果开发人员发现OOM异常后的Dump文件很小,而程序中又直接或间接使用了NIO,可以考虑是否是这方面的原因。

补充:java程序性能分析之thread dump和heap dump

jvm高级特性(1)(内存泄漏实例)的更多相关文章

  1. jvm高级特性(4)(内存分配回收策略)

    JVM高级特性与实践(四):内存分配 与 回收策略 一. 内存分配 和 回收策略 1,对象内存分配的概念: 往大方向讲,它就是在堆上分配(但也可能经过JIT编译后被拆散为标量类型并间接地栈上分配), ...

  2. jvm高级特性(2)(判断存活对象算法,finaliza(),方法区回收)

    JVM高级特性与实践(二):对象存活判定算法(引用) 与 回收 垃圾回收器GC(Garbage Collection) 于1960年诞生在MIT的Lisp是第一门真正使用内存动态分配和垃圾收集技术的语 ...

  3. jvm高级特性(6)(线程的种类,调度,状态,安全程度,实现安全的方法,同步种类,锁优化,锁种类)

    JVM高级特性与实践(十三):线程实现 与 Java线程调度 JVM高级特性与实践(十四):线程安全 与 锁优化 一. 线程的实现 线程其实是比进程更轻量级的调度执行单位. 线程的引入,可以把一个检查 ...

  4. 《深入理解Java虚拟机:JVM高级特性与最佳实践》【PDF】下载

    <深入理解Java虚拟机:JVM高级特性与最佳实践>[PDF]下载链接: https://u253469.pipipan.com/fs/253469-230062566 内容简介 作为一位 ...

  5. jvm高级特性(5)(1)(原子性,可见性,有序性,volatile,概述)

    JVM高级特性与实践(十二):高效并发时的内外存交互.三大特征(原子.可见.有序性) 与 volatile型变量特殊规则 简介: 阿姆达尔定律(Amdahl):该定律通过系统中并行化与串行化的比重来描 ...

  6. 读书笔记-《深入理解Java虚拟机:JVM高级特性与最佳实践》

    目录 概述 第一章: 走进Java 第二章: Java内存区域与内存溢出异常 第三章: 垃圾收集器与内存分配策略 第四章: 虚拟机性能监控与故障处理 第五章: 调优案例分析与实战 第六章: 类文件结构 ...

  7. JVM高级特性与实践(一):Java内存区域 与 内存溢出异常

    套用<围城>中的一句话,“墙外面的人想进去,墙里面的人想出来”,用此来形容Java与C++之间这堵内存动态分配和垃圾收集技术所围成的“围墙”就再合适不过了. 对于从事C.C++的开发人员而 ...

  8. JVM高级特性-一、java内存结构区域介绍

    区域划分: java虚拟机在执行程序的过程中,将内存分为功能不同的几个区域,如下图: 此图列出了内存划分的各个区域,其中 线程私有的:程序计数器.虚拟机栈.本地方法栈 线程共享的:堆.方法区 下面,逐 ...

  9. JVM高级特性-二、JVM在堆中对象的分配、布局、访问过程

    前面介绍了jvm运行时数据区域后,下面讲解下对内存中数据的其他细节,看他们是如何创建.布局及访问的 一.对象的创建 1.对象的分配 对象的创建分配方式主要有两种:指针碰撞和空闲列表 指针碰撞: 假设堆 ...

随机推荐

  1. 【Linux】Jenkins配置和使用(二)

    摘要 本章介绍Jenkins的简单使用,关于Jenkins的安装,参照[Linux]Jenkins安装(一) 事例说明:在linux环境下,安装的jenkins,集成svn,tomcat的环境,项目是 ...

  2. KbmMW 4.5 发布

    We are happy to announce the release of kbmMW v. 4.50.00 Professional, Enterprise and CodeGear Editi ...

  3. Intelj IDEA的pom.xml显示错误can not reconnect

    从GitHub上边down下来的开源项目,报错莫名其妙,后来发现是跟本地hosts文件有关系 hosts文件加上 127.0.0.1 localhost 然后pom文件reload一下就

  4. 43 We were Born to Nap 我们天生需要午睡

    We were Born to Nap 我们天生需要午睡 ①American society is not nap-friendly.In fact, says David Dinged, a sle ...

  5. springMVC学习(注解实现依赖注入)

    原文:http://blog.csdn.net/mockingbirds/article/details/45399691 上一篇博客,学习了spring的依赖注入,即利用spring容器来为类中的属 ...

  6. Python 爬取数据入库mysql

    # -*- enconding:etf-8 -*- import pymysql import os import time import re serveraddr="localhost& ...

  7. ansible-playbook api 2.0 直接运行

    官方文档见  http://docs.ansible.com/ansible/dev_guide/developing_api.html 拿官方的例子修改如下 import json from col ...

  8. Redis Cluster原理初步

    目录 目录 1 1. 前言 1 2. 槽(slots) 1 3. 路由配置(node.conf) 1 4. 总slots数(cluster.h:16384) 2 5. key的路由 2 6. 将key ...

  9. What if you are involved in an automobile accident in the US

    What if you are involved in an automobile accident in the US With increasing Chinese tourists and vi ...

  10. Eclipse 4.2 failed to start after TEE is installed

    ---------------  VM Arguments---------------  jvm_args: -Dosgi.requiredJavaVersion=1.6 -Dhelp.lucene ...