目录
1.什么是JVM
2.JVM、JRE、JDK、JavaSE、JavaEE之间的联系
3.JVM的内部结构
4.各部分的作用
4.1 类加载器
4.2 方法区
4.3 堆
编辑 4.4 虚拟机栈
4.5 程序计数器
4.6 本地方法栈
4.7 解释器和JIT即时编译器
4.9 GC垃圾回收
5.拓展
5.1一些可能会遇到的问题
5.2堆内存溢出诊断方法
5.3linux环境下线程诊断方法
1.什么是JVM
JVM是Java虚拟机的缩写,是Java程序的运行环境。更准确地说是Java程序的二进制字节码的运行环境,因为JVM的主要作用是将Java源码编译为字节码并执行。Java程序之所以能够跨平台运行就是因为JVM的存在,JVM编译后的字节码在任何支持JVM的平台都可以运行且无需再次编译;并且JVM有自己的垃圾回收机制,自动管理内存,减轻了程序员内存管理的负担。
通过学习JVM,我们就可以了解一个Java程序在底层的执行过程,进而优化我们的代码。
2.JVM、JRE、JDK、JavaSE、JavaEE之间的联系
3.JVM的内部结构
4.各部分的作用
4.1 类加载器
用来加载类的二进制字节码(包括类的基本信息、常量池,类方法定义)。有四种类加载器:启动类加载器、扩展类加载器、应用类加载器、自定义类加载器。
4.2 方法区
方法区是线程共享的一块区域,存储了和类结构相关的信息,比如运行时常量池、成员变量、方法数据等;方法区在虚拟机启动时被创建,逻辑上是堆的一部分。之所以说逻辑上是堆的一部分是因为实际的存储位置并不一定在堆中,比如Hotspot(JVM的一种)的jdk1.8版本是将方法区放在了元空间,而元空间使用的是操作系统的一部分内存;jdk1.6版本的则放在了永久代中,永久代使用的是堆内存。方法区也会触发垃圾回收,当大量的类被加载时会导致方法区空间不足。要注意的是,在JDK1.7版本前方法区空间不足触发的异常是内存不足:永久代(java.lang.OutOfMemoryError: PermGen space),在JDK1.8版本后则是内存不足:元空间(java.lang.OutOfMemoryError: Metaspace)
4.3 堆
堆是线程共享的,所有通过new创建的对象都会存放在堆中。当存储的对象过多或者串池存放的值过多时会导致堆内存溢出,也就会触发垃圾回收。如果垃圾回收后堆内存仍不足,那就是其中的对象大部分都还在被使用无法回收,内存占用仍然很高。要注意的是,在JDK1.6版本前串池空间不足触发的异常是内存不足:永久代(java.lang.OutOfMemoryError: PermGen space),而JDK1.7版本后提示的异常则是内存不足:堆空间(java.lang.OutOfMemoryError: Java heap space)
4.4 虚拟机栈
每个线程在创建时都会创建一个虚拟机栈,这是线程运行所需要的内存,所以虚拟机栈是线程私有的。每个栈中存放一个个栈帧,栈帧则是调用一个方法所需要的内存,里面存放着局部变量表、操作数栈、返回地址等;栈帧中仅有一个活动栈帧,对应当前正在执行的方法,也是栈顶的栈帧,如果当前方法调用了其他方法,那么对应的新的栈帧会被创建出来,放在栈的顶端成为新的活动栈帧。当方法执行完成或者抛出异常自动结束执行时,均作为执行完毕处理,就会将该方法对应的栈帧弹出虚拟机栈,活动栈帧自然就会变成下一个方法对应的栈帧。
虚拟机栈不会触发垃圾回收,因为方法执行完后栈帧就被弹出去了,不会留在栈中。但存在栈内存溢出的问题,一种是栈帧过多导致的,比如无限递归;另一种是栈帧过大导致的,一般都是由栈帧过多引起的栈内存溢出,因为普通情况下执行一个方法所用到的局部变量不是很多,栈帧也不会太大。
虚拟机栈由于栈帧中的局部变量都是线程私有的,所以只要不逃离线程的作用范围就不会产生线程安全问题;但堆是线程共享的,所以都需要考虑线程安全问题。
4.5 程序计数器
主要作用就是记住下一条JVM指令的执行地址,以在执行完当前的一个JVM指令后能够找到下一个指令,实际上是一个寄存器,每个线程只有一个程序计数器,且程序计数器是线程私有的,不存在内存溢出的问题。
4.6 本地方法栈
本地方法栈是线程私有的,随着线程的结束而消失,所以不会触发垃圾回收。
4.7 解释器和JIT即时编译器
4.9 GC垃圾回收
当堆内存不足时就会出发垃圾回收,堆中存放对象的地方又分为新生代和老年代, 新生代又分为伊甸园和幸存区,幸存区分为from和to两部分。一般情况下,新产生的对象被放在伊甸园中,执行垃圾回收时没有被回收的对象会放到幸存区中,如果这些对象在执行多次垃圾回收时依旧没有被回收,那么就会将这些对象从幸存区放到老年代中。
详细内容见另一篇博客:JVM学习-垃圾回收专题。