Java虚拟机(1)-JVM内存模型

时间:2022-12-27 20:26:11

1. JVM(Hotspot)内存模型简介

Java虚拟机(1)-JVM内存模型

如图所示,JVM的内存模型中主要涵盖了以下5个部分.


1.1 程序计数器

程序计数器主要存储每个线程执行的字节码指令的行号.
线程私有.
Java方法中的代码经过javac处理后,在class文件中以Code属性表形式存在,表中以数值方式存储了相应的字节码指令.
当线程执行native方法时,存储的值为undefined.该区域不会出现OOM.


1.2 虚拟机栈

VM栈中以栈帧的形式存储了线程执行Java方法的信息.VM栈只有栈帧的入栈和出栈两种动作.
线程私有.


1.3 本地方法栈

Native栈的作用和VM栈类似,当线程执行了非Java方法(如C实现)时,会在内存中开辟一份Native栈,用以存储改方法的执行信息.
线程私有.
Hotspot中,VM栈和Native栈已经合二为一.


1.4 方法区

方法区用以储存常量,静态变量,类的符号引用等信息.
各个线程公有.
Hotspot中将垃圾回收分代的思想引申到了方法区,所以Hotspot中的方法区也就是持久带.


1.5 堆

堆内存的唯一作用就是存储对象实例.
各线程公有.


2. 代码演示内存溢出异常


2.1 堆溢出

public class JVMTest {
    static class OOMTest {
    };

    public static void main(String[] args) {
        /**
         * 模拟堆溢出
         */
        List<OOMTest> list = new ArrayList<OOMTest>();
        while (true) {
            list.add(new OOMTest());
        }
    }
}

对象创建过多导致堆内存不足.


2.2 方法区溢出

public class JVMTest {
    static class OOMTest {
    };

    public static void main(String[] args) {
         /**
         * 模拟方法区溢出
         */
         List<String> list = new ArrayList<String>();
         int i=0;
         while(true){
         list.add(String.valueOf(i++).intern());
         //String类的intern方法创建的字符串,不会在编译期而是在运行期放入方法区的常量池
         }
    }
}

常量池中字符串过多导致方法区内存不足.


2.3 VM栈溢出

public class JVMTest3 {
    private int count = 1;

    public void stackTest() {
        count++;
        stackTest();
    }

    public static void main(String[] args) throws Throwable {
        JVMTest3 test = new JVMTest3();
        try {
            test.stackTest();
        } catch (Throwable err) {
            System.out.println("Stack length:" + test.count);
            throw err;
        }
    }
}

方法调用层次过多导致栈深过大.


3.其他


  • 什么是JIT即时编译

Java的编译通常指由源文件编译成字节码的过程.
但是,字节码是可以在不同平台运行的,由字节码指令转化到机器指令的过程需要运行时进行.


  • trictfp关键字

此关键字可以修饰类,接口,方法.
被strictfp修饰后,内部的所有运算严格遵守IEEE754规范,不会应为运行在不同平台上产生不同的结果.


  • 各版本jdk的一些新特性

Jdk1.4:
真正走向成熟的版本.
Jdk1.5
增加了泛型,枚举等特性.
Java.util.concurrent并发包等.
Jdk1.6:
在同步锁,垃圾收集等方面做了一些改进.
Jdk1.7:
提供了G1收集器
Jdk1.8:
G1收集器为jvm的默认收集器.


  • 什么是常量池,存在于内存哪个部分,存储什么

class文件常量池:存放编译器生成的字面量和符号引用
运行时常量池:存储运行期生成的常量,如String.intern().
都属于方法区.


  • 查看class文件中常量池信息

javap -verbose ../JVMTest4.class


  • 什么是直接内存

使用native函数库直接分配的对外内存,与NIO类相关.


  • 常见JVM的设置参数有哪些

-Xms 初始堆大小
-Xmx 最大堆大小
-Xmn 年轻代大小
-XX:PermSize 初始永久代大小,默认物理内存1/64
-XX:MaxPermSize 最大永久代大小,默认物理内存1/4
-Xss 单线程栈大小,栈深不是很大128k足够.


  • 永久代与方法区的区别

永久代只是hotspot将分代回收的理念引申到了方法区,
且在jdk1.8之后,不存在永久代的概念,用metaspace实现了方法区.


  • jdk1.6,1.7,1.8中方法区的迁移过程,String.intern()在1.6和1.7中有什么不同

1.6中,使用永久代实现了方法区
1.7中,将运行时常量池从方法区移至堆中.
1.8中,不存在永久代,使用metaspace实现了方法区.
如以下代码:

String s3 = new String("1") + new String("1");
s3.intern();
String s4 = "11";
System.out.println(s3 == s4);

Jdk1.6:
1)常量池中创建”1”对象,堆中两个new String()对象,存储常量池中”1”的引用.堆中创建相加后的对象(内容为”11”),s3存储这个对象的引用.
2)s3.intern()执行时,去常量池中寻找”11”,没找到则在常量池中创建”11”.
3)S4在常量池中找到”11”,返回引用.
4)所以,结果为false.

Jdk1.7
1)常量池中创建”1”对象,堆中两个new String()对象,存储常量池中”1”的引用.堆中创建相加后的对象(内容为”11”),s3存储这个对象的引用
2)s3.intern()执行时,去常量池中寻找”11”,没找到,则将堆中相加后对象的引用存储在常量池中.
3)S4获取到的为对中内容为”11”的引用,和s3的内容一致
4)结果为true