0
点赞
收藏
分享

微信扫一扫

JVM的细节


Java Heap和方法区的布局

Java Heap的布局一般是下图所示:

JVM的细节_字段

但是如果使用G1收集器时,Java堆的内存布局与其他收集器有很大差别,它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔阂了,它们都是一部分(可以不连续)Region的集合:

引自:​​jvm系列(三):java GC算法 垃圾收集器​​

JVM的细节_java_02

内存分配与回收策略

引自:《深入理解Java虚拟机:JVM高级特性与最佳实践》3.5节

原则:

  • 一般情况下(大对象例外),新对象在Eden区中分配。当Eden区中不够分配该对象时,JVM将进行一次Minor GC,将Eden区中该回收的对象回收、该挤到Survivor或old区的对象挤走,然后再在Eden区中分配该新对象
  • 超过-XX:PretenureSizeThreshold的大对象直接分配到old区
  • 长期存活的对象将进入old区
  • 如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入old,无须等到MaxTenURingThreshold中要求的年龄
  • 空间分配担保。它的意思是在new区进行了minor GC后还是无法容纳对象(尽力都搞不定),让old区容纳一部分对象(就是责任分担)。为防止old区都容纳不下的情况,所以要先通过之前每次new区晋升到old区的对象平均大小与old区剩余空间大小比对以及HandlePromotionFailure设置是否允许担保失败来决定是否full GC

class的静态文件与运行时

对于:

public class A {
public final static int i=123;
}

编译成A.class后,利用​​javap -verbose A​​进行反编译得到其字节码:

JVM的细节_字段_03

在class的静态文件并没有说​​A.i​​​的内存地址在哪里(只有字段符号引用),只是在cliinit方法中利用其字段符号引用对​​A.i​​进行了赋值;而其字段符号引用对应的内存地址(即直接引用)由JVM在运行时进行解析后得到。

下图可以看出类加载过程:

JVM的细节_jvm_04

总结:
1. 静态变量(没被final修饰的)是在运行时被确定内存位置并赋值的
2. 在静态文件中的符号引用,会在运行时通过解析替换为直接引用

静态变量、字段表、字段符号引用之间关系

  • 静态变量是在运行时才会有其内存地址(直接引用),值保存在里面
  • 字段表集合是用来对外展示类的静态变量信息(并不含有直接引用)
  • 字段符号引用(CONSTANT_Filedref_info)是保存在类的常量池中,用来描述引用的是哪个类的哪个静态变量

对于静态变量:

class A {
public static int i=123;
}
public class B {
public static int j=A.i;
public static void main(String[] args) {

}
}

​A.i​​​是静态变量。它的修饰符​​public static int​​​放在A类的字段表里。对于​​B.j​​​来说怎么找到​​A.i​​​呢?​​A.i​​​是作为CONSTANT_Filedref_info保存在B类的常量池中。如果CONSTANT_Filedref_info没被解析过,要想获得​​A.i​​的值要通过以下步骤(实际就是对符号引用的解析):

  1. 到B类的常量池中获取该CONSTANT_Filedref_info
  2. 通过该CONSTANT_Filedref_info得知其类名为A,字段名为i
  3. 在Java堆中找到A的类对象(如果找不到就要进行A类加载),通过它跳回到方法区中找到A类
  4. 在A类的常量池中解析得到​​A.i​​的直接引用
  5. 将B类中该CONSTANT_Filedref_info替换为直接引用(完成CONSTANT_Filedref_info在B类的解析)
  6. 返回该直接引用

【疑问】步骤4的具体过程目前还不清楚,需要查看虚拟机规范

JVM的细节_java_05

对于final修饰的静态变量:

public class A {
public final static int i=123;
}

根据《深入理解Java虚拟机:JVM高级特性与最佳实践》p166:

而对于类变量,则有两种方式可以选择:
1. 赋值在类构造器方法中进行
2. 使用ConstantValue属性来赋值
目前Sun Javac编译器的选择是:如果同时使用final和static来修饰一个变量(或者说常量更贴切),并且这个变量的数据类型是基本类型或者java.lang.String的话,就生成ConstantValue属性来进行初始化;否则,选择在方法中进行初始化。

上述内容说明了​​A.i​​​的值​​123​​​不是在运行时赋值给​​A.i​​,在静态文件中就通过字段表的属性表的ConstantValue项进行了值的确定。对A.class反编译得到:

JVM的细节_Java_06


举报

相关推荐

0 条评论