1.JVM的内存结构是怎样的?
答:堆,本地方法栈,虚拟机栈,方法区,程序计数器
堆:在虚拟机启动时创建,几乎所有对象实例都在这里创建,是垃圾收集器管理的主要区域,线程共享。如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError。
方法区:主要用来存储jvm加载的类信息,包括类的方法,常量,静态变量,即时编译器编译后的代码等数据。还包括运行时常量池,用于存放静态编译产生的字面量和符号引用。
很少GC,偶尔发生GC,主要是对常量池的回收和类型卸载。线程共享。当方法去无法满足内存分配需求时,将抛出OutOfMemoryError异常。
虚拟机栈:又被称为栈内存,每个方法执行的时候都会创建一个栈帧,用于存储局部变量表,操作数栈,动态链接和方法出口等信息,每个方法被调用直至执行完成的过程,就对应着
一个栈帧在虚拟机栈中从入栈到出栈的过程。线程私有。如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;对于支持动态扩展的虚拟机,当无法申请
到足够的内存将会抛出OutOfMemory异常。
本地方法栈:类似于虚拟机栈,不过本地方法栈为Native方法服务,而虚拟机栈为java方法服务。线程私有。
程序计数器:内存空间小,字节码解释器工作时通过改变这个计数值可以选取下一条要执行的字节码指令,分支,循环,跳转,异常处理和线程恢复等功能都需要依赖这个计数器完成。
该内存区域是唯一一个Java虚拟机规范没有规定任何OOM情况的区域,线程私有。
2.JVM 的类加载机制是什么样的?有几类加载器?
答:JVM通过双亲委派模型进行类的加载,即当某个类的加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回。
只有父类加载器无法完成此加载任务,才自己去加载。双亲委派模型主要解决了两个问题,第一,基础类的统一加载问题(越基础的类由越上层的类加载器进行加载),如Java.lang.String,
无论哪一个类加载器要加载这个类,最终都委派给启动类加载器进行加载,所以在程序的各种类加载器环境中都是同一个类。第二,提高java代码的安全性,比如用户自定义一个java.lang.String
类,那么这个类就不会被加载,因为最顶层的类加载器会首先加载系统的java.lang.String类,而不会加载自定义的String类,防止了恶意代码的注入。
有三类加载器:启动类加载器(Bootstrap ClassLoader):负责加载JAVA_HOME\lib目录中的,或通过 -Xbootclasspath 参数指定路径中的,且被虚拟机认可(按文件名识别,如rt.jar
名字不符合的类库,即使放在lib目录下也不会被加载)的类。启动类加载器无法 被java程序直接引用。
扩展类加载器(Extension ClassLoader):负责加载JAVA_HOME\jre\lib\ext目录中的,或者通过java.ext.dirs系统变量指定路径中的类库;
应用程序类加载器(Application ClassLoader):负责加载用户路径(classpath)上的类库。
除此之外,还可以通过继承 java.lang.ClassLoader类实现自己的类加载(主要是重写findClass方法)。
一个类的生命周期可以分为七个阶段: 加载、验证、准备、解析、初始化、使用、卸载。其中前五个阶段即 类加载,各个阶段(验证、准备、解析这三个阶段又统称为 连接)。
3.什么是类加载器?
答:负责读取 Java 字节代码,并转换成java.lang.Class类的一个实例。
4.程序计数器作用?
答案:
记录当前线程锁执行的字节码的行号。
程序计数器是一块较小的内存空间。
处于线程独占区。
执行java方法时,它记录正在执行的虚拟机字节码指令地址。执行native方法,它的值为undefined
该区域是唯一一个没有规定任何OutOfMemoryError的区域
5.如果判断两个类是否“相同”?
答:
类加载器除了用于加载类外,还可用于确定类在Java虚拟机中的唯一性。即便是同样的字节代码,被不同的类加载器加载之后所得到的类,也是不同的。
通俗一点来讲,要判断两个类是否“相同”,前提是这两个类必须被同一个类加载器加载,否则这个两个类不“相同”。 这里指的“相同”,包括类的Class对象的equals()方法、isAssignableFrom()方法、
isInstance()方法、instanceof关键字等判断出来的结果。
6.Java虚拟机如何结束生命周期?
答:
执行System.exit()方法;
程序正常执行结束;
程序执行遇到异常或Error终止;
操作系统出错而导致java虚拟机运行终止。
7.何时触发初始化
答案:
为一个类型创建一个新的对象实例时(比如new、反射、序列化);
调用一个类型的静态方法时(即在字节码中执行invokestatic指令);
调用一个类型或接口的静态字段,或者对这些静态字段执行赋值操作时(即在字节码中,执行getstatic或者putstatic指令),不过用final修饰的静态字段除外,它被初始化为一个编译时常量表达式;
调用JavaAPI中的反射方法时(比如调用java.lang.Class中的方法,或者java.lang.reflect包中其他类的方法);
初始化一个类的派生类(子类)时(Java虚拟机规范明确要求初始化一个类时,它的超类必须提前完成初始化操作,接口例外);
JVM启动包含main方法的启动类时。
注意:通过子类引用付了的静态字段,不会导致子类初始化
8.JAVA热部署实现
答案:
首先谈一下何为热部署(hotswap),热部署是在不重启 Java 虚拟机的前提下,能自动侦测到 class 文件的变化,更新运行时 class 的行为。Java 类是通过 Java 虚拟机加载的,某个类的 class 文件在被 classloader 加载后,会生成对应的 Class 对象,之后就可以创建该类的实例。默认的虚拟机行为只会在启动时加载类,如果后期有一个类需要更新的话,单纯替换编译的 class 文件,Java 虚拟机是不会更新正在运行的 class。如果要实现热部署,最根本的方式是修改虚拟机的源代码,改变 classloader 的加载行为,使虚拟机能监听 class 文件的更新,重新加载 class 文件,这样的行为破坏性很大,为后续的 JVM 升级埋下了一个大坑。
另一种友好的方法是创建自己的 classloader 来加载需要监听的 class,这样就能控制类加载的时机,从而实现热部署。
9.String s1 = new String("abc");这句话创建了几个字符串对象?
答案:将创建 1 或 2 个字符串。如果池中已存在字符串文字“abc”,则池中只会创建一个字符串“s1”。如果池中没有字符串文字“abc”,那么它将首先在池中创建,然后在堆空间中创建,因此将创建总共 2 个字符串对象。
10.Java对象结构?
答案:
Java对象由三个部分组成:对象头、实例数据、对齐填充。
对象头由两部分组成,第一部分存储对象自身的运行时数据:哈希码、GC分代年龄、锁标识状态、线程持有的锁、偏向线程ID(一般占32/64 bit)。第二部分是指针类型,指向对象的类元数据类型(即对象代表哪个类)。如果是数组对象,则对象头中还有一部分用来记录数组长度。
实例数据用来存储对象真正的有效信息(包括父类继承下来的和自己定义的)
对齐填充:JVM要求对象起始地址必须是8字节的整数倍(8字节对齐)
11.JDK8中MetaSpace代表什么?
答案:
MetaqSpace是JDK8才诞生的名词,它是一种新的内存空间,中文译为:元空间;JDK8 HotSpot中移除了永久代(PermGen Space),使用MetaSpace来代替,
MetaSpace是使用本地内存来存储类元数据信息。内存容量取决于操作系统虚拟内存大小,通过参数MaxMetaspaceSize来限制MetaSpace的大小。