Bootstrap $javahome/jre/rt.jar
Extension $javahome/jre/lib/*.jar
System Class Loader (AppClassLoader) $classpath
User defined Class Loader 用户自定义的加载器是通过sun.misc.Launcher入口。
Execution Engine执行引擎负责解释命令,提交操作系统执行。
native 是调用底层接口。
如下Thread类中的start方法,会调用start0方法,而start0方法前有关键字native,即接下来的实现过程是底层实现,在java代码中无法查看。
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
private native void start0();
native方法区存放的是登记native关键字的方法。
方法区存放的是每一个类的结构信息,供各线程共享的运行时内存区域。例如:运行时常量池、字段和方法数据、构造函数和普通方法的字节码内容。
注意:实例变量存放在堆内存中。
栈也叫栈内存。主管java程序的运行,是在线程创建是创建,它的生命期是跟随线程的生命周期。栈不存在垃圾回收问题。栈是线程私有的,8种基本类型的变量,对象的引用变量,实例方法都是在函数的栈内存中分配的。
每个方法执行的同时都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息,每一个方法从调用直至执行完毕的过程,就对应着一个栈帧在虚拟机中入栈到出栈的过程。
当一个方法迭代调用自己,此时会产生异常,栈溢出。java.lang.StackOverflowError
java堆中存放类元数据的地址,栈中对象的引用存储的是对象的地址。
一个JVM实例只存在一个堆内存,堆内存的大小是可以调节的。类加载器读取了类文件后,需要把类、方法、常变量放在堆内存中。保存所有引用类型的真实信息,以方便执行器执行,堆内存分为以下三部分:
如果出现java.lang.OutOfMemoryError: Java heap space异常,说明Java虚拟机的堆内存不够。原因有二:
(1)Java虚拟机的堆内存设置不够,可以通过参数-Xms、-Xmx来调整。
(2)代码中创建了大量大对象,并且长时间不能被垃圾收集器收集(存在被引用)。
所以堆、栈、方法区的交互方式如上面标题所示,栈中的对象引用指向堆中new出来的对象,(指到对象类型数据的指针),堆中的对象实例数据接下来访问方法区中对象类型数据。
堆、栈、方法区、常量池的存储机制可以参考这边文章:
https://blog.csdn.net/qq_26805137/article/details/52996910
https://blog.csdn.net/gcw1024/article/details/51026840
https://www.cnblogs.com/xiaowangbangzhu/p/10366200.html
注意:java6和6以前,常量池是存放在方法区中的。java7,常量池存放在堆中,常量池相当于永久代,所以永久代存放在堆中。java8之后,取消了永久代,取代的是元空间,没有对常量池进行调整。
java中常量池,实际上分为静态常量池和运行时常量池。
一个jvm实例只存爱一个堆内存,堆内存的大小是可以调节的。类加载器读取了类文件后,需要把类、方法、常变量放到堆内存中,保存所有引用类型的真实信息,以方便执行器执行。堆内存分为三部分:
如上图所示,新生代又分为伊甸区,幸存者from区,幸存者to区,占比为8:1:1。
当发生垃圾清理时,首次gc,eden区进行清理,将清理完剩余的对象拷贝到幸存者from区,当eden区再次出发gc的时候会扫描eden区和幸存者from区,对着两个区域进行垃圾回收,经过这次回收将还存活的对象复制到幸存者to区,然后清空eden区和from区,之后将原本的from区改为to区,进行交换,原survivorFrom区变成to区,to区成为from区,如此交换15次(由jvm参数MaxTenuringThreshold决定),最终将还存活的对象存入老年代。这就是MinorGC的复制算法。
优点:没有标记和清除的过程,效率高,没有内存碎片;
缺点:复制需要双倍空间。
算法分为标记和清除两个阶段,先标记要回收的对象,然后统一回收这些对象。主要进行两项工作,第一项是标记,第二项是清除。
标记:从引用根节点开始标记遍历所有的GC Roots,先标记出要回收的对象。
清除:遍历整个堆,把标记的对象清除。
优点:不需要额外空间。
缺点:两次扫描,耗时严重,会产生内存碎片。
为了解决标记清除的产生内存碎片的缺点,出现了标记压缩算法。主要进行标记、压缩两项工作。
标记过程和标记清除算法一样,但在整理压缩阶段,不再对标记的对象做回收,而是通过所有存活对象都向一端移动,然后直接清除边界以外的内存。可以看到标记存活对象将会被亨利,按照内存地址一次排序,而未被标记的内存会被清除掉。如此一来,当我们需要对新对象分配内存时,jvm只需持有一个内存的起始地址即可,这比维护一个空闲列表少了许多开销。
优点:弥补标记清除算法中内存区域分散的缺点,也消除了复制算法中内存减半的高额代价。
缺点:不仅需要标记,而且还需要移动对象,效率更低,比标记清除算法更低。
老年代一般是由标记清除或者是标记清除与标记整理的混合实现