1. JVM(Hotspot)内存模型简介
如图所示,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