1、内存模型以及分区,需要详细到每个区放什么。
通俗的说,
Java 虚拟机具有一个堆,堆是运行时数据区域,所有类实例和数组的内存均从此处分配。
JVM主要管理两种类型内存:堆和非堆,堆内存(Heap Memory)是在 Java 虚拟机启动时创建,非堆内存(Non-heap Memory)是在JVM堆之外的内存。
简单来说,堆是Java代码可及的内存,留给开发人员使用的;非堆是JVM留给自己用的,包含方法区、JVM内部处理或优化所需的内存(如 JIT Compiler,Just-in-time Compiler,即时编译后的代码缓存)、每个类结构(如运行时常数池、字段和方法数据)以及方法和构造方法的代
JVM 内存包含如下几个部分:
堆内存(Heap Memory): 存放Java对象
非堆内存(Non-Heap Memory): 就是我们平时说的方法取,存放类加载信息和其它meta-data
其它(Other): 存放JVM 自身代码等
在JVM启动时,就已经保留了固定的内存空间给Heap内存,这部分内存并不一定都会被JVM使用,但是可以确定的是这部分保留的内存不会被其他进程使用,这部分内存大小由-Xmx 参数指定。而另一部分内存在JVM启动时就分配给JVM,作为JVM的初始Heap内存使用,这部分内存是由 -Xms 参数指定。
Java文件经过JVM编译成class文件,通过类加载器,加载到内存中.
JVM内存模型按内存线程是否共享分为主内存和私有内存,
主内存:
堆(Heap):Java除了栈区,几乎所有的java对象都是存储在堆区.
方法区: 就是上述说的非堆,1,加载类时的类的信息,2,final常量,3,静态变量,
私有内存:
虚拟机栈:
虚拟机栈,生命周期与线程相同,是Java方法执行的内存模型。每个方法(不包含native方法)执行的同时都会创建一个栈帧结构,方法执行过程,对应着虚拟机栈的入栈到出栈的过程。
本地方法栈:本地方法栈则为虚拟机使用到的Native方法提供内存空间
程序计数器: 程序计数器PC,当前线程所执行的字节码行号指示器。每个线程都有自己计数器,是私有内存空间,该区域是整个内存中较小的一块。
1,线程正在执行一个Java方法时,PC计数器记录的是正在执行的虚拟机字节码的地址;
2,当线程正在执行的一个Native方法时,PC计数器则为空(Undefined)。
一句话概括:'
堆:存放大部分java对象;
方法区:存放加载累的基本信息,final值和静态变量.
本地方法栈:虚拟机使用Native方法的内存空间
虚拟机栈:Java中方法的内存模型,代表着方法的出战入栈
程序计数器:Java运行的字节码地址.
各个分区出现的异常:
几个JVM参数说明:
-Xms:JVM启动时初始分配给JVM的内存,当JVM内存低于总内存40%,此时JVM内存会达到该值得最大值.
-Xmx:分配给JVM堆内存的值.
outofMemory 代码示例:
1,堆区:
public static void main(String[] args) {
//-Xms20M -Xmx20M -XX:+HeapDumpOnOutOfMemoryError
List<Integer> list = new ArrayList<Integer>();
while(true){
list.add(9);
}
}
显示结果:
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid14249.hprof ...
Heap dump file created [15664339 bytes in 0.049 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3210)
at java.util.Arrays.copyOf(Arrays.java:3181)
at java.util.ArrayList.grow(ArrayList.java:261)
at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:235)
at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:227)
at java.util.ArrayList.add(ArrayList.java:458)
at com.outofmemoryerror.App.main(App.java:15)
2,虚拟机栈和本地方法栈溢出;
private int stackLength = 1;
public void increase(){
stackLength++;
increase();
}
//-Xss160k 设置虚拟机栈大小160k
public static void main(String[] args) throws Throwable{
JavaStackOOM oom = new JavaStackOOM();
try {
oom.increase();
} catch (Throwable e) {
System.out.println("oom.stackLength is:"+oom.stackLength);
throw e;
}
}
异常信息如下:
oom.stackLength is:771Exception in thread "main"
java.lang.StackOverflowError
at xxxx
3,方法区常量池:
// -XX:PermSize=10M -XX:MaxPermSize=10M
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
int i=0;
while(true){
list.add(String.valueOf(i++).intern());
}
}
上述代码在jdk6以前会出现异常,在jdk7以后该异常不会出现.
在JDK1.7中, 已经把原本放在永久代的字符串常量池移出, 放在堆中. 为什么这样做呢? 因为使用永久代来实现方法区不是个好主意, 很容易遇到内存溢出的问题. 我们通常使用PermSize和MaxPermSize设置永久代的大小, 这个大小就决定了永久代的上限, 但是我们不是总是知道应该设置为多大的, 如果使用默认值容易遇到OOM错误.
JVM的实现中将类的元数据放入 native memory, 将字符串池和类的静态变量放入java堆中
2、堆里面的分区:Eden,survival (from+ to),老年代,各自的特点。
3、对象创建方法,对象的内存分配,对象的访问定位。
对象的创建
我们都知道java语言中是用new关键字来创建对象的,然而在虚拟机中创建对象的过程是怎样的呢?
当虚拟机遇到一条new指令时,首先会去检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且坚持这个符号引用对应的类是否已经加载,解析,初始化如果没有的话,必须先执行相应类的加载。
类加载检查之后,虚拟机就要为新生的对象分配内存,为对象分配内存空间相当于在java堆中开辟一块和新生对象一样大小的内存。
内存分配之后,虚拟机将分配到内存的空间初始化,这一操作为了保证对象的实例字段在未被赋初始值就可以使用。接下来就要对对象就行必要的设置,例如,此对象属于哪个类,对象的哈希码,这些对象存放在对象的对象头中。在这些操作完成之后,从虚拟机的角度看,一个对象已经创建完成,但是从Java程序来看,真正的创建才刚开始,去执行init()方法。对象按照我们的意愿初始化完之后,一个对象就算真正的创建完成了。
对象的内存布局
在HotSpot虚拟机中,对象在内存中存储的布局可以分为3部分:对象头,实例数据和对齐数据。
对象头中包括两部分信息,一部分用于存储对象自身的运行时数据,如哈希码,锁状态标志等;另一部分时类型指针,即对象指向它的类元数据的指针,虚拟机就是通过此指针来确定这个对象属于哪个类的实例。
实例数据时对象真正存储的有效信息,也是在程序中定义的各个字段的内容,至于这部分的存储顺序是由虚拟机的分配策略和在Java中中定义的顺序决定的,其中父类中定义的变量一定会出现在子类定义的变量之前。
对象对齐填充,其实它并不一定存在,在Hot Spot在虚拟机的内存管理系统要求对象起始位置的大小必须是8字节的整数倍,所以当对象实例数据部分没有对齐,就需要对齐补充来补全。
对象的访问定位
在Java程序中我们用栈上的reference数据访问堆上的对象。在Java虚拟机规范中规定reference也只是一个指向对象的引用,但是具体但是如何定位,访问堆中对象的具体位置,不同的虚拟机实现方式是不一样的。常用的访问方式有句柄和直接指针两种。
如果使用句柄访问,Java堆中首先划分出一块内存作为句柄池,reference中存储的是句柄的地址,而句柄中存储的是对象实例数据和类型数据的地址信息。
如果使用直接指针访问,reference中存储的就是对象的地址,而Java堆对象的布局中就必须考虑如何放置访问堆对象的信息。
4、GC的两种判定方法:
5、GC的三种收集方法:标记清除、标记整理、复制算法的原理与特点,分别用在什么地方,如果让你优化收集方法,有什么思路?
6、GC收集器有哪些?CMS收集器与G1收集器的特点。
7、Minor GC与Full GC分别在什么时候发生?
8、几种常用的内存调试工具:jmap、jstack、jconsole。
9、类加载的五个过程:
10、双亲委派模型:Bootstrap ClassLoader、Extension ClassLoader、ApplicationClassLoader。
11、双亲委派模型是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。-----例如类java.lang.Object,它存在在rt.jar中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的Bootstrap ClassLoader进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。相反,如果没有双亲委派模型而是由各个类加载器自行加载的话,如果用户编写了一个java.lang.Object的同名类并放在ClassPath中,那系统中将会出现多个不同的Object类,程序将混乱
12、分派:静态分派(重载)与动态分派(重写)。
13、你知道哪些JVM性能调优
14,JVM运行内存的分类
15,Java内存堆和栈区别
16,GC回收机制
17,GC 标记对象的死活
18,GC 回收算法
19,MinorGC&FullGC
20,Java 堆内存的分配策略
21,Class 的加载过程
22,类加载器
23,自定义ClassLoader
24,双亲委派模型&打破双亲委派模型
25,引起类初始化操作的四个行为