JVM原理及调优(一)----JVM的基本结构

一、了解JVM

1 JVM的基本结构
java虚拟机由五部分组成分别为:Java堆(Heap)、方法区(Method)、虚拟机栈(VM Stack)、本地方法区(Native MethodcStack)和程序计数器(Program Counter Register)。
在日常工作中我们经常将JVM 粗略概括为堆栈(Java堆和方法区),但事实上其组成上远比这复杂。(PS:JDk1.8的JVM)
在这里插入图片描述
1.1Java堆
java堆是JVM管理的内存中最大的一块,该区域是所有内存共享的,在虚拟机启动时被创建。The heap is the runtime data area from which memory for all class instances and arrays is allocated。(java 虚拟机规范中原文) 此区域是用来存储各类生成的对象及数组等,在JVM8以后把运行时的常量池、静态变量也移到这里存储。
java堆可以细分为年轻代(Young Generation)、老年代(old Gen),而年轻代又可以分为Eden区、From Survivor、To Survivor 比例为 8:1:1。根据java虚拟机规范中的规定,Java堆可以处于物理上不连续的内存空间中,只要逻辑上连续就可以,就像磁盘空间。在实现上,java堆既可以固定大小,又可以拓展,当前的java 虚拟机都是按照可拓展来实现的(通知设置-Xmx和-Xms),如果堆中有没有完成内存分配的实例,并且堆也无法再拓展,就会抛出OOM(OutOfMemoryError),因此java堆也是垃圾收集管理的主要区域。
下面我们就通过代码模拟java堆溢出:

import java.util.ArrayList;
import java.util.List;

/**
 * 测试java堆溢出
 * VM:-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堆用于存储对象实例,只要不断创建对象,并且保证GC Roots到对象之前有可达路径来避免垃圾回收机制清除这些对象,那么对象数量达到最大堆容量就会产生内存溢出。
java堆内存溢出是实际应用中常见的内存溢出情况。要解决这个区域的异常,一般先通过工具分析(比如Eclipse Memory Analzer)对Dump出来的文件进行分析,确认是内存泄露还是内存溢出。
如果是内存泄露,可进一步通过工具查看泄露对象到GC Roots的引用链。查明对象是通过怎么样的路径与GC roots相关联并导致垃圾回收无法自动回收它们,定位到相关联的代码。
如果是内存溢出,那就应当调整虚拟机的内存大小。

1.2 方法区
在1.8之前方法区中分为也被称为“永久代”,用于存储类信息、常量、静态变量、即时编译后的代码等数据,内存分配上也是使用jvm 中的内存。到了1.8之后,将常量、静态变量的存储移至java堆,使用‘元空间(MetaSpace)’的实现,不在共用JVM内存,直接使用系统内存,该区域也是线程共享的。
下面通过一段代码来看看方法区的内存溢出

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * 方法区内存溢出
 * VM: -XX:MaxMetaspaceSize=5M
 *
 */
public class MetaOOM {
    static class OOMObject{
    }
    public static void main(String[] args) {
        while (true){
            Enhancer enhancer=new Enhancer();
            enhancer.setSuperclass(OOMObject.class);
            enhancer.setUseCache(false);
            enhancer.setCallback(new MethodInterceptor() {
                public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                    return methodProxy.invokeSuper(o,objects);
                }
            });
            enhancer.create();
        }
    }
}

其运行结果为
在这里插入图片描述
该例子中使用运行时产生大量的类来填满方法区,直到溢出。该区域可以通过 -XX:MaxMetaspaceSize 设置大小,在不指定大小的情况小,虚拟机会耗尽所有可用的系统内存。
在实际应用中,如spring、Hibernate等在对类进行增强时都会使用到CGlib这类字节码技术,增强的类越多,就需要越大的方法区来保证动态生成的class可以载入内存。除了上述情况会导致方法区内存溢出,还有使用大量jsp或者动态产生jsp文件的应该(因为jsp第一次运行时需要编译为java类)。

1.3 java虚拟机栈
java虚拟机栈是线程私有的,它的生命周期与线程相同。每个方法被执行的时候都会同时创建一个栈帧用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈的入栈和出栈过程。
局部变量表存放了编译期可知的各种基本类型(boolean、byte、char、int、long、double、float)、对象引用和returnAddress类型。
当线程请求的栈深度大于虚拟机所允许的深度时,将抛出StackOverflowError异常;当拓展时无法申请到足够的内存时会抛出OOM异常。

/**
 * 测试虚拟机栈溢出
 * VM -Xss128k
 */
public class StackOFE {

    private int stacklen=1;
    public void stackleak(){
        stacklen++;
        stackleak();
    }

    public static void main(String[] args)throws Throwable {
        StackOFE stackOFE=new StackOFE();
        try{
            stackOFE.stackleak();
        }catch (Throwable e){
            System.out.println("stack length:" +stackOFE.stacklen);
            throw e;
        }
    }
}

运行结果为:
在这里插入图片描述
1.4本地方法栈
Java虚拟机栈是为执行java方法服务,而本地方法栈是为虚拟机以系统的交互提供服务接口。它为我们提供了一个非常简洁的接口,而且我们无需去了解java应用之外的繁琐的细节 ,一个Native Method就是一个java调用非java代码的接口。方法对应的实现不是在当前文件,而是在用其他语言(如C和C++)实现的文件中。Java语言本身不能对操作系统底层进行访问和操作,但是可以通过JNI接口调用其他语言来实现对底层的访问。

1.5程序计数器
程序计数器的作用是当前线程所执行的字节码的行号指示器,为了能让线程切换后恢复到正确的执行位置。每个线程都有一个独立的程序计数器,各个线程之间的计数器互不影响。

1.6运行时常量池
运行时常量池就是class文件在编译后除了存储一些类的版本、字段、方法、接口等元数据信息外,还有部分信息是常量池,用于存储编译期生成的各种字面量和符号引用。这类数据变量的好处简单来说就是如果堆区中已经存在一个数据变量,即使再创建一个这样的变量,那么JVM将会直接指向已经创建好的数据,而不会再分配内存区域,这样一方面加快数据的创建,另一方面节省内存空间!

PS:本文参考《深入理解JAVA虚拟机–JVM 高级特性与最佳实践》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Resean0223

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值