参考文档:
java直接内存原理
JVM内存结构
JVM内存结构
JVM内存结构
JVM内存结构
JVM运行时内存
- 程序计数器:
- 程序计数器是一块很小的内存,它存储的是当前线程运行的字节码的行号,它在分配的时候是线程独享的,使用的时候是线程共享的,它的生命周期和线程的生命周期一致。
- 正常单线程程序计数器是没有必要出现的,它的出现和JVM的cpu时间片切换运行方式有关系,多线程时每个线程得到cpu运行的一个时间片,当时间片结束,程序还在没有结束的时候,线程回挂起,线程再次运行的时候就可以根据程序计数器来继续运行。
- JVM分配管理,此区域不会出现内存溢出。
- Java虚拟机栈:
- java虚拟机栈在线程建立的时候分配的,生命周期和线程相同。
- java虚拟机栈中存在一种结构栈帧,一个方法对应一个栈帧,栈帧存储的时局部变量表、操作数栈、动态链接、方法出口。局部变量表就是我们通常所说的“栈内存”,它的空间大小是在编译期间分配的固定大小,它存储的是编译时已知的基本数据类型和对象引用类型。
- Java虚拟机栈分配:
- 固定大小:当栈深度超过JVM固定大小,StackOverFlowError。
- 动态扩展:当栈动态扩展超过本地内存限定,OutOfMemoryError。
- 本地方法栈:
- 本地方法栈和Java虚拟机栈一样,区别在于本地方法栈服务员native方法,另外在HotSpot虚拟机中这两个是合在一起的。
- 堆:
- JVM管理的最大一块内存区域,属于线程共享的,是垃圾收集器最主要管理的内存区域,也称为“CG堆”。
- 堆存储的是对象实例,采用的是分代管理的垃圾回收机制,分为新生代、老年代、永久代(部分虚拟机没有)。
- 堆分配的内存空间可以不是连续的内存,在虚拟机启动的时候分配,它的大小可以通过-Xmx -Xms来改变堆的大小,堆的内存空间不够的时候会抛出OutOfMemoryError。(章内容缺垃圾收机制没有补全)
- 方法区:
- 方法区逻辑上属于堆,在HotSpot虚拟机中方法区位于永久代,但是在其他虚拟机中不存在永久代。
- 方法区存储的是类信息(类版本、字段、接口、方法)、常量、静态变量、即时编译器编译后的代码等。(文章内容缺垃圾收机制没有补全)
- 方法区在虚拟机启动的时候分配内存。从内存分配,当内存不足分配时,提示OutOfMemoryError。
- 运行时常量池:
- 运行时常量池属于方法区的一部分。
- 在class文件中存在一个常量池,编译的时候将字面量和符号引用存储在这个常量池中。在类加载的时候这个常量池中的内容会进入运行时常量池中,另外运行时常量池还存储运行时产生的常量,如String()、Intern()方法。
直接内存
- 可以使用native方法直接分配直接内存,然后使用存储在java堆中DirecByteBuffer对象作为这块内存的引用进行操作。直接内存大小不会受堆大小的限制,但是收到本地内存和处理器寻址空间限制。-Xmx设置不合理也会出现OutOfMemoryError。
关于JVM内存结构问答
- (1)JVM管理的内存结构是怎样的?
JVM管理的内存结构由PC寄存器、java虚拟机栈、本地方法栈、堆、方法区、运行时常量池构成。 - (2)不同的虚拟机实现运行时内存池有什么不同?
《Java虚拟机规范》定义了JVM运行时内存结构,该规范对内存的定义比较宽松的,特别时方法区。方法区定义是逻辑上属于堆,不同厂商实现也不尽相同,在HotSpot虚拟机中,HotSpot将方法区置于堆的永久代中,到了java8取消了永久代,使用本地内存来存储元数据信息,称为元空间。 - (3)运行时内存哪些是线程独享,哪些是线程共享的?
程序计数器分配是线程独享的,使用是线程共享的;Java虚拟机栈和本地方法栈是线程独享的;堆和方法区是线程共享的。 - (4)出来java运行时内存空间外,还有哪些内存可以使用?
直接内存,可以由native方法分配内存空间,通过JVM堆中的DirecByteBuffer对象作为这块内存的引用进行操作。 - (5)堆和栈的区别?
- 堆:线程共享、JVM启动时分配、存储实例
- 栈:线程独享、线程启动的分配、存储局部变量表,操作数,动态链接,方法出口、
- (6)Java数组存储在堆里面。
- (7)Java对象创建有哪几种方式?
- 方法一:
- User user = new User()
- 方法二:
- User user = User.class.newInstance();
- 方法三:
- Constructor constructor = User.getConstructor();
- User user = constructor.newInstance();
- 其他:还可以使用反序列化和Clone方法。
- 方法一:
- (8)Java创建对象的过程?
- a 到常量池中定位类的符号引用
- b 查看引用所代表的类是否被加载、解析、初始化过,没有,则加载类
- c 为类从堆分配内存空间,指针碰撞(内存规整)或空闲列表(内存交错)
- d 类空间全部赋值为零
- e 对对象进行必要设置
- f 使用构造函数初始化类
- (9)java对象一定在堆中么?
- 逃逸分析。如果一个对象逃逸出方法,则采用栈上分配。
- (10)如何获取堆和栈的快照信息
- 使用服务器上的jMap命令获取堆的快照信息,使用jStack命令来获取栈快照信息。
内存结构代码参考
编译器编译后的字节码类文件:
1. Cow.class
//
//编译器在外部类添加了静态方法 Cow.access$0(Cow arg0)。它将返回作为参数传递给它的对象的私有域weight。
//如果内部类不访问外部类的私有字段,将不会在外部类中添加静态方法Cow.access$0(Cow arg0)。
public class Cow {
private double weight;
public Cow() {
}
public Cow(double weight) {
this.weight = weight;
}
public void test() {
//编译器将CowLeg cl = new CowLeg(1.12, "黑白相间");语句编译为
CowLeg cl = new CowLeg(this, 1.12D, "黑白相间");
cl.info();
}
//编译器在外部类添加了静态方法
static double access$0(Cow arg0){
return arg0.weight;
}
public static void main(String[] args) {
Cow cow = new Cow(378.9D);
cow.test();
}
}
//编译器为了在内部类的实例中引用外部类的实例对象,必添加一个附加的实例域Cow this$0(this$0名字是由编译器合成的,在自编写的代码中不应该引用它,因为合成名称可能不同)。
//另外,编译器修改了所有的内部类的构造器,添加了一个引用外部类实例的参数Cow arg0。
//不管内部类是否访问外部类,内部类的构造器是一样的,均有Cow arg0参数。
class Cow$CowLeg {
private double length;
private String color;
//编译器必添加一个附加的实例域Cow this$0
final Cow this$0;
//编译器在内部类的构造方法中,必添加一个引用外部类实例的形参Cow arg0
public Cow$CowLeg(Cow arg0) {
this.this$0 = arg0;
}
public Cow$CowLeg(Cow arg0, double length, String color) {
this.this$0 = arg0;
this.length = length;
this.color = color;
}
public void setLength(double length) {
this.length = length;
}
public double getLength() {
return this.length;
}
public void setColor(String color) {
this.color = color;
}
public String getColor() {
return this.color;
}
public void info() {
System.out.println("当前牛腿颜色是:" + this.color + ", 高:" + this.length);
System.out.println("本牛腿所在奶牛重:" + Cow.access$0(this.this$0));
}
}
参考文档:
java直接内存原理
JVM内存结构
JVM内存结构
JVM内存结构