JVM
- 请你谈谈你对jvm的理解?java8虚拟机和之前的变化更新?
- 什么是OOM,什么是StackOverFlowError?怎么分析?
- jvm的常用调优参数有哪些?
- 内存快照如何抓取,怎么分析Dump文件
- 谈谈JVM,类加载器你的认识?
JVM的位置
JVM的体系结构
类加载器
作用:加载class文件【类是模板,而对象是具体的】
- 虚拟机自带的加载器
- 启动类(根)加载器
- 扩展类加载器
- 应用程序加载器
- 双亲委派机制
沙箱安全机制
Native,本地方法JNI,调用本地方法,可调用C,C++
凡是带了native 关键字的,说明java的作用范围达不到,会去调用底层C语言库!
会进入本地方法栈,调用本地方法
JNI作用:扩展Java使用,融合不同的编程语言为Java所用!最初:C/C++
Java诞生的时候,C、C++横行,想要立足,必须要调用C、C++的程序
它在内存区域中专门开辟了一块标记区域:Native Method Stack,等级native方法
在最终执行的时候,加载本地方法库中的方法通过JNI
PC寄存器
程序计数器:每个线程都有一个程序计数器,是线程私有的,就是一个指针,指向方法区中的方法字节码(用来存储指向一条指令的地址,即将要执行的指令代码),在执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不计
方法区
Method Area方法区
方法区是被所有线程共享,所有字段和方法字节码,以及一些特殊方法,如构造函数,接口代码也在此定义,简单说,所有定义的方法的信息都保存在该区域,此区域属于共享区间;
静态变量、常量、类信息(构造方法、接口定义)、运行时常量池存在方法区中,但是实例变量存在堆内存中和方法区无关
:static final Class 常量池
栈
先进后出,;类似:桶,main方法先执行最后结束~
队列:先进先出(FIFO:First input First Output)
喝多了吐就是栈,吃多了拉就是队列
栈内存:主管程序的运行,生命周期,线程同步;
线程结束,栈内存也就释放,对于栈来说,不存在垃圾回收问题
一旦线程技术
三种JVM(了解)
堆(Heap)
一个JVM只有一个堆,堆内存的大小是可以调节的!
类加载器读取了类文件后,一般会把什么东西放到堆中?类、方法、常量变量
GC垃圾回收,主要是在伊甸园区和养老区
假设内存满了,OOM,堆内存不够!
-
新生区(伊甸园区)Young/New
-
类:诞生 -> 和成长的地方,甚至死亡;
-
伊甸园,所有的对象都在伊甸园区new出来的
-
幸存者区(0,1)
经过研究99%的对象都是临时对象!
-
-
老年区 old
-
永久区(元空间)
这个区域,用来存放jdk自身携带的Class对象,Interface元数据,存储的是java运行时的一些环境或者类信息,这个区域不存在垃圾回收,关闭JVM虚拟机就回什邡这个区域的内存
一个启动类,加载了大量的第三方jar包,Tomcat部署类太多的应用,大量动态生成的反射类,不断地被加载,知道内存满,就会OOM
元空间,逻辑上存在,物理上不存在!
package com.tang;
public class Demo02 {
public static void main(String[] args) {
//返回虚拟机视图使用的最大内存
long maxMemory = Runtime.getRuntime().maxMemory(); //字节1024*1024
//返回jvm的总内存
long totalMemory = Runtime.getRuntime().totalMemory();
System.out.println("max->"+maxMemory+"字节\t"+ (maxMemory/(double)1024/1024));
System.out.println("max->"+totalMemory+"字节\t"+ (totalMemory/(double)1024/1024));
// System.out.println(totalMemory);
//默认情况下:分配的总内存是电脑内存的 1/4, 而初始化的内存 :1/64
//OOM,
//1. 尝试调大堆内存空间
//2.分析内存,看一下哪个地方出现问题(专业工具)
//-Xms1024m -Xmx1024m -XX:+PrintGCDetails
//Young + old = totalMemory
// 305664K + 699392K =1,005,056k = 981.5M
}
}
max->1029177344字节 981.5
max->1029177344字节 981.5
Heap
PSYoungGen total 305664K, used 15729K [0x00000000eab00000, 0x0000000100000000, 0x0000000100000000)
eden space 262144K, 6% used [0x00000000eab00000,0x00000000eba5c420,0x00000000fab00000)
from space 43520K, 0% used [0x00000000fd580000,0x00000000fd580000,0x0000000100000000)
to space 43520K, 0% used [0x00000000fab00000,0x00000000fab00000,0x00000000fd580000)
ParOldGen total 699392K, used 0K [0x00000000c0000000, 0x00000000eab00000, 0x00000000eab00000)
object space 699392K, 0% used [0x00000000c0000000,0x00000000c0000000,0x00000000eab00000)
Metaspace used 3391K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 359K, capacity 388K, committed 512K, reserved 1048576K
Process finished with exit code 0
package com.tang;
import java.util.Random;
//-Xms8m -Xmx8m -XX:+PrintGCDetails
public class Hello {
public static void main(String[] args) {
String str = "duxiaoweiTestjava";
while (true){
str += str + new Random().nextInt(88888888)+new Random().nextInt(999999999);
}
}
}
[GC (Allocation Failure) [PSYoungGen: 1522K->504K(2048K)] 1522K->867K(7680K), 0.0191425 secs] [Times: user=0.00 sys=0.00, real=0.03 secs]
[GC (Allocation Failure) [PSYoungGen: 2040K->496K(2048K)] 2403K->1187K(7680K), 0.0005133 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 2032K->504K(2048K)] 2723K->1480K(7680K), 0.0005598 secs] [Times: user=0.19 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 1851K->504K(2048K)] 2828K->1800K(7680K), 0.0004825 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 1879K->504K(2048K)] 3175K->2545K(7680K), 0.0005259 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 1635K->504K(2048K)] 4740K->3923K(7680K), 0.0003040 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 1637K->416K(2048K)] 6120K->5763K(7680K), 0.0006296 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Ergonomics) [PSYoungGen: 416K->0K(2048K)] [ParOldGen: 5347K->2810K(5632K)] 5763K->2810K(7680K), [Metaspace: 3378K->3378K(1056768K)], 0.0034702 secs] [Times: user=0.02 sys=0.00, real=0.00 secs]
[Full GC (Ergonomics) [PSYoungGen: 1160K->0K(2048K)] [ParOldGen: 4938K->2452K(5632K)] 6099K->2452K(7680K), [Metaspace: 3387K->3387K(1056768K)], 0.0024263 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 1121K->192K(2048K)] 5702K->4772K(7680K), 0.0004441 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 192K->160K(2048K)] 4772K->4740K(7680K), 0.0002400 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 160K->0K(2048K)] [ParOldGen: 4580K->4302K(5632K)] 4740K->4302K(7680K), [Metaspace: 3403K->3403K(1056768K)], 0.0060114 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] 4302K->4302K(7680K), 0.0002241 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] [ParOldGen: 4302K->4283K(5632K)] 4302K->4283K(7680K), [Metaspace: 3403K->3403K(1056768K)], 0.0057527 secs] [Times: user=0.17 sys=0.00, real=0.01 secs]
Heap
PSYoungGen total 2048K, used 75K [0x00000000ffd80000, 0x0000000100000000, 0x0000000100000000)
eden space 1536K, 4% used [0x00000000ffd80000,0x00000000ffd92f50,0x00000000fff00000)
from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
to space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
ParOldGen total 5632K, used 4283K [0x00000000ff800000, 0x00000000ffd80000, 0x00000000ffd80000)
object space 5632K, 76% used [0x00000000ff800000,0x00000000ffc2edf8,0x00000000ffd80000)
Metaspace used 3449K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 367K, capacity 388K, committed 512K, reserved 1048576K
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3332)
at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124)
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:674)
at java.lang.StringBuilder.append(StringBuilder.java:208)
at com.tang.Hello.main(Hello.java:11)
Process finished with exit code 1
堆内存调优
//-Xms 设置初始化内存大小,默认1/64
//-Xmx 设置最大分配内存,默认1/4
//-XX:+PrintGCDetails //打印GC垃圾回收信息
//-XX:+HeapDumpOnOfMemoryError //OOM DUMP
//-Xms1m -Xmx8m -XX:+HeapDumpOnOfMemoryError
// 一般首先查看大对象,然后查看线程,找到错误位置行数
jprofiler,idea安装,然后官网下载一个,添加VM options参数
https://www.ej-technologies.com/download/jprofiler/version_12
package com.tang;
import java.util.ArrayList;
// -Xms64m -Xmx128m -XX:+HeapDumpOnOutOfMemoryError
public class Demo03Jprofiler {
byte[] array = new byte[1*1024*1024]; //1m
public static void main(String[] args) {
ArrayList<Object> list = new ArrayList<>();
int count = 0;
try{
while (true){
list.add(new Demo03Jprofiler()); //问题所在
count++;
}
}catch (Error e){
System.out.println(count);
e.printStackTrace();
}
}
}
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid15024.hprof ...
Heap dump file created [123794203 bytes in 0.074 secs]
116
java.lang.OutOfMemoryError: Java heap space
at com.tang.Demo03Jprofiler.<init>(Demo03Jprofiler.java:7)
at com.tang.Demo03Jprofiler.main(Demo03Jprofiler.java:14)
找到文件,一般src或者根目录中
先找大对象,堆【放对象的】,然后找线程具体问题所在
GC
-
JVM 在进行GC时,并不是对这三个区域统一回收。大部分时候,回收都是新生代
- 新生代Eden
- 幸存区(from ,to)会交换
- 老年区
GC两种类:轻GC(普通GC),重GC(全局GC)
- GC的算法有哪些?标记清除法、标记压缩,复制算法,引用计数器等
-
好处:内有内存的碎片~
-
坏处:浪费了内存空间:多了一半空间永远是to。假设对象100%存货(极端情况下)OOM
复制算法最佳使用场景:对象存货低的时候:新生区
-
标记算法
优点:不需要额外的空间
缺点:两次扫描,严重浪费内存空间。会产生内存碎片。
- 标记压缩
总结
内存效率:复制算法 > 标记清除算法 > 标记压缩算法(时间复杂度)
内存整齐度:复制算法 = 标记压缩算法 > 标记清除算法
内存利用率:标记压缩算法 = 标记清除算法 > 复制算法
年轻代:
- 存活率低
- 复制算法
老年代
- 区域大:存活率
- 标记清除(内存碎片不是太多)+标记压缩混合实现
GC -> 分代收集算法
JMM
-
什么是JMM?
java内存模型
-
它干嘛的?:官方,其他人的博客,对应的视频
作用:缓存一致性协议,用于定义数据读写的规则(遵守,找到这个规则)
JMM定义了线程工作内存和主内存之间的抽象关系,县城之间的共享变量存储在主内存中,每个线程都有一个私有的本地内存
解决共享对象可见性问题:volilate
-
它该如何学习?:JMM是一个抽象的概念
反射
package com.tang;
public class test {
public static void main(String[] args) throws ClassNotFoundException {
//通过反射 获取类的Class对象
Class forName = Class.forName("com.tang.User");
System.out.println(forName);
System.out.println(forName);
//反射
Class c1 = Class.forName("com.tang.User");
Class c2 = Class.forName("com.tang.User");
Class c3 = Class.forName("com.tang.User");
// 正常
User user = new User();
Class c4 = user.getClass();
//
Class c5 = User.class;
// 一个类在内存中只有一个Class对象
//一个类被加载后,类的整个结构都会被封装在Class对象中
System.out.println(c1.hashCode());
System.out.println(c2.hashCode());
System.out.println(c3.hashCode());
System.out.println(c4.hashCode());
System.out.println(c5.hashCode());
}
}
//pojo
class User{
private String name;
private int id;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", id=" + id +
", age=" + age +
'}';
}
}
通过想反射,拿到这个类
-
一个加载的类,在JVM中只会有一个Class实例,无论创建多少个对象,这个类只有一个
-
一个Class对象对应的是一个加载到JVM的一个.class文件
-
每个类的实例都记得自己是由哪个Class实例所生成
-
通过Class可以完整地得到这个类的所有被加载的结构
-
Class是Reflection的根源,对任何你想动态加载、运行的类,唯有先获得响应的Class对象
- Class cla = Person.class; //最安全可靠
- Class cla = person.getClass(); //通过具体对象获得
- Class cla = Class.forName(“demo01.Person”); //通过静态方法forName()获取
- 内置基本数据类型可以直接类名.Type
- 还可以利用ClassLoader
类的初始化
- new对象时候
- 反射创建对象时候
- 调用类的静态代码块,常量,不会初始化类
- 子类调用父类的静态变量,子类不会初始化,父类会初始化
- 先静态代码块,然后构造器,然后常量值