JVM组成结构
JVM结构
区别Java内存模型JMM Java Memory Model
Java内存模型是一套规范,描述了Java程序中的各种变量(线程共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存中读取变量的底层细节。
1.主内存:主内存是所有线程共享的,都能访问的,所有的共享变量都存储于主内存。
2.工作内存:每一个线程有自己的工作内存,工作内存只存储该线程对共享变量的副本,线程对变量的所有操作都必须在工作内存中完成,而不能直接读写主内存中的变量,不同线程之间也不能访问对方工作内存中的变量。
JMM的作用
JMM是一套在多线程读写共享数据时,对共享数据的可见性、原子性、有序性的规则和保障。通过关键字synchronized 和 volatail。
PC Register程序计数器
记住下一条JVM指令的执行地址,通过寄存器来实现。
特点:线程私有;
不会存在内存溢出。
虚拟机栈 JVM Stacks
栈是线程运行时需要的内存空间
栈帧:一个栈帧对应一次方法的调用,每个方法运行时需要的内存。
特点:先进后出
垃圾回收不涉及栈内存
栈内存越大,线程数越少,反而影响效率 500M/2M 500M/1M
方法内的局部变量是否线程安全?
不会有线程安全问题,若是有static修饰,或者逃离了方法的作用范围(return),则需要考虑线程安全问题。
栈内存溢出
1.栈帧过多会导致内存溢出,如递归且无终止条件,只进不出
2..栈内存过大
溢出时报错:StacksOverFlowError
本地方法栈 Native Method Stacks
给本地方法运行提供运行空间
堆 Heap
通过new关键字,创建对象都会使用堆内存
特点:
它是线程共享的,堆中对象都需要考虑线程安全的问题
有垃圾回收机制
堆内存溢出:没使用了会回收,倘若一直在使用且增加,则会溢出
堆内存诊断工具:
1.jps 查看当前系统中有哪些Java进程
2.jmap工具 查看堆内存占用情况 jmap -heap 进程ID
3.jconsole 也能检测死锁,图像化界面,更直观
4.jvisualVM 若内存回收后,占用仍然很高 ,直观的查看内存情况
方法区 MethodArea
定义:所有Java虚拟机共享的区域,存储了跟类结构相关的信息,包括成员变量,方法数据、成员方法、构造方法等。
方法区在虚拟机启动时创建,逻辑上属于堆的一部分。
方法区内存溢出:
类加载器可以用来加载类的二进制字节码
1.8以前会导致永久代内存溢出
1.8以前会导致元空间内存溢出
运行时常量池
反编译:javap - v 类名.class
常量池就是一张表,虚拟机指令根据这张表找到要执行的类名、方法名、参数类型、字面量等信息
运行时常量池,常量池是*.class文件中的,当该类被加载,它的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址。
StringTable 串池
String s1="a";
String s2="b";
String s3="ab"; //String ab
String s4=s1 +s2; //底层为 new StringBuilder().append("a").append("b").toString()
即 new String("ab") 所以s3 == s4 为false
String s5 ="a" + " b" // String ab javac 在编译器的优化,结果在编译期间确定为ab,s3 == s5 true
intern()方法:1.8将这个字符串对象尝试放入串池,如果有则不会放入,没有则放入。
直接内存DirectMemory
通过 ByteBuffer.allocateDirect(_1Gb) 分配一块直接内存,该区域java代码可以直接访问,系统也可以访问。
常用于NIO操作,用于数据缓冲区;
分配回收成本较高,单读写性能高(比IO快很多);
不受JVM内存回收管理。
allocateDirect 内部有个 DirectByteBuffer 该方法内又调用了allocateMemory、setMemory。
直接内存的分配、回收原理:
分配和释放 是通过unsafe 来分配和释放(调用unsafe.freememory)的,不是垃圾回收。
ByteBuffer的实现类内部,使用了 Cleaner(虚引用)来监测ByteBuffer对象,一旦ByteBuffer对象被垃圾回收,呢么就会由ReferenceHandler线程通过Cleaner的clean的方法调用freeMemory来释放直接内存。
垃圾回收
1.如何判断对象可以回收
1.1引用计数法 引用一次+1,为0时表示无人引用,此时可回收。弊端:循环引用,A引用B,B引用A。
1.2可达性分析算法 首先确定一系列根对象 GC Root(肯定不会当成垃圾的对象),垃圾回收前对堆内存中的对象进行一遍扫描,若被根对象直接或间接的引用之不能被回收,反之可以回收。可通过MAT工具来查看哪些GC Root。
1.3四种引用 强引用;软引用、弱引用、虚引用、终结器引用
强引用:只有GC Root对象都不通过强引用引用该对象才能被回收。
软引用:未被强引用引用。垃圾回收并且内存不足时,弱引用的对象被回收。
可以配合引用队列释放软引用本身。
弱引用:只要垃圾回收,不管内存是否足够,都会被回收。
可以配合引用队列释放软引用本身。
虚引用:必须配合引用队列来使用。主要配合ByteBuffer使用,被引用对象回收时会将虚引 用引用入队,由Reference Handler线程调用虚引用相关方法释放直接内存。
终结器引用:必须配合引用队列来使用,在垃圾回收时,终结器引用入队(被引用对象暂 时没有被回收),再由Finalizer线程通过终结器引用找到被引用对象并调用它 的finalize方法,第二次GC时才能回收被引用对象。
2.垃圾回收算法
2.1标记清除 Mark Sweep 优点:速度快,缺点:容易造成内存碎片
2.2标记整理 Mark Compact 优点:无内存碎片 , 缺点:效率低
2.3复制算法 Copy 优点:无内存碎片 , 缺点:占用双倍的内存
Copy分为 From 和 To 两块
3.分带垃圾回收
实际在JVM垃圾回收器中不会说只用一种算法,会结合使用,协同工作,名为分带回收。
- 对象首先分配在伊甸园区域
- 新生代空间不足时,触发Minor GC,伊甸园和from存活的对象使用Copy复制到to中,存活对象的年龄+1,并且交换From To。Minor GC会引发 stop the world就是暂停用户的其它线程方法,等垃圾回收结束,才恢复运行。
- 当对象寿命超过阈值时,最大寿命为15(4bit),会晋升老年代。
- 当老年代空间不足时,会先尝试触发 Minor GC,如果空间仍不足,那么会触发 Full GC,STW时间更长。
- 若老年代空间也不够了,那么会触发 OutOfMemoryError.
如果有个大对象,即使是新生代没有任何东西也放不下,若老年代空间足够会直接放到老年代,此时不会引起GC。
一个线程内的OutOfMemoryError不会导致Java进程的结束。
相关参数
4.垃圾回收器
4.1串行
单线程、堆内存较小、适合个人电脑
4.2吞吐量优先
目标:让单位时间内STW时间最短 如一小时两次,每次0.2秒,共0.4秒
多线程、堆内存较大、多核CPU
4.3响应时间优先
目标:让单次STW时间最短 如一小时五次,每次0.1秒,共0.5秒
多线程、堆内存较大、多核CPU
4.4Garbage First G1
5.垃圾回收调优