[jjzhu学java]深入理解JVM笔记之内存管理机制

简介: 深入理解JVM笔记之内存管理机制运行时数据区域程序计数器JVM栈本地方法栈Java堆方法区运行时常量池直接内存对象访问OutOfMemoryError异常Java堆溢出示例JVM栈和本地方法栈溢出运行时常量池溢出本机直接内存溢出深入理解JVM笔记之内存管理机制运行时数据区域程序计数器每个线程

深入理解JVM笔记之内存管理机制

运行时数据区域

程序计数器

每个线程都有一个程序计数器(PC),是当前线程所执行的字节码的行号指示器,通过改变程序计数器的值来选取下一条指令。各线程之间的计数器互不影响,是线程私有的内存。
如果线程执行的是一个JAVA方法,则计数器记录的为正在执行的字节码指令的地址,如果执行的是Natvie方法,这计数器的值为空(Undifined)。
程序计算器所在的内存区域是唯一一个JVM规范中没有规定OOMError的内存区域

JVM栈

JVM栈也是每个线程所私有的内存区域,随着线程的创建而分配,线程的消亡而回收。JVM栈是描述Java方法执行的内存模型,每个java方法在执行的时候,都会创建一个栈帧(Stack Frame),其作用是用来保存java方法中的局部变量、操作栈、方法出口等信息。每一个java方法从被调用到执行完毕的过程,都伴随着对应的栈帧在JVM栈中的进栈和入栈。
JVM规范中,该区域中有两个异常状况:
-StackOverflowError:线程请求的栈深度超过了JVM栈所允许的栈深度
-OutOfMemoryError:如果JVM栈是动态可扩展的,在无法申请到足够内存时,抛出该异常

本地方法栈

本地方栈(Native Method Stack)是描述虚拟机所用到的本地方法的内存模型,与JVM栈类似,也会抛出StackOverflowError和OutOfMemoryError

Java堆

Java堆是java虚拟机所管理的最大的内存区域,我们所说的垃圾回收,就是对该区域的内存进行回收,所以也叫GC堆。java堆是所有线程所公用的内存区域,程序中的对象、数组都存放在该区域中。
从内存回收的角度来看,由于现在的收集器所采用的都是分代收集算法,所以java堆可以在进一步细分为:新生代和老年代,在新生代中,又可以划分为Eden、From survivor、To survivor等空间。从内存分配的角度来看,java堆可能划分为多个线程私有的分配缓存区。
java堆可以是物理上不连续但逻辑上要连续的内存空间,可以通过指定-Xmx(最大)、-Xms(最小)、-Xmn(新生代)等VM参数来设置java堆得大小。若有新对象申请内存空间但是java堆没有足够的内存分配时,会报OOMError。

方法区

方法区(Method Area)也是各线程共享的内存区域,用于存储被虚拟机加载的类信息、常量、静态常量、编译后的代码等数据看,其还有一个别名叫Non-Heap(非堆),与java堆加以区分。在HotSpot虚拟机上,也可叫做“永久代”(Permanent Generation),本质上不等价(HotSpot将GC收集扩展到了该区域)。
若要对该区域进行GC,主要是回收常量池以及对类型的卸载。该区域在内存满后也会抛出OOMError异常。

运行时常量池

运行时常量池(Runtime Constant Pool)是方法区内的一部分,Class文件中除了类的版本、字段、方法、接口等描述信息,还常量池表,用于存放编译期间生成的葛总常量和符号引,这部分内容在类加载后存放运行时常量池这种(class如何加载会在后面章节提及)。运行时常量池具备动态性,java语言不单单在编译期间会产生常量,在运行期间也可能将新的常量放入运行时常量池中,如开发人员用了String.intern()方法,之后的异常测试就是用该方法引起常量池抛出OOMError。

直接内存

直接内存(Direct Memory)不是虚拟机运行时数据区域的一部分,也不是JVM规范中定义的内存区域,但其也频繁被使用。自JDK1.4后,引入了基于通道(Channel)与缓冲区的I/O方式,它可以使用native函数库直接分配堆外内存,并通过java堆中的DirectByteBuffer对象操作该块内存。避免了java堆和native堆之间的数据复制,提高了性能。
本机直接内存不受java堆大小限制,但是受本机总内存大小和处理器的寻址空间限制。在动态扩展时也会抛出OOMError异常。

对象访问

OutOfMemoryError异常

之前对虚拟机运行时数据区域进行了描述,现在通过实例来验证OOM异常发生的情况,可以进一步了解jvm各内存区域的存储内容,及发生异常的情况。
测试的java版本为:

java version “1.7.0_80”
Java(TM) SE Runtime Environment (build 1.7.0_80-b15)
Java HotSpot(TM) 64-Bit Server VM (build 24.80-b11, mixed mode)

Java堆溢出示例

之前已经提到,对象实例都存储在java堆中,所以可以不断的创建对象且保证对象不被GC就可以让java堆溢出
编写如下代码清单:

/** 
* @ClassName: HeapOOM 
* @Description: Java堆内存溢出异常测试
* @author 祝佳俊(jjzhu_ncu@163.com)
* @date 2016年10月18日 下午8:30:13 
* 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());
        }
    }
}

并配置运行时的VM参数:
VM参数配置
这里-XX:UseParNewGC是指定用ParNew收集器,默认的是
参数解释: -Xms:20M:java堆最小20M
-Xmx:20M:java堆最大20M(避免动态扩展)
-XX:+HeapDumpOnOutOfMemoryError:开启该选项,可以让虚拟机在抛出OutOfMemoryError时Dump出当前内存堆转存快照
运行后,就会有如下输出:

java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid3576.hprof ...
Heap dump file created [34233497 bytes in 0.301 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Arrays.java:2760)
    at java.util.Arrays.copyOf(Arrays.java:2734)
    at java.util.ArrayList.ensureCapacity(ArrayList.java:167)
    at java.util.ArrayList.add(ArrayList.java:351)
    at study.hard.jjzhu.HeapOOM.main(HeapOOM.java:20)

JVM栈和本地方法栈溢出

之前有提过,HotSpot虚拟机并没有区分JVM和本地方法栈。在HotSpot虚拟机中可以通过制定-Xss参数来指定栈的大小。
编写如下测试代码:

/** 
 * @ClassName: JavaVMStackSOF 
 * @Description: 虚拟机栈和本地方法栈OOM测试 
 * @author 祝佳俊(jjzhu_ncu@163.com)
 * @date 2016年10月18日 下午9:09:21 
 *  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 deep:" + oom.stackLength);
            throw e;
        }
    }
}

运行后就会报StackOverflowError异常:

stack deep:1007
Exception in thread "main" java.lang.StackOverflowError
    at study.hard.jjzhu.memory.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:13)
    at study.hard.jjzhu.memory.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:15)
    at study.hard.jjzhu.memory.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:15)
    at study.hard.jjzhu.memory.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:15)

运行时常量池溢出

String.intern()方法可以向运行时常量池中添加内,所以要达到运行时常量池溢出,执行用本地方法intern()像池中不断添加字符串即可,可以同过-XX:PermSize -XX:MaxPermSize限制方法区大小。

/** 
 * @ClassName: RuntimeConstantPoolOOM 
 * @Description: 方法区和运行时常量池溢出
 * @author 祝佳俊(jjzhu_ncu@163.com)
 * @date 2016年10月18日 下午9:18:07 
 * VM Args: -XX:PermSize=10M -XX:MaxPermSize=10M
 */
public class RuntimeConstantPoolOOM {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        int i = 0 ;
        while(true){
            /* 
             * String.intern()是一个Native方法,作用是:如果字符串常量池中已经包含一个等价于此String
             * 对象的字符串,则返回常量池中这个字符串的String对象,否则,将此String对象包含的字符串添加到常量池中,
             * 并返回此String对象的引用
            */
            list.add(String.valueOf(i++).intern());
        }
    }
}
>运行结果:
Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
    at java.lang.String.intern(Native Method)
    at study.hard.jjzhu.memory.RuntimeConstantPoolOOM.main(RuntimeConstantPoolOOM.java:23)

本机直接内存溢出

本机直接内存可以通过 -XX:MaxDirectMemorySize指定内存大小,若不指定,则默认与java堆得最大值一样。

/** 
* @ClassName: DirectMemoryOOM 
* @Description:  本机直接内存溢出
* @author 祝佳俊(jjzhu_ncu@163.com)
* @date 2016年10月18日 下午9:41:40 
* VM Args: -Xmx20M -XX:MaxDirectMemorySize=20M
*/
public class DirectMemoryOOM {
    private static final int _1MB = 1024 * 1024;
    public static void main(String[] args) throws IllegalArgumentException, IllegalAccessException {
        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 study.hard.jjzhu.memory.DirectMemoryOOM.main(DirectMemoryOOM.java:21)
目录
相关文章
|
Arthas 存储 算法
深入理解JVM,包含字节码文件,内存结构,垃圾回收,类的声明周期,类加载器
JVM全称是Java Virtual Machine-Java虚拟机JVM作用:本质上是一个运行在计算机上的程序,职责是运行Java字节码文件,编译为机器码交由计算机运行类的生命周期概述:类的生命周期描述了一个类加载,使用,卸载的整个过类的生命周期阶段:类的声明周期主要分为五个阶段:加载->连接->初始化->使用->卸载,其中连接中分为三个小阶段验证->准备->解析类加载器的定义:JVM提供类加载器给Java程序去获取类和接口字节码数据类加载器的作用:类加载器接受字节码文件。
1016 55
|
Arthas 监控 Java
Arthas memory(查看 JVM 内存信息)
Arthas memory(查看 JVM 内存信息)
1032 6
|
8月前
|
存储 缓存 Java
我们来说一说 JVM 的内存模型
我是小假 期待与你的下一次相遇 ~
540 5
|
8月前
|
存储 缓存 算法
深入理解JVM《JVM内存区域详解 - 世界的基石》
Java代码从编译到执行需经javac编译为.class字节码,再由JVM加载运行。JVM内存分为线程私有(程序计数器、虚拟机栈、本地方法栈)和线程共享(堆、方法区)区域,其中堆是GC主战场,方法区在JDK 8+演变为使用本地内存的元空间,直接内存则用于提升NIO性能,但可能引发OOM。
|
10月前
|
安全 Oracle Java
JAVA高级开发必备·卓伊凡详细JDK、JRE、JVM与Java生态深度解析-形象比喻系统理解-优雅草卓伊凡
JAVA高级开发必备·卓伊凡详细JDK、JRE、JVM与Java生态深度解析-形象比喻系统理解-优雅草卓伊凡
691 0
JAVA高级开发必备·卓伊凡详细JDK、JRE、JVM与Java生态深度解析-形象比喻系统理解-优雅草卓伊凡
Java程序员必学:JVM架构完全解读
Java 虚拟机(JVM)是 Java 编程的核心,深入理解其架构对开发者意义重大。本文详细解读 JVM 架构,涵盖类加载器子系统、运行时数据区等核心组件,剖析类加载机制,包括加载阶段、双亲委派模型等内容。阐述内存管理原理,介绍垃圾回收算法与常见回收器,并结合案例讲解调优策略。还分享 JVM 性能瓶颈识别与调优方法,分析 Java 语言特性对性能的影响,给出数据结构选择、I/O 操作及并发同步处理的优化技巧,同时探讨 JVM 安全模型与错误处理机制,助力开发者提升编程能力与程序性能。
Java程序员必学:JVM架构完全解读
|
11月前
|
存储 运维 Kubernetes
Java启动参数JVM_OPTS="-Xms512m -Xmx1024m -XX:+HeapDumpOnOutOfMemoryError"
本文介绍了Java虚拟机(JVM)常用启动参数配置,包括设置初始堆内存(-Xms512m)、最大堆内存(-Xmx1024m)及内存溢出时生成堆转储文件(-XX:+HeapDumpOnOutOfMemoryError),用于性能调优与故障排查。
1169 0
|
Java 关系型数据库 MySQL
JVM深入原理(六)(二):双亲委派机制
自定义类加载器打破双亲委派机制的方法:复写ClassLoader中的loadClass方法常见问题:要加载的类名如果是以java.开头,则会抛出安全性异常加载自定义的类都会有一个共同的父类Object,需要在代码中交由父类加载器去加载自定义类加载器不手动指定parent会默认指定应用类加载两个自定义类加载器加载同一个类会被认为是两个对象,只有相同的类加载器+想通的类限定名才会被认为是一个对象。
415 0
|
11月前
|
存储
阿里云轻量应用服务器收费标准价格表:200Mbps带宽、CPU内存及存储配置详解
阿里云香港轻量应用服务器,200Mbps带宽,免备案,支持多IP及国际线路,月租25元起,年付享8.5折优惠,适用于网站、应用等多种场景。
3251 0
|
11月前
|
存储 缓存 NoSQL
内存管理基础:数据结构的存储方式
数据结构在内存中的存储方式主要包括连续存储、链式存储、索引存储和散列存储。连续存储如数组,数据元素按顺序连续存放,访问速度快但扩展性差;链式存储如链表,通过指针连接分散的节点,便于插入删除但访问效率低;索引存储通过索引表提高查找效率,常用于数据库系统;散列存储如哈希表,通过哈希函数实现快速存取,但需处理冲突。不同场景下应根据访问模式、数据规模和操作频率选择合适的存储结构,甚至结合多种方式以达到最优性能。掌握这些存储机制是构建高效程序和理解高级数据结构的基础。
1064 1