JVM结构图
类加载器
Bootstrap Class Loader 启动类加载器 C++
Extension Class Loader 扩展类加载器 java
Application Class Loader 应用程序加载器
启动类加载器 只加载包名为java,javax,sun开头的类
扩展类加载器负责加载JAVA_HOME/lib/ext目录的下的类,开发者可以直接使用扩展类加载器,由系统变量-Djava.ext.dir指定位路径中的类库
应用程序加载器,我们自定义的类一般由它加载
沙箱安全机制
类加载过程
加载、连接、初始化 连接又分为验证、准备、解析
加载通过全类路径名把class文件加载进来
验证会验证class二进制文件是否合法
准备阶段为静态变量分配内存,并默认初始化
所谓解析就是指在常量池中找到类、接口、方法、字段的符号引用,并将其替换为直接引用的过程(字节码里面都是符号引用)
初始化,执行()方法,此法方法包含了所有类变量的赋值和静态代码语句块的执行代码,静态语句块只能对后面的静态变量进行赋值,而不能对其进行访问。
class初始化的时机(六大初始化时机)
创建类的实例
访问类中的某个静态变量或者对静态变量进行赋值
主动调用类的静态方法
Class.forName(“全路径类名”)
完成子类的初始化,也会完成本类的初始化
该类是程序引导入口(main入口或者test入口)
双亲委派机制
当一个类接收到加载请求,这个类不会立即去加载,而是委派给自己的父类去加载,每个层次的类都是如此,因此,所有的加载请求都会传入到父类哪里,只有父类加载不了的时候子类才会去加载。
采用双亲委派机制好处就是,使用不同类的加载器最后得到的同一个对象,不会污染Java的源代码。(防止恶意代码干涉善意代码)
本地接口
本地接口的作用就是融合不同的编程语言为Java所用。在Java代码中被native修饰的代码为本地方法。
PC寄存器(程序计数器)
是一块较小的内存空间,可以看作是指向当前线程所执行字节码的行号指示器。字节码解释器工作就是改变计数器的值运行下一行所需要执行的字节码指令,分支,循环,跳转,异常处理,线程恢复都需要依赖计数器完成。
方法区
方法区和堆一样,是各个线程共享的区域,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码数据等. 1.8以后为元空间。
方法区是一套规范,有不同的实现,比如永久代和元空间。
虚拟机栈
java虚拟机栈也是线程私有的,它的生命周期和线程保持一致。他是存储当前线程运行方法时所需要的数据、指令、返回地址。在每个方法执行时,虚拟机栈都会创建一个栈帧(Stack Frame),用于存储:局部变量表、操作数栈、动态链接、方法出口等信息。
堆
此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。
堆被划分成两个不同的区域:新生代 ( Young )1/3、老年代 ( Old )2/3。新生代 ( Young ) 又被划分为三个区域:Eden8/10、From Survivor(s0)1/10、To Survivor(s1)1/10。进一步划分的目的是更好地回收内存,或者更快地分配内存。
对象都会首先在 Eden 区域分配,在进行第一次垃圾回收后存活的对象复制一份放在s0区,删除eden区的对象,在第二次进行垃圾回收时扫描eden区和s0区的对象,把存活的对象复制一份放在s1区,对象的年龄加1(Eden区->Survivor 区后对象的初始年龄变为1),这时s1区变为了s0区,s0区变为了s1区。这里有一个s0,s1区交换的过程。当它的年龄增加到一定程度(默认为15岁),就会被晋升到老年代中。
堆参数
堆内存参数
-Xms 设置初始分配大小,默认为物理内存的1/64
-Xmx 最大分配内存,默认为物理内存的1/4
-XX:+PrintGCDetails 输出详细的GC处理日志
查看CPU核数 Runtime.getRuntime().availableProcessors()
查看最大内存 Runtime.getRuntime().maxMemory()
查看当前内存 Runtime.getRuntime().totalMemory()
生产环境下最大内存和当前内存必须一样大
四大垃圾回收算法
引用计数法
每有一个引用对象引用值加1,没有引用引用值为0.当引用值为0的时候,让垃圾回收器回收。
缺点:
1.每次对对象赋值时都要维护引用计数器,且计数器本身也有一定消耗。
2.较难处理循环引用
引用计数法没有被采用。
复制算法
为了解决效率问题,“复制”收集算法出现了。它可以将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对内存区间的一半进行回收。
复制算法不会产生碎片,缺点耗费空间,用在年轻代下·
在一次GC后,通过Tracing从From中找到存活对象,拷贝到To中
From和To交换身份,下次内存分配从To开始
标记清除
算法分为标记和清楚阶段:首先标记出所有需要回收的对象,在标记完后统一回收所有被标记的对象。
缺点:会产生大量不连续的内存碎片。而且比复制算法效率低,在进行GC的时候要停止应用程序,这会导致用户的体验会非常差劲。
标记压缩(标记整理)
标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。
缺点:效率低