JVM在执行字节码时,实际上最终还是把字节码解释成具体平台上的机器指令执行。
Java Development Kit
),Java 语言的软件开发工具包,是开发者用来编译、调试程序用的开发包。JDK是整个java开发的核心,它包含了JAVA的运行环境(JVM+Java系统类库)和JAVA工具。JDK也是Java程序需要在JRE上运行。JDK包含的基本组件包括:
Java Runtime Environment
),也就是Java平台。所有的Java程序都要在JRE环境下才能运行。JRE包括:
Java Virtual Machine
,JVM)Class Library
)Java Virtual Machine
),是JRE的一部分。它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。方法区:属于共享内存区域,存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。运行时常量池存在方法区中。
堆:Java虚拟机所管理的内存中最大的一块,一个JVM只有一个堆,堆的大小可以调节,唯一的目的是存放对象实例。由于是垃圾收集器管理的主要区域,因此有时候也被称作GC堆。
栈:用于描述Java方法执行的模型。每个方法在执行的同时都会创建一个栈帧,用于存储8大数据类型、对象的引用、局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用至执行完成,对应于一个栈帧在虚拟机栈中从入栈到出栈。
程序计数器:当前线程所执行字节码的行号指示器。每一个线程都有一个独立的程序计数器,线程的阻塞、恢复、挂起等一系列操作都需要程序计数器的参与,因此必须是线程私有的。
本地方法栈:与虚拟机栈作用相似,只不过虚拟机栈为执行Java方法服务,而本地方法栈为执行Native
方法服务,比如在Java中调用C/C++。
类加载器通过一个类的全限定名来获取描述此类的二进制文件流的代码模块。
加载、验证、准备、解析、初始化、使用、卸载
虚拟机把描述类的数据从 Class 文件加载到内存,并对数据进行校验、装换解析和初始化,最终形成可以被虚拟机直接使用的 Java 类型。
加载:类加载器获取二进制字节流,将静态存储结构转化为方法区的运行时数据结构,并生成此类的Class对象,即把字节码通过二进制的方式转化到方法区中的运行数据区。
验证:验证文件格式、元数据、字节码、符号引用,确保Class的字节流中包含的信息符合当前虚拟机的要求。
准备:为类变量分配内存并设置其初始值,这些变量使用的内存都将在方法区中进行分配。
解析:将常量池内的符号引用替换为直接引用,包括类或接口的解析、字段解析、类方法解析、接口方法解析。
初始化:前面过程都是以虚拟机主导,而初始化阶段开始执行类中的 Java 代码。
启动类加载器(BootStrap ClassLoader):主要负责加载jre/lib/rt.jar相关的字节码文件的。
扩展类加载器(Extension Class Loader):主要负载加载 jre/lib/ext/*.jar 这些jar包的。
应用程序类加载器(Application Class Loader):主要负责加载用户自定义的类以及classpath环境变量所配置的jar包的。
自定义类加载器(User Class Loader):负责加载程序员指定的特殊目录下的字节码文件的。大多数情况下,自定义类加载器只需要继承ClassLoader这个抽象类,重写findClass()和loadClass()两个方法即可。
类的加载是通过双亲委派模型来完成的,双亲委派模型即为下图所示的类加载器之间的层次关系。
检查:类是否已被加载,没有加载就先加载类
分配内存空间:使用 new 关键字在堆内存中分配一块空间,使用CAS方式分配,防止在为A分配内存时,执行当前地址的指针还没有来得及修改,对象B就拿来分配内存。
初始化对象头:在分配的内存空间中,Java 虚拟机会为对象头分配一定的空间,用于存储对象的元数据信息,如对象的类型、哈希码、GC 信息等。
执行构造方法:在对象头初始化完成后,Java 虚拟机会调用对象的构造方法,对对象进行初始化,包括成员变量的赋值、方法的调用等。
返回对象引用:构造方法执行完毕后,会返回对象的引用,该引用可以被赋值给变量,或者作为参数传递给其他方法。
对象头中有两部分:
GC(Garbage Collection)垃圾收集,回收垃圾,释放内存。
程序计数器、虚拟机栈、本地方法栈是线程私有的,所以会随着线程结束而消亡。 Java 堆和方法区是线程共享的,在程序处于运行期才知道哪些对象会创建,这部分内存的分配和回收都是动态的,垃圾回收所关注的就是这部分内存。
GC的目的实现内存的自动释放,使用可达性分析法判断对象是否可回收,采用了分代回收思想,将堆分为新生代、老年代,新生代中采用复制算法,老年代采用整理算法,当新生代内存不足时会minorGC,老年代不足时会fullGC
在进行内存回收之前要做的事情就是判断那些对象是‘死’的,哪些是‘活’的。
引用计数法:
给对象中添加一个引用计数器,当一个地方引用了对象,计数加1;当引用失效,计数器减1;当计数器为0表示该对象已死、可回收;
注意:如果不下小心直接把 Obj1-reference 和 Obj2-reference 置 null。则在 Java 堆当中的两块内存依然保持着互相引用无法回收。引用计数法很难解决循环引用问题;
可达性分析:
通过一系列的 ‘GC Roots’ 的对象作为起始点,从这些节点出发所走过的路径称为引用链。当一个对象到 GC Roots 没有任何引用链相连的时候说明对象不可用。
可作为 GC Roots 的对象:
引用:下面四种引用强度依次减弱
什么情况下会内存溢出?
CMS:收集器有以下特点:
G1收集器有以下特点:
方法区即被称为永久代,而堆中存放的是对象实例,为了回收的时候对不同的对象采用不同的方法,又将堆分为新生代和老年代,默认情况下新生代占堆的1/3,老年代占堆的2/3。
注意:逻辑上存在,物理上不存在
新生代(Young):
HotSpot将新生代划分为三块,一块较大的Eden(伊甸园)空间和两块较小的Survivor(幸存)空间,默认比例为8:1:1。
老年代(Old):
在新生代中经历了多次GC后仍然存活下来的对象会进入老年代中。老年代中的对象生命周期较长,存活率比较高,在老年代中进行GC的频率相对而言较低,而且回收的速度也比较慢。
永久代(Permanent):永久代存储类信息、常量、静态变量、即时编译器编译后的代码等数据,对这一区域而言,一般而言不会进行垃圾回收。
元空间(metaspace):
从JDK 8开始,Java开始使用元空间取代永久代,元空间并不在虚拟机中,而是直接使用本地内存。那么,默认情况下,元空间的大小仅受本地内存限制。当然,也可以对元空间的大小手动的配置。
GC 过程:
新生成的对象在Eden区分配(大对象除外,大对象直接进入老年代),当Eden区没有足够的空间进行分配时,虚拟机将发起一次Minor GC。
Minor GC:
Minor GC指新生代GC,即发生在新生代(包括Eden区和Survivor区)的垃圾回收操作,当新生代无法为新生对象分配内存空间的时候,会触发Minor GC。因为新生代中大多数对象的生命周期都很短,所以发生Minor GC的频率很高,虽然它会触发stop-the-world,但是它的回收速度很快。
Major GC:
指发生在老年代的垃圾收集动作,出现了 Major GC,经常会伴随至少一次 Minor GC(非绝对),MajorGC 的速度一般会比 Minor GC 慢10倍以上。
Full GC:
Full GC是针对整个新生代、老生代、元空间(metaspace,java8以上版本取代perm gen)的全局范围的GC。Full GC不等于Major GC,也不等于Minor GC+Major GC,发生Full GC需要看使用了什么垃圾收集器组合,才能解释是什么样的垃圾回收。
Minor GC触发条件:
虚拟机在进行minorGC之前会判断老年代最大的可用连续空间是否大于新生代的所有对象总空间
Full GC触发条件:
工具:
命令:
-Xms 设置初始化内存分配大小,默认 1/64
-Xmx 设置最大分配内存,默认 1/4
-XX:+PrintGCDetails 打印GC垃圾回收信息
-XX:+HeapDumpOnOutOfMoryError OOM DUMP-Xmx[]:堆空间最大内存-Xms[]:堆空间最小内存,一般设置成跟堆空间最大内存一样的-Xmn[]:新生代的最大内存-xx:[survivorRatio=3]:eden区与from+to区的比例为3:1,默认为4:1-xx[use 垃圾回收器名称]:指定垃圾回收器-xss:设置单个线程栈大小一般设堆空间为最大可用物理地址的百分之80