JVM
- 1 JVM内存模型
- 1.1 类装载器ClassLoader
- 1.2 程序计数器(Program Counter Register)
- 1.3 虚拟机栈(Java Virtual Machine Stacks)
- 1.4 本地方法栈(Native Method Stacks)
- 1.5 方法区
- 1.6 堆
- 1.7 jvm参数
- 2 对象创建
- 2.1 给对象分配内存
- 2.2 线程安全性问题
- 2.3 初始化对象
- 2.3.1 对象结构
- 2.3.2 对象的访问定位
- 3 垃圾回收
- 3.1 如何判断对象为垃圾对象
- 3.2 如何回收
- 3.2.1 回收策略
- 3.2.2 垃圾回收器
- 4 class文件
- 4.1 class文件简介
- 4.1.1 类文件结构
1 JVM内存模型
1.1 类装载器ClassLoader
负责加载class文件
BootStrap启动类加载器,C语言实现,加载rt.jar里的所有class(包括Object)
Extension扩展类加载器
APPClassLoader应用程序类加载器,也叫系统类加载器,加载当前应用的classpath的所有类
也可以自定义加载器,只要继承ClassLoader就可以
双亲委派机制:
委派给父类ClassLoader去加载,为了保证原始的rt.jar下的代码不被篡改。
- AppClassLoader先不去加载,委派给ExtClassLoader去加载,
- ExtClassLoader也不去加载,委派给BootStrap加载,
- 只有在父类加载不到的时候,才会自己去加载。
反射有几种方式?
三种。
- 通过Object类的getClass方法来获取;
- 使用.class的方式;
- 使用Class.forName方法
native方法(本地方法)
本地接口的作用是融合不同的编程语言为java所用
1.2 程序计数器(Program Counter Register)
是JVM中一块较小的内存区域
保存着当前线程所执行的字节码的行号。
如果正在执行的是native方法,这个计数器的值为undefined。
JVM的多线程是通过线程轮流切换并分配CPU执行时间片的方式来实现的,任何一个时刻,一个CPU都只会执行一条线程中的指令。为了保证线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各线程间的程序计数器独立存储,互不影响。
此区域是唯一一个在java虚拟机规范中没有规定任何OutOfMemoryError情况的区域,因为程序计数器是由虚拟机内部维护的,不需要开发者进行操作。
1.3 虚拟机栈(Java Virtual Machine Stacks)
存放
- 本地变量(8中基本类型,对象的引用变量+实例方法):输入输出参数、方法中的变量
- 栈操作:记录入栈、出栈操作
是线程隔离的,每创建一个线程时就会对应创建一个Java栈,即每个线程都有自己独立的虚拟机栈。
栈帧包含局部变量表、操作数栈、动态链接、方法返回地址等信息,每一个方法从调用到最终返回结果的过程,就对应一个栈帧从入栈到出栈的过程。
当前栈帧:栈顶的栈帧才是有效的,与这个栈帧相关联的方法称为当前方法,当前活动帧栈始终是虚拟机栈的栈顶元素。
局部变量表:存放了编译期可知的各种基本数据类型和对象引用类型。通常我们所说的“栈内存”指的就是局部变量表这一部分。
局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧分配多少内存是固定的,运行期间不会改变局部变量表的大小。
64位的long和double类型的数据会占用2个局部变量空间,其余的数据类型只占用1个。
1.4 本地方法栈(Native Method Stacks)
与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。
1.5 方法区
存放类/接口信息+常量+静态变量+运行时常量区,是一种定义/接口,一种规范。
其中类信息包含包名、类名、继承的对象、接口定义、方法定义、类变量、类常量
常量池( Constant Pool )是方法区的一部分 Class 文件除了有类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池,这部分内容将在类加载后进入方法区的运行时常量池中存放。
永久带(jdk7)/元空间(jdk8)是方法区的实现。
不会被垃圾回收,关闭jvm时释放此区域所占内存。方法区溢出报错java.lang.OutOfMemoryError: PermGen space,说明程序启动时需要加载大量第三方jar包,例如在一个tomcat下不熟太多应用。可以增加该区域内存空间。
Jdk1.6 及之前: 有永久代 , 常量池 1.6 在方法区
Jdk1.7 有永久代,但已经逐步“去永久代”,常量池 1.7 在堆
Jdk1.8 及之后: 无永久代,常量池 1.8 在元空间
1.6 堆
类加载器读取了类文件后,需要把类、方法、常变量放到堆内存中,保存所有引用类型的真实信息。
小对象内存分配:先进栈上分配,然后是eden区
大对象直接进如老年代
S0区线程区
YoungGC只会回收eden
1.7 jvm参数
-Xms设置堆的最小空间大小。
-Xmx设置堆的最大空间大小。
-Xmn:设置年轻代大小
-XX:NewSize设置新生代最小空间大小。
-XX:MaxNewSize设置新生代最大空间大小。
-XX:PermSize设置永久代最小空间大小。
-XX:MaxPermSize设置永久代最大空间大小。
-Xss设置每个线程的堆栈大小
-XX:+UseParallelGC:选择垃圾收集器为并行收集器。此配置仅对年轻代有效。即上述配置下,年轻代使用并发收集,而年老代仍旧使用串行收集。
-XX:ParallelGCThreads=20:配置并行收集器的线程数,即:同时多少个线程一起进行垃圾回收。此值最好配置与处理器数目相等。
典型JVM参数配置参考:
java-Xmx3550m-Xms3550m-Xmn2g-Xss128k
-XX:ParallelGCThreads=20
-XX:+UseConcMarkSweepGC-XX:+UseParNewGC
-Xmx3550m:设置JVM最大可用内存为3550M。
-Xms3550m:设置JVM促使内存为3550m。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。
-Xmn2g:设置年轻代大小为2G。整个堆大小=年轻代大小+年老代大小+持久代大小。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,官方推荐配置为整个堆的3/8。
-Xss128k:设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。更具应用的线程所需内存大 小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000 左右。
2 对象创建
2.1 给对象分配内存
- 指针碰撞
- 空间列表
2.2 线程安全性问题
- 线程同步(性能较差,同步开销)
- 本地线程分配缓冲(TLAB)
2.3 初始化对象
2.3.1 对象结构
- Header( 对象头)
- 自身运行时数据( Mark Word)
– 哈希值
– GC 分代年龄
– 锁状态标志
– 线程持有锁
– 偏向线程 ID
– 偏向时间戳 - 类型指针
- 数组长度(只有数组对象才有)
- InstanceData
相同宽度的数据分配到一起( long,double - Padding (对齐填充
8 个字节的整数倍
2.3.2 对象的访问定位
- 使用句柄
好处:如果堆中的对象内存地址变更之后,句柄的指向变更就可以了,栈中对象的引用不需要改变
缺点:性能 - 直接指针(HotSpot)
3 垃圾回收
3.1 如何判断对象为垃圾对象
引用计数法、可达性分析
- 引用计数法
在对象中添加一个引用计数器,当有地方引用这个对象的时候,计数器 +1 ,当失效的时候,计数器 -1
verbose:gc XX:+PrintGCDetails - 可达性分析
GCRoot 的对象
- 虚拟机栈 局部变量表中的
- 方法区的类属性所引用的对象
- 方法区的常量所引用的对象
- 本地方法栈所引用的对象
3.2 如何回收
3.2.1 回收策略
标记清除、复制、标记整理、分代算法
标记清除
分成标记和清除两个阶段
缺点:效率问题、内存碎片复制
缺点:空间利用率低、只能用一般的空间
堆
新生代(Eden、Survivor)
老年代(Tenured Gen)方法区
虚拟机栈
本地方法栈
程序计数器
4. 标记整理
5. 分代算法
3.2.2 垃圾回收器
Serial、ParNew、CMS、G1、ZGC
4 class文件
public static void main(String args){ int a = 2; int b = 400; int c = a+b; System.out.println(c); }用javap命令反编译后
public static void main(java.lang.String); descriptor: (Ljava/lang/String;)V //L代表引用类型 V代表没有返回值 flags: ACC_PUBLIC,ACC_STATIC //方法描述符 code: stack=2,locals=4,args_size=1 //操作数栈深度为2,本地变量表最大长度4(单位slot),参数1个 0: iconst_2 //常量2压栈 1: istore_1 //常量2出栈并保存到变量1里 2: iconst_3 //常量400压栈 3: istore_2 //常量400出栈并保存到变量2里接上
4.1 class文件简介
https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html
class文件是一组已8位字符为基础单位的二进制流,没有任何分隔符,节省空间,执行效率高。
class文件只有两种数据类型:无符号数、表
4.1.1 类文件结构
ClassFile { u4 magic; //魔数 u2 minor_version; //jdk版本号 8对应52 7对应51 类推 u2 major_version; //jdk版本号 u2 constant_pool_count; cp_info constant_pool[constant_pool_count-1]; u2 access_flags; u2 this_class; u2 super_class; u2 interfaces_count; u2 interfaces[interfaces_count]; u2 fields_count; field_info fields[fields_count]; u2 methods_count; method_info methods[methods_count]; u2 attributes_count; attribute_info attributes[attributes_count];}
魔数:代表了文件类型cafe babe
java、grovy、jruby、scala都可以编译成class文件。这些语言只需要增加对应的编译器,按照class规范翻译成class文件。