JVM- 运行时数据区

5.1 Native方法

编写线程启动类

package cn.guardwhy.jvm_02;

public class ThreadDemo {
    
    
    public static void main(String[] args) {
    
    
        new Thread(()->{
    
    

        }, "Curry").start();
    }
}

查看start方法源码

public synchronized void start() {
    
    
    /**
     * This method is not invoked for the main method thread or "system"
     * group threads created/set up by the VM. Any new functionality added
     * to this method in the future may have to also be added to the VM.
     *
     * A zero status value corresponds to state "NEW".
     */
    if (threadStatus != 0)
        throw new IllegalThreadStateException();

    /* Notify the group that this thread is about to be started
     * so that it can be added to the group's list of threads
     * and the group's unstarted count can be decremented.
     */
    group.add(this);

    boolean started = false;
    try {
    
    
        // 调用了一个start0方法
        start0();
        started = true;
    } finally {
    
    
        try {
    
    
            if (!started) {
    
    
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
    
    
            /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack 
             */
        }
    }
}

private native void start0();

Native方法总结

凡是带了native关键字的,说明 java的作用范围达不到,去调用底层C语言的库!进入本地方法栈(Native Method Stack),调用本地方法本地接口JNI(Java本地方法接口)。JNI的具体作用,拓展Java的使用,融合不同的编程语言为Java所用。

Java在诞生的时候是C/C++横行的时候,想要立足,必须有调用C、C++的程序,于是就在内存中专门开辟了一块区域处理标记为native的代码,它的具体做法是在 Native Method Stack 中登记native方法,在 ( ExecutionEngine ) 执行引擎执行的时候加载Native Libraies

5.2 程序计数器

程序计数器:Program Counter Register

每个线程都有一个程序计数器,是线程私有的,就是一个指针,指向方法区中的方法字节码(用来存储指向像一条指令的地址,也即将要执行的指令代码),在执行引擎读取下一条指令,是一个非常小的内存空间。几乎可以忽略不计。

5.3 方法区

方法区是被所有线程共享。所有字段和方法字节码,以及一些特殊的方法,如构造函数,接口代码也在此定义。简单的说,所有定义的方法的信息都保存在该区域,此区域属于共享空间。它与堆(heap)一样在线程之间共享。

JDK7 之前(永久代)

用于存储已被虚拟机加载的类信息、常量、字符串常量、类静态变量、即时编译器编译后的代码等数据。每当一个类初次被加载的时候,它的元数据都会被放到永久代中。永久代大小有限制,如果加载的类太多,很可能导致永久代内存溢出,即 java.lang.OutOfMemoryError:PermGen。

JDK8以后

JDK8 彻底将永久代移除出 HotSpot JVM,将其原有的数据迁移至 Java Heap 或 Native Heap(Metaspace),取代它的是另一个内存区域被称为元空间。

元空间(Metaspace)

元空间是方法区的在 HotSpot JVM 中的实现,方法区主要用于存储类信息、常量池、方法数据、方法代码、符号引用等,但是实例变量存在堆内存中,和方法区无关元空间的本质和永久代类似都是对 JVM 规范中方法区的实现元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存
可以通过 -XX:MetaspaceSize 和 -XX:MaxMetaspaceSize 配置内存大小。

如果Metaspace的空间占用达到了设定的最大值,那么就会触发GC来收集死亡对象和类的加载器。

代码示例

package cn.guardwhy.jvm_02;

public class Test {
    
    
    // 定义常量
    private int age;
    private String name = "guardwhy";
    public static void main(String[] args) {
    
    
        Test st = new Test();
        st.age = 10;
        st.name = "Curry";
    }
}

内存分析

5.4 栈(Stack)

栈和队列

栈:后进先出 / 先进后出

队列:先进先出

Stack 栈特点

栈管理程序运行:存储8大基本类型、对象的引用、实例的方法。栈的优势是,存取速度比堆要快,仅次于寄存器,栈数据可以共享。

思考:为什么main( )先执行,最后结束?

由栈的特点决定的。

1、栈也叫栈内存,主管Java程序的运行,是在线程创建时创建,它的生命期是跟随线程的生命期,线程结束栈内存也就释放。
2、对于栈来说不存在垃圾回收问题,只要线程一旦结束,该栈就结束,生命周期和线程一致,是线程私有的
3、方法自己调自己就会导致栈溢出(递归死循环测试)。

栈运行原理

遵循 “先进后出” / “后进先出” 的原则。

Java栈的组成元素—栈帧。栈帧是一种用于帮助虚拟机执行方法调用与方法执行的数据结构。栈帧是独立于线程的,每个线程都有自己的栈帧。封装了方法的局部变量表、动态链接信息、方法的返回地址以及操作数栈等信息。第一个方法从调用开始到执行完成,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

栈 + 堆 + 方法区的交互关系

HotSpot是使用指针的方式来访问对象,Java堆中会存放访问类元数据的地址。reference存储的是对象的地址。

5.5 堆(Heap)

jdk1.7 之前

Heap 堆,一个JVM实例只存在一个堆内存,堆内存的大小是可以调节的,类加载器读取了类文件后,需
要把类、方法,常变量放到堆内存中,保存所有引用类型的真实信息,以方便执行器执行,堆内存分为
三部分:

  • 新生区 Young Generation Space Young/New
  • 养老区 Tenure generation space Old/Tenure
  • 永久区 Permanent Space Perm

堆内存逻辑上分为三部分:新生,养老,永久(元空间 : JDK8 以后名称)

代码示例

package cn.guardwhy.jvm_02;

import java.util.Random;
/*
* OutOfMemoryError 
*/
public class Hello {
    
    
    public static void main(String[] args) {
    
    
        // 定义字符串变量
        String str = "guardwhy Learn English";
        while (true){
    
    
            str += str + new Random().nextInt(666666666) + new Random().nextInt(777777777);
        }
    }
}

执行结果

GC垃圾回收主要是在新生区和养老区,又分为 轻GC 和 重GC,如果内存不够,或者存在死循环,就会导致 java.lang.OutOfMemoryError: Java heap space

新生区

新生区是类诞生,成长,消亡的区域,一个类在这里产生,应用,最后被垃圾回收器收集,结束生命。新生区又分为两部分:伊甸区(Eden Space)和幸存者区(Survivor Space),所有的类都是在伊甸区被new出来的,幸存区有两个:0区 和 1区,当伊甸园的空间用完时,程序又需要创建对象,JVM的垃圾回收器将对伊甸园区进行垃圾回收(Minor GC)。将伊甸园中的剩余对象移动到幸存0区,若幸存0区也满了,再对该区进行垃圾回收,然后移动到1区,那如果1区也满了(这里幸存0区和1区是一个互相交替的过程)再移动到养老区,若养老区也满了,那么这个时候将产生MajorGC(Full GC),进行养老区的内存清理,若养老区执行了Full GC后发现依然无法进行对象的保存,就会产生OOM异常“OutOfMemoryError ”。如果出现 java.lang.OutOfMemoryError:java heap space异常,说明Java虚拟机的堆内存不够,原因如下

  • Java虚拟机的堆内存设置不够,可以通过参数 -Xms(初始值大小),-Xmx(最大大小)来调整。
  • 代码中创建了大量大对象,并且长时间不能被垃圾收集器收集(存在被引用)或者死循环。

HotSpot内存管理

注意: 不同对象的生命周期不同,在Java中98%的对象都是临时对象。

永久区(Perm)

永久存储区(Perm)是一个常驻内存区域,用于存放JDK自身所携带的Class,Interface的元数据,也就是说它存储的是运行环境必须的类信息,被装载进此区域的数据是不会被垃圾回收器回收掉的,关闭JVM才会释放此区域所占用的内存
如果出现 java.lang.OutOfMemoryError:PermGen space,说明是 Java虚拟机对永久代Perm内存设置不够。一般出现这种情况,都是程序启动需要加载大量的第三方jar包,Tomcat部署了太多的应用。或者大量动态反射生成的类,从而不断被加载,最终导致Perm区被占满,从而出现OOM。
注意

  • Jdk1.6之前: 有永久代,常量池1.6在方法区。
  • Jdk1.7: 有永久代,但是已经逐步 “去永久代”,常量池在堆中。
  • Jdk1.8及之后:无永久代,常量池1.8在元空间内。

三区结构


方法区(Method Area)和堆一样,是各个线程共享的内存区域,它用于存储虚拟机加载的:类信息+普通常量+静态常量+编译器编译后的代码,虽然JVM规范将方法区描述为堆的一个逻辑部分,但它却还有一个别名,叫做Non-Heap(非堆),目的就是要和堆分开。对于HotSpot虚拟机,习惯将方法区称之为 “永久代(Parmanent Gen)”,但严格本质上说两者不同,或者说使用永久代实现方法区而已,永久代是方法区(相当于是一个接口interface)的一个实现,Jdk1.7的版本中,已经将原本放在永久代的字符串常量池移走。常量池(Constant Pool)是方法区的一部分,Class文件除了有类的版本,字段,方法,接口描述信息外,还有一项信息就是常量池,这部分内容将在类加载后进入方法区的运行时常量池中存放。

猜你喜欢

转载自blog.csdn.net/hxy1625309592/article/details/114553604
今日推荐