JVM(二)native\程序计数器\方法区\栈\堆\新生区养老区永久区

JVM(二)native\程序计数器\方法区\栈\堆

学习视频链接,以示尊重:https://www.bilibili.com/video/BV1iJ411d7jS?p=4



一、native关键字

先举个例子:看一看线程类的start()方法:

public static void main(String[] args) {
    new Thread().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();
        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();

显然,其中start()方法主体部分调用了start0()方法,而start0()方法是:private native void start0(); 其中,就用到了native关键字。

凡是带了native关键字,说明java的作用范围无法达到,它会去调用底层的c语言库。

  • 会进入本地方法栈,然后调用本地方法接口(即JNI,java native interface)。
    • JNI作用:扩展Java的使用,融合不同的编程语言为Java所用。

native背景:

Java诞生的时候c\c++横行一时,想要立足必须要有调用c\c++的程序。因此,Java在内存区域开辟了一块标记区域:本地方法栈。然后登记native方法,在最终执行引擎执行的时候,通过JNI加载本地方法库中的方法。

native应用:

例如Java程序驱动打印机、管理系统,在企业级应用中较为少见。


二、PC寄存器

程序计数器:Program Counter Register

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


三、方法区

方法区:Method Area

方法区是所有线程共享的,所有字段和方法字节码以及一些特殊方法,如构造函数、接口代码也在此定义。简单说,所有定义的方法的信息都保存在该区域,这个区域属于共享区间。

静态变量(static)、常量(final)、类信息(Class、构造方法、接口定义)、运行时的常量池也存在方法区中,但是实例变量存储在堆内存中,和方法区无关。


四、栈

栈和队列:

  • 栈:先进后出(FILO)、后进先出
  • 队列:先进先出(FIFO)

栈:栈内存的生命周期和线程同步。

  • 线程结束,栈内存也就会释放(栈空)
  • 对于栈来说,不存在垃圾回收问题,一旦线程结束,栈就结束
  • 栈是线程级的,每个线程拥有独自的栈,相互隔离。而堆是共享的

栈:8大基本类型 + 对象引用 + 实例的方法索引

  • 基本类型:Boolean、int、char、float、long、double、byte、short

栈运行原理:栈帧

在这里插入图片描述

堆、栈、方法区:

在这里插入图片描述

图片来自:https://blog.csdn.net/weixin_42609363/article/details/115265943(仅自我学习用,无其他用途)


五、三种JVM
  • Sun公司:HotSpot(我们常用的)
  • BEA:JRockit
  • IBM:j9vm

查看自己用的JVM是哪个:

在这里插入图片描述


六、堆

堆(Heap):一个JVM只有一个堆内存,堆内存的大小是可以调整的。

类加载器读取类文件后,一般把什么放在堆中:类、方法、常量、变量,保存所有引用类型的真实对象

堆内存中还要细分为三个区域:

  • 新生区(伊甸园区):young/new
  • 养老区:old
  • 永久区:perm

在这里插入图片描述

图片来自:https://blog.csdn.net/weixin_42609363/article/details/115265943(仅自我学习用,无其他用途)

Tips:

  • 幸存者0区和幸存者1区是动态的,不停更换的

  • GC垃圾回收,主要在伊甸园区和养老区

    • GC垃圾回收分为轻量级和重量级:轻量级针对新生区、重量级针对养老区
  • 假设内存满了,堆内存不够,会报java.lang.OutOfMemoryError:Java heap space(OOM)

  • 在JDK8之后,永久存储区有另外一个名字:元空间


七、新生区、养老区、永久区

新生区是一个类诞生、成长甚至死亡的位置。分为伊甸园区和幸存者0区、幸存者1区:

  • 所有的对象都在伊甸园区new出,如果没有被垃圾回收,则存入幸存者区
  • 幸存者区也没有被回收掉的就会进入养老区(层层存活)

在这里插入图片描述

经过研究,99%的对象都是临时对象,也就是在新生区会被轻GC回收掉。因此,很少遇到OOM错误。

永久区:

这个区域常驻内存,用来存放JDK自身携带的Class对象,Interface元数据,存储的是Java运行时的一些环境或类信息,这个区域不存在垃圾回收。关闭虚拟机的时候才会释放这个区域的内存。

当一个启动类加载了大量的第三方jar包、Tomcat部署了太多的应用、大量动态生成的反射类不断的被加载,直到内存满,就会出现OOM。

  • JDK1.6之前:永久代(Perm),常量池在方法区中
  • JDK1.7:永久代(Perm),但是慢慢的退化了,提出了去永久代的概念,常量池在堆中
  • JDK1.8之后:无永久代(Perm),常量池在元空间

在这里插入图片描述

硬件方面:

默认情况下JVM分配的总内存是电脑内存的1/4,但实际初始化的内存是1/64。

出现OOM错误时:

  • 尝试扩大堆内存看结果:-Xms1024m -Xmx1024m -XX:+PrintGCDetails
  • 分析内存,看一下哪个地方出现了问题(死循环、递归调用等等)(使用专业工具)
    • 能够看到代码第几行出错:内存快照分析工具:MAT、Jprofiler
      • MAT、Jprofiler作用:
        • 分析Dump内存文件,快速定位内存泄漏
        • 获得堆中的数据
        • 获得大的对象
    • DeBug,一行一行分析:企业级项目不现实
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值