1 JVM运行机制概述
JVM运行机制
- 类加载机制: 类加载过程由类加载器来完成,即由ClassLoader及其子类实现,有隐式加载和显式加载两种方式。隐式加载是指在使用new等方式创建对象时会隐式调用类加载器把对应的类加载到JVM中;显式加载是指通过直接调用Class.forName()把对应的类加载到JVM中。
- 内存模型(运行时数据区):共享区【方法区、堆】、私有区【虚拟机栈、本地方法栈、程序计数器】、直接内存(不受JVM GC管理)。其中程序计数器是唯一不会出现OOM的内存区。
- 执行引擎:即时编译器、垃圾收集器(按代回收算法:新生代-复制算法(Minor GC),老年代-标记整理算法(Major GC / Full GC))
2 类加载机制
类加载过程由类加载器来完成,即由ClassLoader及其子类实现,有隐式加载和显式加载两种方式。隐式加载是指在使用new等方式创建对象时会隐式调用类加载器把对应的类加载到JVM中;显式加载是指通过直接调用Class.forName()把对应的类加载到JVM中。
参考链接:jvm之java类加载机制和类加载器(ClassLoader)的详解
2.1 类加载过程
-
装载:查找和导入Class文件;
-
链接:把类的二进制数据合并到JRE中;
校验:检查载入Class文件数据的正确性;
准备:给类的静态变量分配存储空间;
解析:将符号引用转成直接引用;
-
初始化:对类的静态变量,静态代码块执行初始化操作
2.2 类加载器
类的加载是动态的,它不会一次性将所有类全部加载后再运行,而是保证程序运行的基础类(如基类)完全加载到JVM中,至于其他类,则在需要时才加载。类分为3类:核心类、扩展类、自定义类。针对这3种类有3种类型的加载器:Bootstrip ClassLoader、Extension ClassLoader、Application ClassLoader。
- 启动类加载器(Bootstrap ClassLoader):负责加载 Java 的核心类( $JAVA_HOME 中 jre/lib/rt.jar 里所有的class),由C++实现的,不是 java.lang.ClassLoader 的子类。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到该类加载器的引用,所以不允许直接通过引用进行操作。
- 扩展类加载器(Extension ClassLoader):负责加载JRE的扩展目录,lib/ext 或者由 java.ext.dirs 系统属性指定的目录中的JAR包的类。由Java语言实现,父类加载器为null。
- 应用类加载器(Application ClassLoader):也被称为系统类加载器(System ClassLoader),负责在JVM启动时加载来自Java 命令的 -classpath 选项、java.class.path 系统属性,或者 CLASSPATH 环境变量所指定的 JAR 包和类路径。程序可以通过 ClassLoader 的静态方法 getSystemClassLoader() 来获取系统类加载器。如果没有特别指定,则用户自定义的类加载器都以此类加载器作为父类加载器。由Java语言实现,父类加载器为ExtClassLoader。
- 自定义类加载器(User ClassLoader):必须继承 java.lang.ClassLoader。
public class Main {
public static void main(String[] args) {
ClassLoader appClassLoader=Main.class.getClassLoader(); //获取AppClassLoader
System.out.println(appClassLoader);
ClassLoader extClassLoader=appClassLoader.getParent(); //获取ExtClassLoader
System.out.println(extClassLoader);
ClassLoader bootClassLoader=extClassLoader.getParent(); //获取BootClassLoader
System.out.println(bootClassLoader);
}
}
sun.misc.Launcher$AppClassLoader@2a139a55
sun.misc.Launcher$ExtClassLoader@7852e922
null
双亲委派机制
双亲委派模型
注意:上图是委派关系,不是继承关系。
双亲委派机制工作原理:如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式,即每个儿子都很懒,每次有活就丢给父亲去干,直到父亲说这件事我也干不了时,儿子自己才想办法去完成。
双亲委派机制的优势:采用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。其次是考虑到安全因素,java核心api中定义类型不会被随意替换,假设通过网络传递一个名为java.lang.Integer的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer,而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改。
2.3 反射机制
反射机制能够实现在运行时对类进行装载,增加了程序的灵活性。反射机制提供的功能主要有:获取类、获取父类、获取接口、获取成员变量、获取构造器、获取成员方法、运行时创建对象、运行时调用方法。
class Demo{
int x;
String s;
Demo(){}
Demo(int x,String s){
this.x=x;
this.s=s;
}
public void show() {
System.out.println(x+","+s);
}
public String show(int x,String s) {
System.out.println(x+","+s);
return "success";
}
}
(1)获取 Class 类
Class c=Class.forName("Demo");
Class c=Demo.class;
Class c=demo.getClass();
(2)获取父类和接口
Class c=String.class;
Class superClass=c.getSuperclass(); //Object
Class[] interfaces=c.getInterfaces(); //Serializable, Comparable<String>, CharSequence
(3)获取类的属性、构造器、方法
Class c=Class.forName("Demo");
//获取属性
Field[] fields1=c.getFields(); //获取所有public属性
Field[] fields2=c.getDeclaredFields(); //获取所有属性
Field field=c.getDeclaredField("x"); //获取指定属性
//获取构造器
Constructor[] constructors=c.getDeclaredConstructors(); //获取所有构造器
Constructor constructor1=c.getDeclaredConstructor(null); //获取无参构造器
Constructor constructor2=c.getDeclaredConstructor(int.class,String.class); //获取指定参数的构造器
//获取方法
Method[] methods1=c.getMethods(); //获取所有public方法
Method[] methods2=c.getDeclaredMethods(); //获取所有方法
Method method=c.getDeclaredMethod("show",int.class,String.class); //获取所有指定方法
(4)创建实例
Class c=Class.forName("Demo");
Demo demo1=(Demo)c.newInstance();
demo1.show();
Constructor constructor=c.getDeclaredConstructor(int.class,String.class); //获取指定参数的构造器
Demo demo2=(Demo) constructor.newInstance(20,"abc");
demo2.show();
0,null
20,abc
(5)调用方法
c=Class.forName("test.Demo");
Method method=c.getDeclaredMethod("show",int.class,String.class); //获取show()方法
String s=(String)method.invoke(c.newInstance(),10,"edf"); //调用show()方法
System.out.println(s); //打印show()方法返回值
10,edf
success
注意:若待调用的方法是静态方法,invoke(Object obj, Object... args) 方法的第一个参数为 null。
3 内存模型
内存模型
- 方法区:用于存储被JVM加载的类信息、常量、静态变量、即时编译后的代码;HotSpot VM把GC分代收集扩展至方法区,使用堆的永久代实现方法区(永久代的内存回收主要针对常量池的回收和类的卸载)。在jdk1.8以前,方法区也叫永久代,主要存放 Class 和 Meta(元数据),GC不会在主程序运行时对永久代进行清理,随着加载的Class的增多而膨胀,最终抛出OOM;jdk1.8中,永久代被元空间取代,元空间并不在虚拟机中,而是使用本地内存,因此,元空间的大小仅受本地内存限制。类的元数据放入本地内存中,字符串池和类的静态变量放入堆中。
- 堆:分为新生代(Eden区:from Survivor:to Survivor=8:1:1)、老年代。新生代占堆内存1/3,由Minior GC回收;老年代占堆内存(2/3)由Major GC(或 Full GC)回收。
- 虚拟机栈:每个方法在执行的同时都会创建一个栈帧(Stack Frame)【局部变量表、操作数栈、动态链接、方法出口】。栈帧随着方法的调用而创建,随着方法的结束而销毁,因此不需要GC。
- 本地方法栈:虚拟机栈为Java方法服务,本地方法栈为native方法服务
- 程序计数器:当前线程所执行的字节码的行号指示器(唯一没有OOM的区域)
堆和栈的比较:
- 内存结构:堆空间被划分为新生代、老年代,新生代被又被划分为Eden区、Survivor区;栈空间没有这种划分,有虚拟机栈和本地方法栈两类,在方法调用时,会产生栈帧,栈帧中主要保存局部变量表、操作数栈、动态链接、方法出口。
- 空间大小:堆空间较大,栈空间较小。
- 存储信息:堆主要用于存储对象;栈主要存储对象引用、基本类型数据、方法调用。
- 共享性:堆是线程共享的,栈是线程私有的,每个线程有自己独立的栈空间。
- 运行速度:堆和栈都位于通用RAM中,但栈处理速度较快,仅次于寄存器。
- 垃圾回收:堆由GC回收,栈不需要GC回收;方法调用结束时,自动销毁栈帧。
- 抛出异常:堆满时,抛出 OutOfMemoryError 异常;栈满时,抛出 StackOverFlowError 异常。
4 垃圾回收算法
4.1 判断是否为垃圾
- 引用计数法:引用和对象相关联,对象的引用数为0即可回收
- 可达性分析:为了解决引用计数法的循环引用问题,Java使用了可达性分析的方法。通过一系列的“GC roots”对象作为起点搜索,2次被标记为不可达的对象,即可回收。
4.2 垃圾回收算法
- 复制算法:将内存分为大小相等的两块,每次只使用其中一块,垃圾回收时,将存活的对象复制到另一块内存中,并清空已使用的内存
- 标记清除算法:标记需要回收的对象,并清除
- 标记整理算法:标记存活对象,将其移相内存一端,清除端边界外的对象
- 分代收集算法:新生代-复制算法(Minior GC),老年代-标记整理算法(Major GC / Full GC)
JDK1.6 中 Sun HotSpot 虚拟机垃圾收集器如下(其中,连线表示可以配套使用):
垃圾收集器
5 引用类型
- 强引用:最常见,如:将对象赋给一个引用变量,它处于可达状态,不会被GC 回收。强引用是造成内存泄漏的主要原因之一
- 软引用:使用SoftReference类实现,当系统内存足够时不会被回收,当系统内存不够时会被回收
- 弱引用:使用WeakReference类实现,只要GC一运行,就会被回收
- 虚引用:使用PhantomReference类实现,不能单独使用,必须和引用队列联合使用,用于跟踪对象被垃圾回收的状态
6 JVM参数调优
JVM参数调优主要是针对堆的参数进行调优。在Java程序界面右击,依次选择【Run As】 ->【Run Configurations】->【Arguments】->【VM arguments】,然后填入要传入的调优参数,如“-Xms512m -Xmx1024m -XX:+PrintGCDetails”,再点击【Run】即可。如下图所示:
JVM参数调优步骤
程序输出结果如下:
Heap
PSYoungGen total 153088K, used 7895K [0x00000000eab00000, 0x00000000f5580000, 0x0000000100000000)
eden space 131584K, 6% used [0x00000000eab00000,0x00000000eb2b5dc8,0x00000000f2b80000)
from space 21504K, 0% used [0x00000000f4080000,0x00000000f4080000,0x00000000f5580000)
to space 21504K, 0% used [0x00000000f2b80000,0x00000000f2b80000,0x00000000f4080000)
ParOldGen total 349696K, used 0K [0x00000000c0000000, 0x00000000d5580000, 0x00000000eab00000)
object space 349696K, 0% used [0x00000000c0000000,0x00000000c0000000,0x00000000d5580000)
Metaspace used 2670K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 294K, capacity 386K, committed 512K, reserved 1048576K
堆设置
- -Xms:初始堆大小,默认为物理内存的1/64
- -Xmx:最大堆大小,默认为物理内存的1/4
- -XX:NewSize=n:设置新生代大小
- -XX:NewRatio=n:设置新生代和老年代的比值。默认为2,表示新生代:老年代=1:2,新生代占堆内存的1/3
- -XX:SurvivorRatio=n:年轻代中Eden区与一个Survivor区的比值。默认为8,表示Eden:Survivor=8:2(Survivor区有2个)
- -XX:PermSize=n:设置永久代初始空间
- -XX:MaxPermSize=n:设置永久代最大空间
收集器设置
- -XX:+UseSerialGC:设置串行收集器
- -XX:+UseParallelGC:设置并行收集器
- -XX:+UseParalledlOldGC:设置并行年老代收集器
- -XX:+UseConcMarkSweepGC:设置并发收集器
垃圾回收统计信息
- -XX:+PrintGC:输出GC处理日志
- -XX:+PrintGCDetails:输出详细的GC处理日志
- -XX:+PrintGCTimeStamps
- -Xloggc:filename
并行收集器设置
- -XX:ParallelGCThreads=n:设置并行收集器收集时使用的收集线程数
- -XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间
- -XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)
并发收集器设置
- -XX:+CMSIncrementalMode:设置为增量模式。适用于单CPU情况。
- -XX:ParallelGCThreads=n:设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数。
声明:本文转自JVM详解
标签:ClassLoader,XX,详解,GC,内存,JVM,Class,加载 From: https://www.cnblogs.com/zhyan8/p/17232801.html