-
JVM位置
jvm是运行在操作系统上的程序,它只负责运行Java程序; -
JVM体系结构
-
类加载器
作用:加载class文件
public class AnalysisJVM {
int age = 1;
public static void main(String[] args) {
AnalysisJVM a1 = new AnalysisJVM();
AnalysisJVM a2 = new AnalysisJVM();
AnalysisJVM a3 = new AnalysisJVM();
System.out.println(a1.hashCode());//356573597
System.out.println(a2.hashCode());//1735600054
System.out.println(a3.hashCode());//21685669
Class c1 = a1.getClass();
Class c2 = a2.getClass();
Class c3 = a3.getClass();
System.out.println(c1.hashCode());//1956725890
System.out.println(c2.hashCode());//1956725890
System.out.println(c3.hashCode());//1956725890
ClassLoader classLoader = c1.getClassLoader();
System.out.println(classLoader);//AppClassLoader
System.out.println(classLoader.getParent());//ExtClassLoader jre/lib/ext/*
System.out.println(classLoader.getParent().getParent());//null (根加载器由C编写,java程序获取不到) jre/lib/rt.jar
}
}
1. 虚拟机自带的加载器:根(或启动类)加载器
2. 扩展类加载器
3. 应用程序(或系统类)加载器
-
双亲委派机制
保证安全的机制,实例化类的时候先从下向上(AppClassLoader->ExtClassLoader->BootStrapClassLoader)全部找一遍这个类,但最终永远实例化最上层的类。即BootStrapClassLoader访问路径下没有才会实例化ExtClassLoader访问路径下类,如果ExtClassLoader访问路径下也没有才会实例化AppClassLoader访问路径下的类(我们自己写类)1. 类加载器收到类加载请求 2. 将这个请求向上委托给父类加载器去完成,一直向上委托,直到启动类加载器 3. 启动类加载器检查是否能够加载当前这个类,能加载就结束,使用当前加载器,否则抛出异常,通知子加载器进行加载 4. 重复步骤3
-
沙箱安全机制
Java安全模型的核心就是Java沙箱(sandbox),什么是沙箱?沙箱是一个限制程序运行的环境。沙箱机制就是将 Java 代码限定在虚拟机(JVM)特定的运行范围中,并且严格限制代码对本地系统资源访问,通过这样的措施来保证对代码的有效隔离,防止对本地系统造成破坏。沙箱主要限制系统资源访问,那系统资源包括什么?——CPU、内存、文件系统、网络。不同级别的沙箱对这些资源访问的限制也可以不一样。
所有的Java程序运行都可以指定沙箱,可以定制安全策略。 -
Native
- 凡是带了native关键字的,说明java的作用范围达不到了,会去调用底层C语言的库;
- 带native修饰符的会进入本地方法栈
- 调用本地方法的本地接口 JNI
- JNI的作用:扩展Java的使用,融合不同的编程语言为Java所用!最初是为了C C++
- Java诞生的时候C C++盛行,想要立足必须要有调用C C++的程序
- 它在内存区域中专门开辟了一块标记区域,本地方法栈 Native Method Stack,,等级native方法
- 在最终执行的时候,加载本地方法库中的的方法,通过JNI
- 用例:Java程序驱动打印机,管理系统,掌握即可,在企业级应用中较为少见!
- PC寄存器
程序计数器(Program Counter Register)
https://blog.csdn.net/niugang0920/article/details/104687699?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.channel_param
1. 作用
PC寄存器用来存储指向下一条指令的地址,也即将要执行的指令代码。由执行引擎读取下一条指令。
- 它是一块很小的内存空间,几乎可以忽略不记。也是运行速度最快的存储区域。
- 在JVM规范中,**每个线程都有它自己的程序计数器,是线程私有的,生命周期与线程的生命周期保持一致。**
- 任何时间一个线程都只有一个方法在执行,也就是所谓的当前方法。程序计数器会存储当前线程正在执行的java方法的JVM指令地址:或者,如果是在执行native方法,则是未指定值(undefined)
- 它是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
- 字节码解释器工作时就是通过改变这个计数器的值来选取下一个条需要执行的字节码指令。
- 它是唯一一个在Java虚拟机规范中没有规定任何OOM情况的区域。
2. PC寄存器为什么会被设定为线程私有
所谓多线程在一个特定的时间段内只会执行其中某一个线程的方法,CPU会不停的做任务切换,这样必然导致经常中断或回复。为了能够准确地记录各个线程正在执行的当前字节码指令地址,最好的办法自然是为每一个线程分配一个pc寄存器,这样一来各个线程之间便可以进行独立计算,从而不会出现相互干扰的情况。
-
方法区 Method Area
方法区被所有线程共享,所有字段和方法字节码,以及一些特殊方法,如构造函数,接口代码也在此定义;简单说,所有定义的方法信息都保存在方法区,此区域属于共享区域
静态变量(static),常量(final),类信息(构造方法 接口定义)(Class/模板),运行时的常量池存在方法区中,但是实例变量存在堆内存中,和方法区无关 -
栈
栈内存,主管程序的运行,生命周期和线程同步,一旦线程结束,栈就结束 ;
线程结束,栈内存也就释放;
对于栈来说,不存在垃圾回收问题
栈存储:8大基本类型+对象的引用+实例的方法
栈 堆 方法区交互关系 -
三种JVM
Java HotSpot™ 64-Bit Server VM (build 25.152-b16, mixed mode)
BEA JRockit
IBM J9VM -
堆
Heap,一个JVM只有一个堆内存,堆内存的大小是可以调节的
类加载器读取了类文件后,一般会把什么东西放到堆中?类 方法 变量 常量 引用类型的真实对象
堆内存还要细分为三个区域:
- 新生区(伊甸园区)Young/New
- 养老区 old
- 永久区 perm
GC垃圾回收主要在伊甸园区和养老区;幸存区是两者的过渡;假设内存满了就会报OOM(OutOfMemoryError),内存不够
JDK8以后,永久存储区改名为元空间
堆内存分布
元空间逻辑上存在物理上不存在
- 新生区/老年区
类诞生和成长的地方,甚至死亡
99%的对象都是临时对象,无法进入到老年区 - 永久区
这个区域是常驻内存的,用来存放JDK自身携带的Class对象,Interface元数据。存储的是Java运行时的一些环境或类信息,这个区域不存在垃圾回收关闭虚拟机就会释放这个区域的内存
永久区出现OOM:
- 一个启动类加载了大量的第三方jar包;
- Tomcat部署了太多应用;
- 大量生成的反射类,不断被加载,直到内存满。
- 堆内存调优
1.尝试扩大堆内存看结果
2.分析内存,使用内存分析工具(Eclipse MAT Idea JProfiler插件)定位问题
- -Xms8m 设置初始化内存分配大小 默认1/64
- -Xmx8m 设置最大分配内存,默认1/4
- -Xms8m -Xmx8m -XX:+PrintGCDetails 程序结束后打印GC信息
- -Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError 当报出OOM时,dump堆内存文件
-
GC:分代收集算法
两个对象相互调用造成循环引用时会产生内存泄漏,引用计数法无法解决内存泄漏问题
//此时obj1和obj2的引用计数都为1
Object obj1 = new GcObject();
Object obj2 = new GcObject();
//建立循环引用:此时obj1和obj2的引用计数都为2
obj1.instance = obj2;
obj2.instance = obj1;
//引用释放后obj1和obj2就形成了循环引用
//此时obj1和obj2的引用计数都为1,互相引用,没有完整的根可达链
obj1 = null;
obj2 = null;
可达性分析算法(根搜索算法):
GC root不是对象是引用,GCroot有以下几种:
1.虚拟机栈的栈帧的局部变量表所引用的对象
2.本地方法栈的JNI所引用的对象
3.方法区的静态变量和常量所引用的对象
引用链上的所有对象都叫做GC可达对象(存活对象),不在引用链上的对象为GC不可达对象
串行(STW Stop The World):执行GC时业务线程不能抢占CPU,执行业务线程时不发生GC ;消耗时间,系统性能低
常用算法:标记清除法 标记整理(标记压缩) 复制算法 引用计数法
标记清除算法:扫描对象,对活着的进行标记;对没有标记的对象进行清除;
先标记再清楚再跑业务线程
坏处:递归两次扫描,严重浪费时间;清理出的内存不连续会产生内存碎片
好处:不需要额外的空间
标记清除前
标记清除后:产生碎片,一旦进来大的对象,需要连续空间,这些碎片空间无法使用
复制算法:为了保证两个幸存区始终有一个是干净的,新生代主要是用的复制算法
好处:没有内存碎片
坏处:浪费了内存空间,多一半永远为空的to区
复制算法最佳使用场景:对象存活度较低,所以用在新生区
将内存分为两部分,使用其中一半
将上图的存活对象递归整理成连续空间口转移到另一半,使原来存数据一半变成空内存,用来进行下一次的同样操作
标记压缩算法:是标记清除算法的优化,再次扫描向一端移动存活的对象,防止内存碎片产生
坏处:多了一次移动成本
引用计数法:给每个对象都分配一个计数器,计数器本身会消耗空间;
内存效率:复制算法>标记清除算法>标记压缩算法(时间复杂度)
内存整齐度:复制算法>标记压缩算法>标记清除算法
内存利用率:标记压缩算法=标记清除算法>复制算法
- 垃圾收集器
serial 1.3之前 新生代 单线程 STW
serial old 老年代 单线程 标记整理算法 STW
ParNew serial的多线程版本 更关注STW的停顿时间(交互式应用 )
Parallel Scavenge 更关注吞吐量 新生代算法
Parallel Old 老年代算法
CMS(ConMarkSweep):并发类垃圾回收器,不去STW,业务线程与GC线程同时进行
标记清除算法
Garbage First:比CMS停顿时间更短,可以随意设置并且解决了空间碎片的问题;jdk1.7出现,1.8推荐 1.9默认
- JMM:JavaMemoryMode
JMM的关键技术点都是围绕着多线程的原子性、可见性和有序性来建立的,这也是Java解决多线程并行机制的环境下,定义出的一种规则,意在保证多个线程间可以有效地、正确地协同工作.