JVM学习笔记——初识JVM

简述

"Write Once,Run Anywhere"(一次编译,处处运行)是sun宣传Java语言所提出的口号。Java语言跨平台的特性与Java虚拟机的存在密不可分。Java源代码经过编译生成.class文件字节码后再被JVM解释转化为目标机器代码,处处运行的关键与前提就是JVM。因此并非Java语言自己能够能够跨平台,而是在不一样的平台都有可让Java语言运行的环境而已。在能够运行Java虚拟机的地方都内含一个JVM操做系统,从而使JAVA提供了各类不一样平台上的虚拟机制,因而可知导出运行的关键就是JVM。java

JVM内存区域

JVM结构数据结构

线程共享区域

  • ①Java堆是Java虚拟机管理的内存中最大的一块。
    ②堆中存放对象实例,几乎全部的对象实例都在这里分配内存,因此是GC主要区域。
    ③不须要连续的内存
    ④若堆中没有内存完成实例分配,则会抛出OutOfMemoryError异常

  • 方法区
  • ①存储已被虚拟机加载的类信息(类名、访问修饰符、字段描述、方法描述等)、常量、静态变量、即时编译器编译后的代码等数据
    ②运行时常量池是方法区的一部分
    ③这区域的内存回收目标主要是针对常量池的回收和对类型的卸载
    ④不须要连续的内存,方法区gc性价比较低,gc频率远没有堆中高
    ⑤若方法区没法知足内存分配需求时,则会抛出OutOfMemoryError异常

    线程隔离区域(随线程而生,随线程而灭)

  • 虚拟机栈
  • ①生命周期与线程相同,管理Java方法执行的内存模型
    ②方法执行建立栈帧(栈帧所需内存在类结构肯定下来时就被已知,不考虑JIT优化)用于存储局部变量表、操做数栈、动态连接、方法出口等信息
    ③栈的大小决定了方法调用的深度(递归多少层次,或嵌套调用多少层其余方法)
    ④若线程请求的栈深度大于虚拟机所容许的深度,则抛出StackOverflowError异常
    ⑤若栈中无忧内存可分配,则抛出OutOfMemoryError异常

  • 本地方法栈
  • ①与虚拟机栈功能相似,但管理的不是Java方法,而是本地native方法
    ②一样也会出现StackOverflowError、OutOfMemoryError异常

  • 程序计数器
  • ①线程执行Java方法,计数器记录正在执行的虚拟机字节码指令地址;执行native方法,计数器为空(Undefined)
    ②此区域不会出现OutOfMemoryError异常

    OOM

  • 堆溢出
  • 不断建立对象oracle

    /**
         * VM Args:-Xmx10m
         */
        public static void main(String[] args) {
            List list = new ArrayList();
            while (true){
                list.add(new Object());
            }
        } 
    复制代码

    结果:
    java.lang.OutOfMemoryError: Java heap space

    ide

  • 栈溢出
  • StackOverflowError异常:

    递归调用布局

    private void test(){
            test();
        }
        public static void main(String[] args) {
            new StackTest().test();
        }
    复制代码

    结果:
    java.lang.StackOverflowError

    优化

    OutOfMemoryError异常:

    /**
     * VM Args:-Xss2M
     * @author zzm
     */
    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) {
            JavaVMStackOOM oom = new JavaVMStackOOM();
            oom.stackLeakByThread();
        }
    
    }
    复制代码

    本人运行了一下果真像书(深刻理解Java虚拟机)中所说电脑重启了,Windows系统的同窗谨慎运行spa

  • 方法区溢出
  • /**
     * VM Args:-XX:MetaspaceSize=10M -XX:MaxMetaspaceSize=10M (jdk8)
     * -XX:PermSize=10M -XX:MaxPermSize=10M(jdk7)
     * @author zzm
     */
    public class Test {
    
        static class OOMOjbect{}
    
        public static void main(String[] args) {
            while(true){
                Enhancer eh = new Enhancer();
                eh.setSuperclass(OOMOjbect.class);
                eh.setUseCache(false);
                eh.setCallback(new MethodInterceptor(){
                    @Override
                    public Object intercept(Object arg0, Method arg1,
                                            Object[] arg2, MethodProxy arg3) throws Throwable {
                        return arg3.invokeSuper(arg0, arg2);
                    }
    
                });
                eh.create();
            }
        }
    }
    复制代码

    本人用的是jdk8,输出结果:
    java.lang.OutOfMemoryError: Metaspace操作系统

    对象的内存布局

    对象在内存中布局能够分红三块区域:对象头、实例数据和对齐填充
    线程

    对象头

    对象头包括两部分信息:
    设计

  • 运行时数据:
  • 运行时数据包括哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向锁ID和偏向时间戳等,这部分数据在32位和64位虚拟机中的长度分别为32bit和64bit,官方称为"Mark Word"。Mark Word被设计成非固定的数据结构,以实如今有限空间内保存尽量多的数据。Mark Word的存储内容以下:
    存储内容 标志位 状态
    对象哈希码、对象分代年龄 01 未锁定
    指向锁记录的指针 00 轻量级锁定
    指向重量级锁的指针 10 膨胀(重量级锁定)
    空,不须要记录信息 11 GC标记
    偏向线程ID、偏向时间戳、对象分代年龄 01 可偏向

  • 类型指针:
  • 对象指向它的类元数据的指针,虚拟机经过这个指针来肯定这个对象是哪一个类的实例

    实例数据

    实例数据就是在程序代码中所定义的各类类型的字段,包括从父类继承的,这部分的存储顺序会受到虚拟机分配策略参数和字段在源码中定义顺序的影响

    对齐填充

    并非必然存在的,没有特别的含义。因为HotSpot的自动内存管理要求对象的起始地址必须是8字节的整数倍,即对象的大小必须是8字节的整数倍,对象头的数据正好是8的整数倍,因此当实例数据不够8字节整数倍时,须要经过对齐填充进行补全。

    对象访问

    句柄

    使用句柄访问的话,Java堆中将会划分出一块内存来做为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息:

    直接指针

    使用直接指针访问的话,Java堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,reference中存储的直接就是对象地址

    这两种对象访问方式各有优点:

  • 使用句柄来访问的最大好处就是reference中存储的是稳定句柄地址,在对象被移动(垃圾收集时移动对象是很是广泛的行为)时只会改变句柄中的实例数据指针,而reference自己不须要被修改
  • 使用直接指针来访问最大的好处就是速度更快,它节省了一次指针定位的时间开销,因为对象访问的在Java中很是频繁,所以这类开销积小成多也是一项很是可观的执行成本
  • oracle JDK官方默认虚拟机HotSpot采用第二中方式进行对象访问

    感谢

    《极客时间——杨晓峰Java核心技术36讲第一讲》 《深刻理解Java虚拟机》

    相关文章
    相关标签/搜索