目录
二.Java内存区域与内存溢出异常
1 运行时数据区域
1.1 程序计数器(Program Counter Register)
1.2 Java虚拟机栈(JVM Stacks)
1.3 本地方法栈(Native Method Stack)
1.4 Java堆(Java Heap)
1.5 方法区(Method Area)
1.6 运行时常量池
1.7 直接内存
2.1 对象的创建
2.2 对象的内存布局
2.3 对象的访问定位
2.4 实战:OOM异常
2.5 总结
Java虚拟机既然是虚拟机,那它需要在内存中占据一片位置,用于存放运行时的数据和代码存放。
这部分区域被称为运行时数据区域,当然硬件层面它就是计算机内存中的一片区域,与硬盘没有任何关系。
它被划分为5块
方法区,堆,虚拟机栈,本地方法栈,程序计数器。
作用:
特点:
作用:
特点:
作用:
这个栈与Java虚拟机栈类似,区别在于它保存的方法不再是Java方法,而是Native方法,Native方法就是针对不同的运行环境,而实现的方法,它针对虚拟机所运行的环境而有不同的实现,顾名思义本地方法。
特点:
虚拟机规范中没有具体规定本地方法需要使用的语言和使用方式以及数据结构。
在HotSpot虚拟机(虚拟机也分很多版本)中直接将本地方法栈与虚拟机栈合二为一。
当然报的异常与Java虚拟机栈相同。栈溢出与内存溢出。
作用:
特点:
(在GC时也被称为永久代Permanent Generation,一般简写为Perm,一般别名叫Non-Heap非堆,我的理解是堆的一部分,因为作用和堆有所不同因此为了区分与堆不一样而得名。)
作用:
用于储存已被Java虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码数据等。
特点:
------------- 最后结束修改于2018年12月 31日 22:13 待续... -------------
(需要与class文件常量池,字符串常量池区分开来,后面会做详说)
作用:在类加载后,存放类被编译期的各种字面量和符号引用。
特点:
补充:相关博客
https://blog.csdn.net/zm13007310400/article/details/77534349
作用:
在某些场景中增加性能
特点:
遇到一条 new 指令
| 检查指令的参数能否在 常量池定位到类的符号引用
|符号引用是否已被加载,解析,初始化过
|没有 需执行相应的类加载过程
|类检查过后,为新生对象分配内存,相当于在堆中分配一块确定大小的内存。
2.1.2 分配内存方式
假设堆中的内存绝对规整,用过与未用过的分别放一边,中间放一个指针当做分界点的指示器。
分配内存仅仅是把指针向空闲空间挪动与新对象内存大小相等的一端距离。
假设堆中内存相互交错,虚拟机需要维护一个列表,确定哪些内存块是可用的。
分配内存时找到一块足够大,对象能放下的内存块,
补充:
当使用Serial,ParNew等有Compact过程的垃圾收集器时,系统内存分配方式为指针碰撞。
当使用CMS(基于Mark-Sweep标记清楚算法收集垃圾)这种收集器时,系统采用空闲列表。
CG机制将在第三章的笔记详述。
2.1.3 内存空间初始化
保证实例字段在不赋初值的情况下就能直接使用。程序能访问到这些字段的数据类型对应的0值。
在HotSpot虚拟机中,对象在内存中的布局可以分为3部分。
对象头 (header)------- 实例数据(Instance Data)------- 对齐填充(Padding)
2.2.1 对象头
分为两部分
哈希码
GC分代年龄(用于GC回收参考,将在第三章详解)
锁状态标志
线程持有的锁
偏向线程ID
偏向时间戳
虚拟机通过此指针来确定对象属于哪个类的实例
如果对象为一个数组时,对象头中还须有一块用于记录数组长度的数据。因为jvm可以通过普通Java对象的元数据信息确定Java对象的大小。但是从数组的元数据区确定不了,因此需要对象头里有数组的长度记录。
2.2.2 实例数据
存储对象真正有效的信息。
程序代码中所定义的各种类型的字段内容,不论是父类继承还是子类定义的,都记录。
存储顺序手虚拟机分配策略参数(FieldAllocationStyle)和源码中顺序影响。
2.2.3 对齐填充
------------- 最后结束修改于2019年1月 15日 21:52 待续... -------------
当建立对象后,想使用对象,Java程序需要通过栈上的reference数据来操作堆上的具体对象。
目前主流的访问对象的方式有,句柄和直接指针两种。
句柄:
解释:在Java堆中划分出一个句柄池,reference数据存储的是对象的句柄地址,句柄里存放的是对象的实例数据与类型数据各自的具体地址信息。
优点:reference中指定句柄的地址不会变。对象变动时,只需变动句柄里的实例数据指针即可。
直接指针:
解释:reference直接指向对象的实例数据,对象的类型指针在对象的实例数据里存放。
有点:访问快,因为减少了一次指针定位的时间开销。
两种指向方式中对象的类型数据存放都在方法区。
在Hotspot中使用的是直接指针的方式访问对象。
2.4.1 Java堆溢出
实践方式:不断的在堆中创建对象。限制堆的大小以尽快达到堆溢出。
限制堆大小:将堆的最小值 -Xms 和最大值 -Xmx 设为相等可以防止堆的自动扩展。
dump出堆内存转储快照:此操作用于堆溢出后的异常分析。设置参数:-XX:+HeapDumpOnOutOfMemoryError。
实践过程:
https://blog.csdn.net/qq_29519041/article/details/86513666
2.4.2 Java栈溢出
HotSpot虚拟机中不区分虚拟机栈与本地方法栈。
实践方式:通过不断递归调用自己,不断入栈入栈的方式达到栈溢出。
限制栈大小:使用虚拟机参数 -Xss
实践过程:
https://blog.csdn.net/qq_29519041/article/details/86513246
------------- 最后结束修改于2019年1月 16日 20:33 待续... -------------
2.4.3 方法区和运行时常量池溢出
Ps:运行时常量池为方法区的一部分。
String.intern()方法:
如果字符串常量池中已包含一个等于此String对象的字符串,则返回代表池中这个字符串的String对象。
否则将此String对象包含的字符串添加到常量池中,并返回String对象的引用。
实践方式:通过创建一个数组,并且不断向数组中添加字符串,并调用intern方法,使得常量池中字符串越来越多。以致溢出。
2.4.4 本机直接内存溢出
实践方式:
本机直接内存的分配需要使用Unsafe类,但只有rt.jar(java基础类库)中的类才能使用户Unsafe类中的方法
因此这里需要用到反射获取Unsafe实例进行内存分配。
/**
* DirectMemoryOOM.java
* @author anyunpei
*2019年1月23日下午5:44:34
*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);
}
}
}
补充: http://www.cnblogs.com/duanxz/p/6097779.html
通过第二章的学习及在网上阅读其他优秀的博客熟悉了Java的内存区域,各区域的作用,特点,及内存溢出异常的处理和实践。