一、Java平台的结构图
二、JVM与JRE、JDK关系
JVM:Java Virtual Machine负责执行符合规范的Class文件
JRE:Java Runtime Environment(java运行环境),包含JVM和类库
JDK:Java Development Kit(java开发工具包)包含JRE和开发工具包
三、JVM所处的位置
四、java运行过程
即时编译器(JIT)
五、jvm结构
image.png-
方法区(method area)
- 类信息、常量、精通变量、即时编译器编译后的代码等数据。方法区是全局共享的,在一定条件下它也会被GC。
- 当方法区使用的内存超过它允许的大小时,就会抛出OutOfMemory:PermGen Space异常。
- JVM方法区的相关参数,最小值:--XX:PermSize;最大值 --XX:MaxPermSize。
-
堆(heap)
- 所有对象实例及数组,堆区是理解JavaGC机制最重要的区域。在JVM所管理的内存中,堆区是最大的一块,堆区也是JavaGC机制所管理的主要内存区域,堆区由所有线程共享,在虚拟机启动时创建。堆区用来存储对象实例及数组值,可以认为java中所有通过new创建的对象都在此分配。
- 年轻代(Young Generation),对象在被创建时,内存首先是在年轻代进行分配(注意,大对象可以直接在老年代分配)。当年轻代需要回收时会触发Minor GC(也称作Young GC)。
如果在执行垃圾回收之后,仍没有足够的内存分配,也不能再扩展,将会抛出OutOfMemoryError:Java Heap Space异常。 - 老年代(Old Generation)老年代用于存放在年轻代中经多次垃圾回收仍然存活的对象,可以理解为比较老一点的对象。当老年代满了的时候就需要对老年代进行垃圾回收,老年代的垃圾回收称作Major GC(也称作Full GC)。
-
程序计数器(Program Counter Register)
- 是一块较小的内存空间,指向当时线程正在执行的字节码指令的地址
- 此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutMemoryError情况的区域
-
本地方法栈(Native Method Stacks)
- 虚拟机用到的本地方法,与虚拟机发挥的作用是非常相似的,其区别不过是虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。
- 与虚拟机栈一样,本地方法区域也会抛出StackOverflowError和OutOfMemoryError异常
-
java虚拟机栈(Java Virtual Machine Stacks)
- 当前线程运行方法时需要用到的局部变量表、操作数栈、动态链接、方法出口等。
- 在Java虚拟机规范中,对这个区域规定了两种异常情况:如果线程请求的栈深度大于虚拟机允许的深度,将抛出StackOverflowError异常;如果虚拟机栈可以动态扩展,当扩展时无法申请到足够的内存时会抛出OutOfMemoryError异常
-
其中,方法区和堆是所有线程共享的。
五、jvm内存模型
可见性
原子性 image.png
六、jvm类加载机制
虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的java类型,这就是类的加载机制。
在java语言里面,类型的加载和连接过程都是在程序运行期完成的。 image.png
加载、验证、准备、初始化和卸载这五个阶段的顺序是确定的,类的加载过程必须按照这种顺序按部就班的开始,而解析阶段则不一定:它在某些情况下可以在初始化阶段之后再开始,这是为了支持java语言的运行时绑定,这些阶段通常都是互相交叉低混合式进行的。
-
类的加载过程
- 加载:类加载过程的第一阶段,在加载阶段,虚拟机需要完成以下三件事情
- 通过一个类的全限定名来获取定义此类的二进制字节流。
- 讲这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
- 在java堆中生成一个代表这个类的ava.lang.Class对象,作为方法区这些数据的访问入口
- 加载:类加载过程的第一阶段,在加载阶段,虚拟机需要完成以下三件事情
-
验证:验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机的安全
- 文件格式验证
- 元数据验证
- 字节码验证
- 符号引用验证
-
准备:为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中进行分配。
-
解析:解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程
- 类或接口的解析
- 字段解析
- 类方法解析
- 接口方法解析
-
初始化:类初始化阶段是类加载过程的最后一步,这个阶段才真正开始执行类定义的java程序代码(字节码)
七、jvm类加载器
-
类与类加载器:类加载器用于实现类的加载动作,任意一个类都需要由加载它的类加载器和这个类本身一同确立其在java虚拟机中的唯一性
-
双亲委派模型:java虚拟机存在两种不同的类加载器,一是启动类加载器(Bootstrap ClassLoader),这个类加载器使用C++语言实现,是虚拟机的一部分。另一种就是其他类加载器,由java语言实现,独立于虚拟机外部,并且全部继承自抽象类java.lang.ClassLoader
image.png -
破坏双亲委派模型
- 第一次被破坏发生在双亲委派模型出现之前(JDK1.2发布之前)
- JDK1.2之后java.lang.ClassLoader添加了一个新的protected方法,findClass()
- 第二次破坏是由这个模型自身的缺陷所导致的,基础类如果又要调回用户的代码会出问题,为了解决这个问题,java设计团队引入线程上下文加载器(Thread ClassLoader)。这个类加载器可以通过java.lang.Thread类的setContextClassLoader()方法设置,如果创建线程时没设置,它将会从父线程中继承一个;如果在应用程序的全局范围内都没设置过,那么这个类加载器默认就是应用程序类加载器
- 第三次破坏是由于用户对程序动态性的追求而导致的。在OSGi环境下,类加载器不再是双亲委派模型中的树状结构,而是进一步发展为网状结构。
- 第一次被破坏发生在双亲委派模型出现之前(JDK1.2发布之前)
八、JVM GC机制
Java GC(Garbage Collection,垃圾收集,垃圾回收)机制
-
JVM通过GC来回收堆和方法区中的内存,这个过程是自动执行的。说到Java GC机制,其主要完成3件事:
- 确定哪些内存需要回收;
- 确定什么时候需要执行GC;
- 如何执行GC。
-
垃圾检测、回收算法:垃圾收集器一般必须完成两件事:检测出垃圾;回收垃圾。怎么检测出垃圾?一般有以下几种方法
- 引用计数法:给一个对象添加引用计数器,每当有个地方引用它,计数器就加1;引用失效就减1。如果我有两个对象A和B,互相引用,除此之外,没有其他任何对象引用它们,实际上这两个对象已经无法访问,即是我们说的垃圾对象。但是互相引用,计数不为0,导致无法回收。
- 根搜索算法(GC Roots Tracing)
-
这个算法的基本思路就是通过一系列的名为“GC Roots”的对象做起点,从这些节点开始向下搜索,搜索所走过的路径成为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。
-
在Java语言里,可作为GC Roots的对象包括下面几种:
- 虚拟机栈(栈帧中的本地变量表)中的引用对象
- 方法区中的类静态属性引用的对象
- 方法区中的常量引用对象
- 本地方法栈中JNI(即一般说的Native方法)的引用对象
-
-
总之,JVM在做垃圾回收的时候,会检查堆中的所有对象是否会被这些根集对象引用,不能够被引用的对象就会被垃圾收集器回收。一般回收算法也有如下几种:
- 标记-清除算法(Mark-sweep)
- 算法和名字一样,分为两个阶段:标记和清除。标记所有需要回收的对象,然后统一回收。这是最基础的算法,后续的收集算法都是基于这个算法扩展的。
- 不足:效率低;标记清除之后会产生大量碎片。
- 复制算法(Copying)
- 此算法把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾回收时,遍历当前使用区域,把正在使用中的对象复制到另外一个区域中。
- 此算法每次只处理正在使用中的对象,因此复制成本比较小,同时复制过去以后还能进行相应的内存整理,不会出现“碎片”问题。当然,此算法的缺点也是很明显的,就是需要两倍内存空间。
- 标记-整理算法(Mark-Compact)
- 此算法结合了“标记-清除”和“复制”两个算法的优点。也是分两阶段,第一阶段从根节点开始标记所有被引用对象,第二阶段遍历整个堆,把清除未标记对象并且把存活对象“压缩”到堆的其中一块,按顺序排放。
- 此算法避免了“标记-清除”的碎片问题,同时也避免了“复制”算法的空间问题。
- 分代收集算法(Generational Collection)
- 这是当前商业虚拟机常用的垃圾收集算法。分代的垃圾回收策略,是基于这样一个事实:不同的对象的生命周期是不一样的。因此,不同生命周期的对象可以采取不同的收集方式,以便提高回收效率。
- 标记-清除算法(Mark-sweep)
- 垃圾收集器
九、JVM性能监控工具
- Jps(JVM Process Status Tools)
- Jps是参照Unix系统的取名规则命名的,而他的功能和ps的功能类似,可以列举正在运行的饿虚拟机进程并显示虚拟机执行的主类以及这些进程的唯一ID(LVMID,对应本机来说和PID相同)
- jstat(JVM Statistics Monitoring Tools)
- Jstat主要用于监控虚拟机的各种运行状态信息,如类的装载、内存、垃圾回收、JIT编译器等,在没有GUI的服务器上,这款工具是首选的一款监控工具
- jinfo(JVM configuration Info for Java)
- Jinfo的作用是实时查看虚拟机的各项参数信息jps –v可以查看虚拟机在启动时被显式指定的参数信息
- jmap(JVM Memory Map for Java)
- Jmap用于生成堆快照(heapdump)
- jhat(JVM Heap Analysis Tool)
- Jhat是用来分析dump文件的一个微型的HTTP/HTML服务器,它能将生成的dump文件生成在线的HTML文件,让我们可以通过浏览器进行查阅,然而实际中我们很少使用这个工具,因为一般服务器上设置的堆、栈内存都比较大,生成的dump也比较大,直接用jhat容易造成内存溢出,而是我们大部分会将对应的文件拷贝下来,通过其他可视化的工具进行分析。
- jstack(JVM Stack Trace for java)
- Jstack用于JVM当前时刻的线程快照,又称threaddump文件,它是JVM当前每一条线程正在执行的堆栈信息的集合。生成线程快照的主要目的是为了定位线程出现长时间停顿的原因,如线程死锁、死循环、请求外部时长过长导致线程停顿的原因。通过jstack我们就可以知道哪些进程在后台做些什么?在等待什么资源等!
- JConsole(JVM Monitoring and management console)(可视化)
- 执行JConsole即可查看效果
- VisualVM被成为是more in one的工具集(可视化)
- 执行jvisualvm即可查看效果
网友评论