栈、堆、方法区的配合
- 从线程共享与否的角度来看
方法区
方法区在逻辑上属于堆的一部分, 但是一些简单的实现可能不会选择区进行垃圾收集或者进行压缩。 但对于HotSpotJVM而言, 方法区还有一个别名叫做非堆, 目的就是要和堆分开
所以, 方法区看作是一块独立于java堆的内存空间
- 方法区和堆一样, 是各个线程共享的内存区域
- 方法区在jvm启动的时候创建, 实际物理内存可以是不连续的
- 方法区的大小,可以固定也可拓展
- 方法区的大小决定了系统可以保存多少个类, 如果系统定义了太多的类, 就会导致方法去的OOM
- 关闭jvm释放
Hotspot中方法区的演进
- jdk之前习惯把方法区成为永久代, jdk8元空间取代了永久代。元空间使用的是本地内存
- 本质上, 方法区和永久代并不等价。
- 元空间与永久代最大的区别在于: 元空间不在虚拟机设置的内存中, 而是使用本地内存
- 内部结构也发生了变化。
设置方法区大小和OOM
- oom的解决办法
方法区的内部结构
- 类信息以及运行时常量池
- 他用于存储已被虚拟机加载的类型信息, 运行时常量信息, 及时编译器编译后的代码缓存。
- 对于类型信息来说
- 存储这个类型的完整有效名称(全名=包名.类名)
- 直接父类的完整有效名
- 这个类型的修饰符
- 这个类型直接接口的一个有序列表
- 还有类的加载器是什么
- 域信息
- 包括域名称、域类型、域修饰符
- non-final的类变量
- 静态变量和类关联在一起, 随着类的额加载, 他们成为类数据在逻辑上的一部分。
- 类变量被类的所有实例共享, 即使没有类实例时, 你也可以访问它。
- 被final修饰的静态变量编译的时候就在编译就已经复制了
public class FinalTest {
//不会报错, 并且是正确答案
public static void main(String[] args) {
Order order = null;
order.hello();
System.out.println(order.count);
}
}
class Order{
public static int count = 1;
public static void hello() {
System.out.println("hello!");
}
}
运行时常量池
- 字节码文件中的常量池, 当类运行起来就叫在方法区叫运行时常量池。
- 一个有效的字节码文件中包含了类的版本信息、字段、方法以及接口等描述信息外, 还包含一项信息就是常量池表, 包含各种字面量和对类型、域和方法的符号引用。
- 为什么需要常量池?
- 符号引用是为了减小字节码文件的大小否则转成直接引用就会使字节码文件变大。
- 运行时常量池
- 它会将符号引用转化为真实的地址
- 它会将符号引用转化为真实的地址
方法区的演进细节
- 只有HotSpot才使用永久代
- HotSpot中方法区的变化
永久代为什么被替换
- stringTable为什么要放到堆
- 因为永久代垃圾回收较为低效, 而字符串的创建确实非常频繁的, 那么字符串的回收效率非常的低, 就导致让无用的东西占用内存。
- 证明jdk7以后静态成员变量的引用是放在堆里面, 而new的对象还是无论之前还是现在都是堆中。
- 在这个程序中new了一个100mb的静态变量打印原空间以及堆的信息集可知, 这里静态变量值得是引用放到了堆里面, 而不是new的一个对象。
- 结论: 静态引用对应的对象实体始终都在堆空间, 而引用名字有变化
- 结果
方法区的垃圾收集
- 这个区域的回收效果比较难令人满意, 尤其是类型的卸载, 条件相当苛刻, 但是这部分区域的回收有时又确实是必要的。
- 方法区的垃圾收集主要回收两部人内容, 常量池中废弃的常量, 和不在使用的类型。
- 方法区常量池中主要存放的两大类常量: 字面量和符号引用, 字面量比较接近java语言层次的常量, 如文本字符串、被声明为final的常量值, 而符号引用则属于编译原理方面的概念, 包括下面三类常量:
* 类和接口的全限定名
* 字段的名称和描述符
* 方法的名称和描述符 - hotSpot虚拟机对常量池的回收策略是很明确的, 只要常量池中的常量没有被任何地方引用, 就可以回收。
- 回收废弃的常量与回收java堆中的对象非常相似
总结
- 运行时数据区完结
面试