字节码
通过javac后产生JVM可以理解的代码就是字节码,扩展名为.class文件;Java的编译与解释并存
JVM被分为三个主要的子系统
- 类加载器子系统
- 运行时数据区
- 执行引擎
类加载机制
- 加载:java类运行时候会生成一个class字节码文件,加载的过程就是去我们的操作系统寻找这个class文件。
- 链接:这个过程就是把class文件加载到java虚拟机。
- 初始化:在虚拟机中根据class文件进行初始化。
- 使用:这个过程大家都明白。
- 卸载:使用完了,java虚拟机进行清理。
Class.forName和ClassLoader区别
- Java类装载过程 加载–>验证–>准备–>解析–>初始化–>使用–>卸载
- 1.Class.forName(className)除了将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块,内部实际调用的方法是Class.forName(className,true,classloader);
className:表示我们要加载的类名 true:指Class被加载后是不是必须被初始化。
不初始化就是不执行static的代码即静态代码,在这里默认为true,也就是默认实现类的初始化。
ClassLoader.getClassLoader(caller):表示类加载器,到这你会发现forNanme其实也是使用的ClassLoader类加载器加载的
caller:指定类加载器 在这里你可以指定是否在class加载后被初始化。而且底层还是使用的classloader加载的
- 2.ClassLoader.loadClass(className)方法,内部实际调用的方法是 ClassLoader.loadClass(className,false);第二个参数表示是否进行链接。链接包含以上的三个步骤,验证–>准备–>解析。在准备的时候便会给类的静态变量分配并初始化内存空间。false表示不进行链接,则无法执行静态代码块或静态对象。classloader只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块。大致流程是先判断class是否已经被加载,如果被加载了那就重新加载,如果没有加载那就使用双亲委派原则加载。加载的时候并没有指定是否要进行初始化
一. JVM 体系结构
栈、本地方法栈、程序计数器不会发生GC;jvm调优主要在堆,方法区有一小部分。
二. 类加载器和双亲委派机制
类加载器作用:加载.class文件。
新建的对象放入堆里面,引用(地址)放到栈,其中引用指向堆里面对应的对象。
双亲委派机制: 当一个类收到了类加载请求,他首先不会尝试自己去加载这个类,而是把这个请求委派给父类去完成,每一个层次类加载器都是如此,因此所有的加载请求都应该传送到启动类加载其中,只有当父类加载器反馈自己无法完成这个请求的时候(在它的加载路径下没有找到所需加载的Class), 子类加载器才会尝试自己去加载。
双亲委派的好处: 把自己编写的类放入java.lang包,自己定义的,不是jar包里面的。这样会报错,因为加载自己编写的类会被委托到BootStrap,这个加载器会把jar包里面的String类加载进内存,所以加载不到自己的类。可以把自己的类写进去bin/ext包
三. 堆内存
一个JVM只有一个堆内存,堆内存的大小是可以调节的。
新生区 : Eden、幸存0区、幸存1区。对象在这里诞生、成长、甚至死亡;所有对象都是在eden区new出来的
老年代: 默认新生区中的对象经过15次GC (15的原因在于java头文件中有四个bit代表年龄)后会进入老年代
永久区: jdk1.8以后,叫元空间(方法区在这里,常量池在方法区里)。这个区域是常驻内存的,用来存放jdk自身携带的class对象。
方法区: 方法区是被所有线程共享的,所有的字段和方法字节码,以及一些特殊方法,如构造函数,接口代码也在此定义,简单说,所有定义方法的信息都保存在该区域,此区域属于共享空间;存储内容:static、final、.class、常量池
元空间(非堆)使用的是直接内存,与新生代和老年代分开。
四. 沙箱机制
Java安全模型的核心就是Java沙箱(sandbox) , 什么是沙箱?沙箱是一个限制程序运行的环境。沙箱机制就是将Java代码限定在虚拟机(JVM)特定的运行范围中,并且严格限制代码对本地系统资源访问,通过这样的措施来保证对代码的有效隔离,防止对本地系统造成破坏。
沙箱主要限制系统资源访问,那系统资源包括什么? CPU、内存、文件系统、网络。不同级别的沙箱对这些资源访问的限制也可以不一样。 所有的Java程序运行都可以指定沙箱,可以定制安全策略。 在Java中将执行程序分成本地代码和远程代码两种,本地代码默认视为可信任的,而远程代码则被看作是不受信的。对于授信的本地代码,可以访问一切本地资源。而对于非授信的远程代码在早期的Java实现中,安全依赖于沙箱Sandbox)机制。
基本组件
- 字节码校验器(bytecode verifier) :确保Java类文件遵循Java语言规范。这样可以帮助Java程序实现内存保护。但并不是所有的类文件都会经过字节码校验,比如核心类
- 类裝载器(class loader) :其中类装载器在三个方面对Java沙箱起作用 :1. 它防止恶意代码去干涉善意的代码; 2.它守护了被信任的类库边界; 3.它将代码归入保护域,确定了代码可以进行哪些操作。
- 存取控制器(access controller) :存取控制器可以控制核心API对操作系统的存取权限,而这个控制的策略设定,可以由用户指定
- 安全管理器(security manager) : 是核心API和操作系统之间的主要接口。实现权限控制,比存取控制器优先级高
- 安全软件包(security package) : java.security下的类和扩展包下的类,允许用户为自己的应用增加新的安全特性,包括: 安全提供者 、 消息摘要、 数字签名、 加密、 鉴别
五. Native
凡是带了native关键字的,说明java的作用范围达不到了,会去调用底层c语言的库,会进入本地方法栈 ,调用本地方法本地接口 JNI (Java Native Interface)
JNI作用: 开拓Java的使用,融合不同的编程语言为Java所用,最初: Java诞生的时候C、C++横行,想要立足,必须要有调用C、C++的程序,它在内存区域中专门开辟了一块标记区域: Native Method Stack,登记native方法,在最终执行的时候,加载本地方法库中的方法通过JNI
本地方法栈: 它的具体做法是Native Method Stack中登记native方法,在( Execution Engine )执行引擎执行的时候加载Native Libraies
六. 堆内存调优
当出现OOM(堆内存满了)故障,怎么解决
工具:
1.能够看到代码第几行出错:内存快照分析工具,MAT, Jprofiler
2.Dubug, 一行行分析代码
设置堆内存的大小:-Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
七. 垃圾回收(GC)
确定垃圾的方式有两种:
引用计数法: 引用计数法是给每个对象设置计数器。有一个地方引用他,那么这个计数器+1,引用失效计数器-1,当计数器为0,说明对象变成了‘垃圾’,在下次扫描标记的时候会进行回收。 该算法有一个缺陷,就是循环引用问题,也可以说是相互引用,这时GC无法进行回收。
可达性算法: 怎么来分析每个对象的可达性呢?原因在于程序中new对象的时候,GC就开始监控对象的地址和大小。他采用的是 有向图的方式来管理对象,那么就可以通过图的连通性来分析对象是否可达。图的起始点被称为GC Roots。
两种比较经典的垃圾回收器:
CMS(Concurrent Mark Sweep) 收集器: 是一种以获得最短回收停顿时间为目标的收集器,标记清除算法,运作过程:初始标记,并发标记,重新标记,并发清除,收集结束会产生大量空间碎片。
G1收集器: 标记整理算法实现,运作流程主要包括以下:初始标记,并发标记,最终标记,筛选标记。不会产生空间碎片,可以精确地控制停顿。
CMS收集器是老年代的收集器,可以配合新生代的Serial和ParNew收集器一起使用;
G1收集器收集范围是老年代和新生代,不需要结合其他收集器使用;
CMS收集器以最小的停顿时间为目标的收集器;
G1收集器可预测垃圾回收的停顿时间
CMS收集器是使用“标记-清除”算法进行的垃圾回收,容易产生内存碎片
G1收集器使用的是“标记-整理”算法,进行了空间整合,降低了内存空间碎片。
①引用计数法:记录每个对象 的引用次数,长时间没有被引用的对象就被清除掉。(很少使用)
②复制算法: 将内存空间分为两块,每次使用其中一块,在垃圾回收时,将正在使用的内存中的存活对象(eden+from)复制到未使用的内存块(to)中,之后清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收
GC roots的对象
-
虚拟机栈中引用的对象
-
方法区中类静态属性引用的对象
-
方法区中常量引用的对象
-
本地方法栈中引用的对象
-
优点:没有内存碎片
-
缺点:浪费内存空间,多了一半to空间永远是空的。
-
复制算法最佳使用场景:对象存活度较低的时候 -> 新生区
-
当to空间已满,或者老年对象,都会直接进入老年代,此时 eden 和 from 中剩余的对象就是垃圾对象,可以直接清空
-
复制算法比较合适新生代,垃圾比较多
③标记清除算法: 先标记可达对象,然后清除未被标记的对象
老年代一般是由标记清除或者是标记清除与标记压缩的混合实现
- 优点:不需要额外的空间。
- 缺点:两次扫描,效率低,递归全堆对象遍历,GC 的时候还要停止应用程序,会产生内存碎片。
Jprofiler
-Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
-XX:+PrintGCDetails
常见JVM面试题