当前位置:首页 > Java技术 > JDK1.8中JVM内存模型浅析

JDK1.8中JVM内存模型浅析

2022年09月16日 11:05:13Java技术4

1.JVM内存模型

JVM内存模型根据jdk版本不同,有部分变化,主要是jdk1.8之后,方法区移至直接内存中的元空间处。对比图如下所示:
JDK1.8中JVM内存模型浅析 _ JavaClub全栈架构师技术笔记

由上图可以看出来,版本之间的变化主要是共享线程区中的 方法区 的位置,jdk8之后转移到直接内存,而不是原先的共享线程区中。

线程私有的 虚拟机栈、本地方法栈、程序计数器;线程共有的 堆、方法区、直接内存(非运行时数据区)

1.1 虚拟机栈

虚拟机栈是线程私有的。虚拟机栈跟线程的生命周期相同,它描述的是java方法执行的内存模型,每次java方法调用的数据,都是通过栈传递的。

java内存可以粗糙的分为 堆内存(heap)和 栈内存(stack) ,其中栈内存就是指的虚拟机栈,或者说是虚拟机栈中局部变量表中的部分。实际上,虚拟机栈就是由一个个栈帧组成,而每个栈帧中都拥有:局部变量表、操作数栈、动态链接、方法出口信息。

局部变量表主要存放的是编译期间可知的各种数据类型(八大基本数据类型)、对象引用(Reference类型,不同于对象,可能是指向对象地址的指针或者与此对象位置相关的信息)

虚拟机栈可能抛出两种错误:StackOverflowError 、OutOfMemoryError

java中方法的调用实际上就是虚拟机栈出栈的操作,每一次方法调用,都有对应的栈弹出,根据每个栈帧中的 局部变量表、操作数栈等信息,执行方法。

1.2 本地方法栈

本地方法栈的工作原理跟虚拟机栈并无区别,唯一的区别就是本地方法栈面向的不是.class字节码,而是Native修饰的本地方法。

本地方法的执行过程,也是本地方法栈中栈帧的出栈过程。

同虚拟机栈一样,本地方法栈也是会抛出 StackOverflowErrorOutOfMemoryError 两种异常。

1.3 程序计数器

程序计数器是一块较小的内存空间,可看作是当前线程所执行字节码的行号指示器。字节码解释器根据这个计数器来获取当前线程需要执行的下一条指令,分支、循环、跳转、异常、线程恢复等功能都需要依赖程序计数器来完成。

此外,在线程争夺CPU时间片的时候,需要线程切换,这时候,就需要这个计数器来帮助线程恢复到正确执行的位置,每一条线程有自己的程序计数器,所以才能够保证当前程序能够正确恢复到上次执行的步骤

注意:程序计数器是唯一一个不会出现OOM错误的内存区域,它的生命周期伴随线程的创建而创建,随程序的消亡而消亡。

1.4 堆

堆是java内存管理中最大的一块内存,也是所有线程共享的一块内存,在虚拟机启动时创建。堆中主要存放的是对象实例以及数组。几乎 所有的对象实力和数组都在这一块内存中分配。

随着编译技术的发展与逃逸分析技术的进步,栈上分配、标量优化等技术使得并不是所有的对象实例都是在堆中分配的。从1.7开始已经默认开启了逃逸分析,如果方法中的对象引用没有被返回或者未被外面使用(未逃逸出去),那么对象可以直接在栈中分配。

堆也是GC垃圾回收的主要区域。垃圾回收现在主要采取的是分代垃圾回收算法。为了方便垃圾回收,java堆还进行了细分,分成:新生代、和老年代;再细致还分成Eden、from survivor、to survivor空间等,如下图:

jdk8之前:

JDK1.8中JVM内存模型浅析 _ JavaClub全栈架构师技术笔记

jdk8及之后:
JDK1.8中JVM内存模型浅析 _ JavaClub全栈架构师技术笔记

大部分情况下,对象都会在Eden区分配,再一次新生代垃圾回收之后,存活下来的对象进入 survivor区域,并且年龄增加;当年龄增加到一定岁数时(默认15岁),就会进入到 老生代。对象进入老生代的年龄阈值,可以根据JVM参数 -XX:MaxTenuringThreshold 来设置。

1.5 方法区

方法区和堆一样,是多线程共享的内存区域。用来存放已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。

方法区也被称为永久代。但是两者之间还是有区别的:方法区是JVM虚拟机规范中的定义,而永久代是这一规范的一种实现。 也就是说,只有HopSpot虚拟机中才会有永久代这个个概念。

可以通过一些参数来调整方法区内存大小:

jdk1.8之前:设置永久代大小

--XX: PermSize=N

--XX: MaxPermSize=N

jdk1.8 :设置元空间大小

-XX:MetaspaceSize=N //设置 Metaspace 的初始(和最小大小)

-XX:MaxMetaspaceSize=N //设置 Metaspace 的最大大小

之所以把永久代去掉,换成元空间,原因是元空间在直接内存中,受本机可用内存控制,虽然元空间仍然有几率会出现溢出,但是几率很小。元空间溢出时,会报错:OutOfMemoryError:MetaSpace

1.5.1 运行时常量池

运行时常量池是方法区的一部分,存放的主要是字面量和引用。

String str = "abc";

Integer i = 2;

像这样的都是存放在常量池中;

String str1 = new String("abc"); //存放在堆中,创建了两个字符串对象,还有一个在常量池

1.6 直接内存

直接内存不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,但是这部分频繁使用,也有可能抛出 OOM错误出现。

2.几种内存溢出异常

2.1Java堆溢出

public class HeapOOM {

    static class OOMObject { }

    /**
     * VM args: -Xms1024k   最小堆空间
     *          -Xmx1024k	最大堆空间  最小和最大堆空间设置相同,则表示不需要自动扩展
     *          -XX: +HeapDumpOnOutOfMemoryError 发生OOM错误时导出当前内存快照便于分析
     *          -XX: +PrintGCDetails 打印GC详情
     * @param args
     */
    public static void main(String[] args) {
        List<OOMObject> list = new ArrayList<>();
        while (true) {
            list.add(new OOMObject());
        }
    }
}

///out
......省略GCDetail信息
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at java.util.Arrays.copyOf(Arrays.java:3210)
	at java.util.Arrays.copyOf(Arrays.java:3181)
	at java.util.ArrayList.grow(ArrayList.java:265)
	at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:239)
	at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:231)
	at java.util.ArrayList.add(ArrayList.java:462)
	at com.lavendor.learn.java.basic.jvm.HeapOOM.main(HeapOOM.java:24)

注意: -Xms 是表示设置堆空间最小容量 -Xmx设置堆空间最大容量 这两个值设置一样,则表示堆空间不会自动扩展

2.2虚拟机栈和本地方法栈溢出

public class JavaVMStackSOF {

    private int stackLength = 1;

    //不断循坏入栈
    public void stackLeak() {
        stackLength++;
        stackLeak();
    }

    //使用多线程来使虚拟机栈抛出OOM
    private void dontStop() {
        while (true) {
        }
    }
    public void stackLeakByThread() {
        while (true) {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    dontStop();
                }
            });
            thread.start();
        }
    }

    /**
     * VM args: -XX:+PrintGCDetails
     *
     * @param args
     */
    public static void main(String[] args) throws Throwable {
        JavaVMStackSOF oom = new JavaVMStackSOF();
        try {
            //VM args: -Xss128k 设置栈空间容量,抛出栈溢出异常
            oom.stackLeak();  // StackOverflowError
            
            //VM args: -Xss128m 这个时候可以设置得稍微大些,让线程的虚拟机栈更大,从而不需要多少线程就能够把内			   // 存撑满
            //oom.stackLeakByThread(); //OutOfMemoryError
        } catch (Throwable tx) {
            System.out.println("stack length: " + oom.stackLength);
            throw tx;
        }
    }
}

注意: -Xss 设置栈大小

单线程情况下无论是栈帧太大还是虚拟机栈容量太小,都只是抛出 StackOverflowError ,不是 OutOfMemoryError

在多线程情况下,会抛出OutOfMemoryError ,但是看起来并不像是虚拟机栈满而导致,更像是多个线程在分配虚拟机栈空间时互相争夺资源,导致内存空间不足,从而抛出OOM异常。从这个角度来说,这时候把栈空间设置很大,更加容易出现OOM异常。

在Windows环境下不要轻易尝试多线程抛出OOM异常,因为Windows环境Java的多线程是映射到操作系统的,可能会导致系统死机

2.3方法区和运行时常量溢出

JDK1.7及以上版本中,逐渐会去掉“永久代”这个概念,方法区和运行时常量都会放置到堆上,-XX:PermSize-XX:MaxPermSize参数也不再支持。

这个模块溢出跟 Java堆溢出相似,通过控制堆大小可以看到OOM溢出。

public class MethodAreaOOM {

    /** 使用cglib动态代理来无限生成类,去填满方法区
     * cglib生成的类不太容易被GC回收
     * VM args: -Xms10m -Xmx10m
     * @param args
     */
    public static void main(String[] args) {

        while (true){
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(OOMObject.class);
            enhancer.setUseCache(false);
            enhancer.setCallback(new MethodInterceptor() {
                @Override
                public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                    return method.invoke(o,objects);
                }
            });
            enhancer.create();
        }
    }

    static class OOMObject{}
}

2.4本地直接内存溢出

public class DirectMemoryOOM {

    public static final int SINGLE_MB = 1024 * 1024;

    /**
     * VM args:-XX:MaxDirectMemorySize=10m
     * @param args
     */
    public static void main(String[] args) throws IllegalAccessException {
        Field unsafeField = Unsafe.class.getDeclaredFields()[0];
        unsafeField.setAccessible(true);
        Unsafe unsafe = (Unsafe) unsafeField.get(null);
        while (true){
            unsafe.allocateMemory(SINGLE_MB);
        }
    }
}

-XX: MaxDirectMemorySize指定直接内存最大容量,不指定,则默认跟堆最大容量(-Xmx)相同

由直接内存导致的OOM,一个很明显的特征是Heap Dump文件不会有明显的异常,如果发现导出的文件很小,而且程序中直接或者间接的使用了NIO,那么可以考虑直接内存这方面的问题。

3.Java垃圾回收

程序计数器、虚拟机栈、本地方法栈这些都是线程私有的,随线程而生,消亡而灭。栈中的栈帧随着方法的进入和退出有条不紊的执行出栈和入栈操作。每一个栈帧中分配多少内存基本上是在类结构确定下来之后就会确定好的,因此,这几个地方的内存是确定的,不需要过多的考虑内存回收的问题。

Java堆和方法区则不同,他们是线程共享的,程序在运行中需要创建多少对象,分配多少内存,这些都是动态的,所以这部分需要来及回收,GC关注的也是这部分内存,后面讨论的回收也只是指这部分内存。

3.1 判断对象可回收

1.引用计数算法

大体思路: 给对象一个引用计数器,有引用时+1, 无引用时-1,当引用计数器为0,则表示可以回收。

弊端: 当对象之前互相引用,并且已无意义时,不能回收。

目前虚拟机 并不是 采用这种算法。

2.可达性分析算法

大体思路: 通过一系列称为“GC Root”的对象作为起点,从起点开始向下搜索,搜索通过的路径称为引用链(reference chain),当一个对象从GC Root开始没有引用链连接,即不可达时(如下图 object5 object6 object7三个对象),他们将会被认为是可以回收的。
JDK1.8中JVM内存模型浅析 _ JavaClub全栈架构师技术笔记

这是目前虚拟机中,判断对象是否可以被回收的主流实现方法。

有以下几种对象可以作为GC Root:

  • 虚拟机栈中(栈帧中的本地变量表)引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈(native方法)中引用的对象

3.finalize()方法拯救快要被GC回收的对象

/**
 * @author yanghao
 * @date 2021-08-10
 * @description
 * 这个例子演示了两点:
 * 1.对象可以在被GC时自我拯救
 * 2.这种自救机会只有一次,因为一个对象的finalize()方法系统最多只会调用一次
 */
public class FinalizeEscape {

    public static FinalizeEscape SAVE_HOOK = null;

    public void isAlive(){
        System.out.println("yes, I'm still alive :)");
    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("finalize method executed");
        //把当前对象指给再关联起来,拯救自己一次
        SAVE_HOOK = this;
    }

    public static void main(String[] args) throws InterruptedException {

        SAVE_HOOK = new FinalizeEscape();
        //对象第一次拯救自己
        //SAVE_HOOK = null;
        System.gc();
        //finalize()方法优先级很低,等待一段时间
        Thread.sleep(500);
        if(SAVE_HOOK != null){
            SAVE_HOOK.isAlive();
        }else {
            System.out.println("no, i am dead :(");
        }

        //下面这段代码与上面完全相同,但是却失败了
        SAVE_HOOK = null;
        System.gc();
        Thread.sleep(500);
        if(SAVE_HOOK != null){
            SAVE_HOOK.isAlive();
        }else {
            System.out.println("no, i am dead :(");
        }
    }
}

3.2 垃圾回收算法及几种实现

1.算法

目前主流的垃圾回收算法是 分代回收算法 ,所以我们堆空间分成新生代、老年代是有必要,这可以使得垃圾回收的时候根据不同年代特点选择合适的垃圾回收算法。
JDK1.8中JVM内存模型浅析 _ JavaClub全栈架构师技术笔记

  • MinorGC 发生在新生代的垃圾回收动作,MinorGC非常频繁,执行速度也非常快。
  • MajorGC/FullGc 发生在老年代的垃圾回收动作,MajorGC的出现有可能会伴随着至少一次MinorGCMajorGC执行速度会比MinorGC慢10倍以上。

1)分代收集算法 根据对象存活周期,分成不同的分区,java中常见就是分成新生代、老年代,然后根据不同年龄代的特点分别回收。

对象分代策略:

  • 对象优先在新生代分配
  • 大对象直接分配到老年代
  • 长期存活的对象直接分配到老年代

2)标记-清除算法 分成两部分:标记清除 。首先标记出所有不需要回收的对象,标记完成后,把没有标记的对象全部清除掉。这个算法会有效率问题;以及空间不连续,内存碎片化的问题。

3)标记-整理算法标记-清除算法 类似,区别就是把存活对象,全部移动到一端,然后再把标记的全部清除掉,这样可以使得清理对象后,内存连成一块,不至于太碎片化。

4)复制算法 把内存分成大小相同的两块,每次使用一块。当这一块内存使用完后,就把还存活的对象复制到另一块去,然后把这块内存全部清除掉。这样效率增加了,但是太耗费内存。

当前的主流虚拟机中,分区成Eden、From Survivor、To Survivor等空间分区,都是采用的此方法:在Eden、From Survivor区中新的对象如果还活着,就会复制到 To Survivor,然后对Eden、Survivor区进行垃圾回收

2.实现

  • Serial GC收集器 Serial Young GC + Serial Old GC (实际上是Full GC)

    是最基本,历史最久的收集器,是单线程的收集器。垃圾回收的线程是单线程,并且在回收垃圾时,需要其余所有的用户线程暂停--Stop the world,回收线程完成之后再恢复用户线程。

  • ParNew GC收集器

    ParNew就是Serial GC的多线程版本,把垃圾回收的单线程改成多线程,其余不变。这是真正意义上的第一款并发(concurrent)收集器

    并发和并行:

    并发(Concurrent):指用户线程与垃圾回收线程同时执行

    并行(Parallel):值多条垃圾回收线程并行工作,但是用户线程等待状态

  • Parallel GC收集器 Parallel Young GC + 非并行的PS MarkSweepGC/并行的Parallel Old GC(这两个实际上也是全局范围内的Full GC) (JDK1.8默认GC)

    并行收集器组合 Parallel Scavenge + Parallel Old 年轻代采用复制算法,老年代采用标记-整理,在回收的同时还会对内存进行压缩。

  • CMS收集器 ParNew(Young) GC + CMS(Old)GC + Full GC for CMS算法
    JDK1.8中JVM内存模型浅析 _ JavaClub全栈架构师技术笔记

  • G1 GC收集器 Young GC + mixed GC(新生代GC ,再加上部分老生代) + G1 GC for CMS算法

参考 https://www.zhihu.com/question/41922036

3.3 垃圾回收器参数总结

1.GC日志理解

在jvm中添加参数 :-XX:+PrintGCDetials 即可打印出GC日志,内容和含义如下:

[GC (Allocation Failure) [PSYoungGen: 3931K->824K(9216K)] 8035K->8000K(19456K), 0.0023478 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Ergonomics) [PSYoungGen: 824K->0K(9216K)] [ParOldGen: 7176K->7888K(10240K)] 8000K->7888K(19456K), [Metaspace: 3471K->3471K(1056768K)], 0.0065113 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 

[GC (Allocation Failure) [PSYoungGen: 6617K->859K(9216K)] 6617K->4963K(19456K), 0.0033870 secs] 
|----------A------------||-----B----| |--------C--------|  |--------D---------|  |-------------
[Times: user=0.00 sys=0.00, real=0.00 secs] 
-----------E-----------------------------|

段A:发生GC的停顿类型,出现Full GC表示发生了Stop-The-World

段B:发生GC的内存区域。此处是使用 Parallel Scavenge 回收器,所以新生区为:PSYoungGen

段C:GC前该内存区域已使用容量->GC后该内存区域已使用容量(该内存区域总容量)

段D:GC前Java堆已使用容量->GC后Java堆已使用容量(Java堆总容量)

段E:此次GC所消耗时间

2.GC参数

参数 描述
UseSerialGC 虚拟机运行在client模式下的默认值,设置此参数后,使用 Serial+Serial Old的收集器组合回收
UseParNewGC 设置此参数后,使用 ParNew+Serial Old的收集器组合回收
UseConMarkSweepGC 设置此参数后,使用ParNew+CMS+Serial Old收集器组合回收,Serial Old将作为CMS回收失败Concurrent Mode Failure的后备收集器回收
UseParallelGC 虚拟机运行在Server模式下的默认值,设置此参数后,使用 Parallel Scavenge+Serial Old(PS MarkSweep)组合进行内存回收
UseParallelOldGC 设置此参数后,使用 Parallel Scavenge+Parallel Old组合进行内存回收
SurvivorRatio 新生代Eden区:Survivor区,默认是8,即表示Eden:Survivor=8:1
PretenureSizeThreshold 直接晋升到老年代的对象大小,大于这个参数的对象,将直接放在老年代
MaxTenuringThreshold 晋升到老年代的对象的年龄。每个对象在坚持过一次Minor GC之后,年龄+1,当对象的年龄超过这个参数时,放入老年代
UseAdaptiveSizePolicy 动态调整Java堆中各个区域的大小及进入老年代的年龄
ParallelGCThreads 设置并行GC线程数,默认4个。视操作系统而定
GCTimeRatio GC时间占总时间比值,默认99,即允许1%GC时间。仅在使用 Parallel Scavenge收集器时有效
MaxGCPauseMillis 设置GC最大停顿时间,仅在使用 Parallel Scavenge收集器时有效

4.类加载器

4.1类加载过程

java中类加载过程大致分为 加载---->连接---->初始化 ,其中连接过程由分为 验证---->准备---->解析
JDK1.8中JVM内存模型浅析 _ JavaClub全栈架构师技术笔记

类的加载都是由加载器来完成的,加载就是指把.class字节码文件加载到JVM当中去执行。

4.2 类加载器

我们常见的有三种类加载器,除了BootstrapClassLoader 另外两种加载器都是继承自java.lang.ClassLoader

  • AppClassLoader 应用加载器,负责加载当前应用classpath下面的jar和类,包括我们自己正在开发的类。

  • ExtClassLoaderAppClassLoader 的父加载器,主要负责加载 %JRE_HOME/lib/ext% 目录下的jar包和类,或者被 java.ext.dirs 系统变量指定的目录下得jar包。

  • BootstrapClassLoader 是终极的加载器,由java底层实现,是 ExtClassLoader 的父加载器,此加载器没有父加载器。此加载器主要负责加载%JAVA_HOME/lib%下的jar包和类,或者加载 -Xbootclasspath变量指定的路径下的jar包和类。

4.3双亲委派模式加载

每一个类都会有对应的加载器加载,java默认使用的类加载模式是双亲委派模式 。即在类加载的时候,会首先判断类是否被加载,如果被加载则直接返回被加载的类,如果没有被加载,就开始尝试加载。加载的时候会首先 委派 父类加载器加载,因此所有的类加载实际上都会传送到 BootstrapClassLoader 加载器中,如果父类不能加载,则由自己加载。如果父类加载器返回null,则会启动 BootstrapClassLoader来作为父类加载。
JDK1.8中JVM内存模型浅析 _ JavaClub全栈架构师技术笔记

双亲委派模式加载的好处

保证了java稳定运行,可以避免类的重复加载,保护了java内部核心API不被篡改。如果不使用这种模式,那用户也能够被允许编写 java.lang.Class这样的类,就会跟java自带的类冲突,造成混乱。

作者:lavendor
来源链接:https://www.cnblogs.com/lavendor/p/15567004.html

版权声明:
1、JavaClub(https://www.javaclub.cn)以学习交流为目的,由作者投稿、网友推荐和小编整理收藏优秀的IT技术及相关内容,包括但不限于文字、图片、音频、视频、软件、程序等,其均来自互联网,本站不享有版权,版权归原作者所有。

2、本站提供的内容仅用于个人学习、研究或欣赏,以及其他非商业性或非盈利性用途,但同时应遵守著作权法及其他相关法律的规定,不得侵犯相关权利人及本网站的合法权利。
3、本网站内容原作者如不愿意在本网站刊登内容,请及时通知本站(javaclubcn@163.com),我们将第一时间核实后及时予以删除。


本文链接:https://www.javaclub.cn/java/41736.html

分享给朋友:

“JDK1.8中JVM内存模型浅析” 的相关文章

JVM中有哪些垃圾收集器?

写在前面 本文隶属于专栏《100个问题搞定Java虚拟机》,该专栏为笔者原创,引用请注明来源,不足和错误之处请在评论区帮忙指出,谢谢! 本专栏目录结构和文献引用请见100个问题搞定Java虚拟机 解答 新生代垃圾...

JVM学习1--数字存储,内存模型,指令重排

JVM学习1--数字存储,内存模型,指令重排

一、数字在计算机中的存储   整数:以补码形式存储。     补码:正数的补码是自身,负数的补码是取反码加1(取反码时符号位还是1)   浮点型:以float类型表示            注意一下,这八位指数实际上是(127...

深入理解JVM(1)——JVM内存模型

Java虚拟机的内存空间分为五个部分,分别是: 程序计数器; Java虚拟机栈 本地方法栈 堆 方法区 接下来对这五部分分别进行详细的介绍 1、程序计数器:   a)什么是程序计数器:程序计数器是内存中的一个很小...

JVM - 双亲委派

JVM - 双亲委派

# JVM - 双亲委派 JDK版本:1.8 # 1、双亲委派机制 Java虚拟机对于class文件采用的加载策略是按需加载。也就是当需要使用该类时才会将该类的.class文件加载到内存中生成Class对象。并且加载某...

深入理解JVM—JVM内存模型

深入理解JVM—JVM内存模型

原文地址:http://yhjhappy234.blog.163.com/blog/static/316328322011101723933875/?suggestedreading&wumii 我们知道,计算机CPU和内存的交互是最频繁的,内存是我们的高速缓存区,用户磁...

jvm性能调优实战 - 31从测试到上线

jvm性能调优实战 - 31从测试到上线

文章目录 Pre 开发好系统之后的预估性优化 系统压测时的JVM优化 对线上系统进行JVM监控 Pre 前面两篇文章,已经给大家介绍...

java jvm设置

2. 如何分配JVM内存设置: (1)当在命令提示符下启动并使用JVM时(只对当前运行的类Test生效):     java -Xmx128m -Xms64m -Xmn32m -Xss16m Test     (2)当在集...

jvm中关于slot的理解

jvm中关于slot的理解

jvm中关于slot的理解 参数值的存放总是在局部变量数组的index0开始,到数组长度-1的索引结束。 局部变量表,最基本的存储单元是slot(变量槽) 局部变量表中存放编译期可知的各种基本数据类型(8种),引...

深入底层之JVM的运行原理和性能调优

深入底层之JVM的运行原理和性能调优

学海无涯  不进则退 如存在问题 或有更好建议 请联系 作者QQ:2940500   前言: 了解程序的运行原理 要先学会看字节码文件 然后这样才能真正的去看程序是怎么运行的  ,不是说在开发工具 里deb...

二: Jvm内存模型

二: Jvm内存模型

为什么jvm要有内存模型   在  上一章节  我们清楚代码的运行流程之后,那么下面一段代码我们就可以知道: 1. main线程启动,main()方法的栈帧压入main线程的虚拟机栈2. web()方法的栈帧也压入main线程的虚拟机栈3. web()栈...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。