文章目录
- 1、JVM
- 2、程序计数器
- 3、堆
- 4、栈
- 4.1 垃圾回收是否涉及栈内存
- 4.2 栈内存分配越大越好吗
- 4.3 方法内的局部变量是否线程安全吗
- 4.4 栈内存溢出的情况
- 4.5 堆和栈的区别是什么
- 5、方法区
- 5.1 常量池
- 5.2 运行时常量池
- 6、直接内存
1、JVM
Java源码编译成class字节码后,JVM负责class字节码的处理。不同操作系统下的JVM,将字节码处理成对应操作系统下的二进制文件,从而实现一次编译,到处运行。能到处运行,是因为不同操作系统有不同的JVM,也即编译后的class字节码和操作系统之间,隔着一个JVM在干活儿。
简单的理解,JVM就是Java二进制字节码的运行环境,是JDK包下包含的一些代码,像一个虚拟的计算机一样处理着class字节码。
JVM有两个最大的亮点:
- 处理class字节码,实现一次编译,到处运行
- 自动内存管理,垃圾回收机制,创建的对象,用完后不用手动回收
JVM的组成:
2、程序计数器
线程私有的,每个线程各有一份,内部保存的字节码的行号。用于记录正在执行的字节码指令的地址。
如上,线程1 执行字节码从第0行到第10行后,CPU时间片用完,线程1的程序计数器记录下当前行号。CPU的时间片分给了线程2 ,线程2执行到第9行后,CPU时间片用完。线程1再次抢到时间片,继续执行,此时,根据线程1的程序计数器,CPU就知道该从第10行继续往下执行。
3、堆
- 堆区,线程共享
- 保存着创建出来的对象
- use、total、max,use == max 后,无法再分配空间,堆内存溢出
堆中有年轻代和老年代。年轻代有三部分,伊甸园区和两块大小相同的幸存者区。根据JVM策略,新创建的对象在伊甸园区,伊甸园区满了以后,触发Young GC,GC后或者的对象,复制到幸存者区,后面每GC一次,活着的对象年龄加一(对象头里存着年龄),对象GC年龄到达阈值(如15)后,晋升到老年代。
元空间里存类的信息、静态变量、常量、编译后的字节码信息(InstanceKlass对象(c++))。对JDK7和8,其位置变化:
- JDK7及以前,方法区在堆区的永久代空间里
- JDK8及以后,永久代被移除,用元空间代替,方法区在元空间,而元空间在操作系统的直接内存里,理论上可以一直分配
因为永久代/方法区或者说后来的元空间,存储的主要是一些类信息和常量,需求开发,加载的类越来越多,这个空间不可控,移除永久代,在本地内存放个元空间,可以防止OOM
4、栈
栈,即每个线程运行时需要的内存空间,保存着该线程方法调用的基本数据,先进后出,其中,每一个调用的方法用一个叫栈帧的东西存。
4.1 垃圾回收是否涉及栈内存
垃圾回收处理的主要是堆内存,对于栈内存,栈帧弹栈后,内存就会释放
4.2 栈内存分配越大越好吗
默认1024k,栈帧过大会导致线程数变少,机器总内存为512m,目前能活动的线程数则为512个,如果把内存改为2048k,则可活动的线程数上限在栈内存方面就会减半
4.3 方法内的局部变量是否线程安全吗
在方法的作用范围之内,是线程安全的。出了方法,被怎么使用就不一定了。如下:
每个线程过来,都会在自己的栈里创建一个m1方法对应的栈帧,栈帧里存着局部变量sb,因此线程安全。
对m2方法来说,局部变量sb是传过来的,那可能就有线程安全问题,如上面main线程中在操作sb,新开的一个线程也在操作sb。同理,m3方法,将局部变量sb return,后面可能被一个成员变量接收,但后面本质上也不关局部变量的事了
4.4 栈内存溢出的情况
- 栈帧过多导致栈内存溢出,如递归调用
public static void m1() {
m1();
}
- 栈帧过大导致栈内存溢出
4.5 堆和栈的区别是什么
- 栈内存一般会用来存储局部变量和方法调用,但堆内存是用来存储Java对象和数组的
- 堆会GC垃圾回收,而栈不会
- 栈内存是线程私有的,而堆内存是线程共有的
- 两者异常错误不同,但如果栈内存或者堆内存不足都会抛出异常,栈空间不足:Java.ang.StackOverFlowError,堆空间不足:java.lang.OutOfMemoryError.
5、方法区
- 方法区和堆一样,各个线程共享
- 主要存储类的信息(InstanceKclass对象)、运行时常量池
- 虚拟机启动的时候创建,虚拟机关闭的时候释放
- 方法区的内存空间不够时,抛异常OutOfMemoryError:Metaspace
- 方法区是一个概念,不同版本的JDK有不同的实现,对JDK7来说,永久代是其对方法区的落地实现(且此时永久代在堆区),对JDK8来说,则给方法区换了一种实现:元空间(元空间在本地内存)
5.1 常量池
javap查看一个类是字节码的结构信息:可以看到类的基本信息、常量池、方法的定义:
常量池可以看作是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等信息
5.2 运行时常量池
上面提到,常量池是class文件中的,当这个类被加载,它的常量池信息就会放入到运行时常量池,并发里面的符号地址变为真实地址
常量池和运行时常量池的区别:
【区别】
6、直接内存
直接内存(操作系统分给JVM进程的内存之外的内存),不属于JVM内存,不由JVM进行管理。在进行NIO操作时,用于数据缓冲区,其分配回收成本高,但读写性能好。常规IO复制文件流程:
NIO复制文件流程:这块直接内存系统和Java代码都可以直接访问,少了一次缓冲区的复制操作
直接内存主要通过 java.nio 包下的 ByteBuffer 类来进行分配和使用。
ByteBuffer.allocateDirect(int capacity)
直接内存可以减少数据在 Java 堆和操作系统内存之间的拷贝次数,从而提高 I/O 的效率,常用于文件读写
【相关】