java虚拟机学习资料
谈谈你对java的理解
- 平台无关性
- GC
- 语言特性
- 面向对象
- 类库
- 异常处理
java如何实现平台无关性
-
编译时
-
运行时
java找不到主类
在包下的类,在Java源文件的地方编译后,需要到最外层包的上一级目录下运行,而且类前面需要带包名,以.隔开。
查看字节码
java -p
Compiled from "test.java"
public class connector.test {
public connector.test(); //这是一个无参构造函数
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V 调用父类的构造方法
4: return // 退出构造函数
public static void main(java.lang.String[]);
Code:
0: iconst_1 //常量1放到栈顶
1: istore_1 //将栈顶的值放到局部变量1中
2: iconst_1 //将常量5放到栈顶
3: istore_2 //将栈顶的值放到局部变量2中
4: iinc 1, 1 //将变量1加上1
7: iinc 2, 1 //将变量2加上1
10: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; //静态变量加入1
13: iload_1 //将变量1的值压入栈顶
14: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
17: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
20: iload_2
21: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
24: return
}
1.编写java文件,.java文件 2.生产字节码,就是.class文件。 3.有了.class文件,jvm加载JVM来进行执行
java源码首先被编译成字节码,再由不同平台的JVM进行解析,Java语言在不同的平台上运行时不需要进行编译,Java虚拟机在执行字节码的时候,把字节码转换成具体平台上的机器指令
jvm如何加载.class文件
- class loader:依据特定格式,加载class文件到内存
- execution Engine:对命令进行解析
- Native Interface:融合不同的开发语言的原生库为java所用
- Runtime Data Area:JVM内存结构模式
主要是由class loader将符合规范的class文件加载到内存,然后通过execution engine来进行解析,最后提交给本地接口来进行使用
谈谈反射
java反射机制是指在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性。这种动态获取信息以及动态调用对象的方法的功能称为java语言的反射机制
Class clazz=Class.forName("basic.reflection.Robot");
Robot r=(Robot)clazz.newInstance();
System.out.println("class name is "+clazz.getName());
Method getHello=clazz.getDeclaredMethod("helloPerson",String.class);
getHello.setAccessible(true);
Object str=getHello.invoke(r,"Bob");
System.out.println("getHello result is " +str);
Method printName=clazz.getMethod("printName",String.class);
printName.invoke(r,"wangming");
Field name=clazz.getDeclaredField("name");
name.setAccessible(true);
name.set(r,"Alice");
printName.setAccessible(true);
printName.invoke(r,"welcome");
getmethod和 getdeclearmethod区别
getDeclaredMethod:获取当前类的所有声明的方法,包括public、protected和private修饰的方法。需要注意的是,这些方法一定是在当前类中声明的,从父类中继承的不算,实现接口的方法由于有声明所以包括在内。
getMethod:获取当前类和父类的所有public的方法。这里的父类,指的是继承层次中的所有父类。比如说,A继承B,B继承C,那么B和C都属于A的父类。
类从编译到执行的过程
- 编译器将Robot.java源文件编译为Robot.class字节码文件
- 类加载器将字节码转换为JVM中的Class对象
类加载器(class Loader)
- ClassLoader主要在Class的装载阶段,主要作用是从系统获得Class二级制数据流。它是Java的核心组件,所有的Class是由classLoader来进行加载的。classloader负责通过将class文件里的二进制数据流装载进系统,然后交给Java虚拟机进行连接、初始化操作。
ClassLoaderd的种类
- BootStrapClassLoader:C++编写,加载核心库java.*
- ExtClassLoader:Java编写,加载扩展库javax.*
- AppClassLoader: Java编写,加载程序所在目录
- 自定义classLoader:java编写,定制化加载
自定义ClassLoader的实现
- findclass
- 修改二进制流的代码,给类添加点信息。asm
- 字节码
- AOP
public class MyClassLoader extends ClassLoader {
private String path;
private String classLoaderName;
public MyClassLoader(String path,String classLoaderName){
this.path=path;
this.classLoaderName=classLoaderName;
}
//用于寻找类文件
@Override
public Class findClass(String name){
byte[] b=loadClassData(name);
return defineClass(name,b,0,b.length);
}
//用于加载类文件
private byte[] loadClassData(String name) {
name=path+name+".class";
InputStream in =null;
ByteArrayOutputStream out=null;
try{
in=new FileInputStream(new File(name));
out=new ByteArrayOutputStream();
int i=0;
while ((i=in.read())!=-1)
{
out.write(i);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
out.close();
in.close();
} catch (Exception e)
{
e.printStackTrace();
}
}
return out.toByteArray();
}
}
谈谈类的加载器的双亲委派机制
1.自底向上检查类是否已经加载 2.自顶向下尝试加载类
有可能多个线程加载同一个类,所以会加一个同步锁
主要是发生在loadclass这个方法中,首先先加个锁,然后判断findloadedclass,当前类是否已经加载,如果为空的话就判断parent是否存在。parent是app-classloader.app-classloader,app-classloader又会调用这个loadclass这个方法。去看看app-classloader是否已经装载过。判断parent是否存在,如果存在的话 就掉用app-classloader的父类,extclass-loader。如果依旧没有装载的话,就继续判断parent是否存在,因为bootstrapclasscloader是用C++写的。肯定为空,就直接调用findbootstrapclass方法。如果findbootstrap这个方法还没有的,他就自己去调用findclass方法。 java6
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
为什么要使用双亲委派机制去加载类
- 避免多分同样字节码的加载
JDK代码闭源, openjdk开源。
hg.oepnjdk.java.net/jdk8u/jdk8u/jdk/file
类的加载方式
- 隐式加载:new
- 显示加载:loadClass,forName等。
loadCLass和forName的区别
类的装载过程
- 加载 通过ClassLoader加载class文件字节码,生成Class对象
- 链接 1. 校验:检查加载的class的正确性和安全性 2. 准备:为类变量(static)分配存储空间并设置类变量初始值 3. 解析: JVM将常量池内的符号引用转换为直接引用
- 解析 执行类变量赋值和静态代码块
- loadclass是加载类,是在链接之前。是还没有进行连接的。只完成了加载,链接和解析都完成了。loadclass在spring AOC 要读取一些配置的时候,如果是以classpath的方式来。 和spring aoc的lazy loading有关。spring aoc为了加快这个加载速度,大量使用延迟加载技术。使用classloader不需要使用链接和解析等步骤。这样可以加快加载速度
- class.forname得到的class是已经初始化完成之后的。要使用静态代码块,一般使用forname
JAVA内存模型
线程私有和线程共享
- 线程私有:程序计数器、虚拟机栈、本地方法栈
- 线程共享:MetaSpace、Java堆
-
线程的私有区域 程序计数器
- 当前线程锁执行的字节码行号指示器(逻辑),逻辑计数器
- 改变计数器的值来选取吓一跳需要执行的字节码指令
- 和线程是一对的关系及"线程私有"
- 对java方法计数,如果是Native方法则计数器值为Undefined
- 不会发送内存泄露
Java虚拟机栈(Stack)
- Java方法执行的内存模型
- 包含多个栈帧- 局部变量表 :包含方法执行过程中的所有变量 操作数栈:入栈、出栈、复制、交换、产生消费变量
javap verbose 口语化来描述.class文件。 istore 是从操作数栈到局部变量表 iload 是从局部变量表load到操作数栈
-
本地方法栈
- 与虚拟机栈类似,主要作用于标注了native的方法
-
线程的共享区域 1.6才有永久代,1.8没有永久代
-
元空间(metaspace)与永久代(PermGen)的区别 元空间使用本地内存,而永久代使用的是JVM的内存
MetaSpace相比PermGen的优势
-
字符串常量池存在永久代中,容易出现性能问和内存溢出
-
类和方法信息大小难易确定,给永久代的大小指定带来困难
-
永久代会为GC带来不必要的复杂性
-
方便hotspot与其他JVM如Jrockit的集成
-
Java堆(Heap)
- 对象实例的分配区域
- GC管理的主要区域
-
metaspace(元空间)
JVM三大性能调优参数-Xms -Xmx -Xss的含义
- Xss:规定了每个线程虚拟机栈(堆栈)的大小
- Xms:堆的初始值
- Xmx: 堆能达到的最大值
Java内存模式中堆和栈的区别
- 内存分配策略
- 静态存储:编译时确定每个数据目标在运行时的存储空间需求
- 栈式存储:数据区需要在编译时未知,运行时模块入口前确定
- 堆式存储:编译时或运行时候模块入口都无法确定,动态分配
- 联系:引用对象、数组时,栈里定义变量保存堆中目标的首地址
- 管理方式:栈自动释放,堆需要GC
- 空间:栈比堆小
- 碎片相关:栈产生的碎片远小于堆
- 分配方式:栈支持静态和动态分配,而堆仅支持动态分配
- 效率:栈的效率比堆高
不同JDK版本之间的Intern()方法的区别--JDK6 VS JDK 6+
JDK6:当调用intern方法时,如果字符串常量池先前已创建出该字符串对象,则返回池种的该字符串的引用。否则,将此字符串对象添加到字符串常量池中,并且返回该字符串对象的引用。
JDK6+ :
String s = new String("1");
String s2 = "1";
s.intern();
System.out.println(s == s2);
String s3 = new String("1") + new String("1");
String s4 = "11";
s3.intern();
System.out.println(s3 == s4);
String s = newString("1"),生成了常量池中的“1” 和堆空间中的字符串对象。
String s2 = "1",这行代码是生成一个s2的引用指向常量池中的“1”对象,但是发现已经存在了,那么就直接指向了它。
s.intern(),这一行在这里就没什么实际作用了。因为"1"已经存在了。
结果就是 s 和 s2 的引用地址明显不同。因此返回了false。
String s3 = new String("1") + newString("1"),这行代码在字符串常量池中生成“1” ,并在堆空间中生成s3引用指向的对象(内容为"11")。注意此时常量池中是没有 “11”对象的。
String s4 = "11", 这一行代码会直接去生成常量池中的"11"。
s3.intern(),这一行在这里就没什么实际作用了。因为"11"已经存在了。
JDK 6以后是能够把常量池的引用放进去,jdk6以前是只能放副本进去,放引用的话地址就有可能一样
结果就是 s3 和 s4 的引用地址明显不同。因此返回了false 和堆空间中的字符串对象。当调用intern方法时候,如果字符串常量池先前已创建出该字符串对象,则返回池中的该字符串的引用。否则,如果该字符串对象已经存在于JAVA堆中,则将堆中堆此对象的引用添加到字符串常量池中,并且返回该引用。如果堆中不存在,则在池中创建该字符串并返回其引用。
字符串常量池在 Java 内存区域的哪个位置
- 在 JDK6.0 及之前版本,字符串常量池是放在 Perm Gen 区(也就是方法区)中,此时常量池中存储的是对象。
- 在 JDK7.0 版本,字符串常量池被移到了堆中了。此时常量池存储的就是引用了。在 JDK8.0 中,永久代(方法区)被元空间取代了。
JAVA 垃圾回收机制
判定对象是否被回收的依据
- 引用计数法
- 通过判断对象的引用数量来决定对象是否可以被回收
- 每个对象实例都有一个引用计数器,被引用则+1,完成引用则-1
- 任何引用计数为0的对象实例可以被当做垃圾收集
- 执行效率比较高,程序执行受影响较小。无法检测出循环引用的情况,导致内存泄露
- 可达性分析算法
- 通过判断对象的引用链是否可达来决定对象是否可以被回收
- 判断GC ROOT对象和该对象是否关联。如果是关联的就代表可达.
- 可以作为GC ROOT的对象
- 虚拟机栈中引用的对象(栈帧中的本地变量表)
- 方法区中的常量引用的对象
- 方法区中的类静态属性引用的对象
- 本地方法栈中JNI(Native方法)的引用对象
- 活跃线程的引用对象
几种垃圾回收算法
标记-清除算法(Mark and Sweep)
- 标记:从根集合进行扫描,对存活的对象进行标记
- 清除: 对堆内存从头到尾进行线性遍历,回收不可达对象内存。
- 缺点:碎片化
复制算法(copying)-适合青年代
- 分为对象块和空闲块
- 对象在对象快上创建
- 存活的对象被从对象块复制到空闲块上
- 将对象块所有对象内存进行清除
- 优点
- 解决了碎片化问题
- 顺序分配内存,简单高效
- 适用于对象存活率低的场景,在青年代
标记-整理算法(Compacting)-适合老年代
- 标记:从根集合进行扫描,对存活的对象进行标记
- 清除:移动所有存活的对象,
且按照内存地址次序依次排列,然后将末端内存地址以后的内存全部回收。不分对象快和内存块 - 缺点:移动成本高
- 优点
- 避免内存的不连续行
- 不用设置两块内存互换
- 适用于老年代的场景
分代收集算法
- 垃圾回收算法的组合拳
- 按照对象生命周期的不同划分区域以采用不同的垃圾回收算法
- 目的:提高加垃圾回收的效率
GC的分类
- 年轻代
- GC方式:Minor GC
- 复制算法
- 年轻代: 尽可能快速收集掉那些生命周期短的对象
- Eden区:伊甸园,初始,如果eden区放不下 就放在 survivor区 8/10 份
- 两个Survivor区,from区和to区是可以相互转换的:
- from区 1/10
- to 区 1/10
- 年轻代占用堆1/3
- MaxTenuringThrehold
- 老年代 -GC方式:Full GC和Major GC - 标记清理算法 - 标记整理算法 Full GC比Minor GC 慢,但执行频率低
触发FULL GC的条件
-
老年代的空间不足
-
永久代空间不足
-
CMS GC时出现 promotion failed,concurrent mode failure
-
Minor GC晋级到老年代的平均大小大于老年代的剩余空间
-
调用system.gc()
-
使用RMI来进行RPC或管理的JDK应用,每小时执行1次FULL GC
对象如何晋升到老年代
-
经历一定Minor GC次数依然存活的对象
-
Survivor区中存放不下的对象
-
新生成的大对象(-xx :+PreenureSizeThreshold)
常用的性能调优的参数
- -XX:SurvovorRatio:Eden和Survivor,默认8:1
- -XX:NewRatio:老年代和年轻代内存大小的比例
- -XX:MaxTenuringThreshold:对象从年轻代晋升到老年代经过GC次数的最大阈值
STOP-THE-World
1. JVM由于要执行GC而停止了应用程序的执行
2. 任何一种GC算法都会发生
3. 多数GC优化通过减少Stop-the-world发生的时间来提高程序性能
safepoint
1. 分析过程中对象引用关系不会发生变化的点
2. 产生Safepoint的地方:方法调用;循环跳转;异常跳转等
3. 安全点数量得适中
常见的垃圾收集器
JVM的运行模式
- server
- 启动慢,但是使用要比client块。
- client
- 启动块,但是使用比server慢
年轻代常见的垃圾收集器
-
Serial收集器(-XX:+UseSerialGC,复制算法)
- 单线程收集,进行垃圾收集时,必须暂停所有工作线程
- 简单高效,Client模式下默认的年轻代收集器
-
ParNew收集器(-xx:+UseParNewGC,复制算法)
- 多线程收集,其余的行为、特定和Serial收集器一样
- 单核执行效率不如Serial,在多核执行才有优势
-
Paralle Scavenge收集器(-xx:+UseParallelGC,复制算法)
- 吞吐量=运行用户代码的时间/(运行用户代码时间+垃圾收集时间)
- 比起关注用户线程停顿时间,更关注系统的吞吐量
- 在多核执行才有优势,Server模式下默认的年轻代收集器
- -XX:+UseAdaptiveSizePolicy
老年代常见的垃圾收集器
-
Serial Old收集器
- 单线程收集,进行垃圾收集时,必须暂停所有工作线程
- 简单高效,Client模式下默认的老年代代收集器
-
Parallel Old收集器(-xx:+UseParallelOldGC,标记整理算法)
- 多线程收集,吞吐量优先
-
CMS收集器(-xx:+UseConcMarkSweepGc,标记-清除算法)
-
硬件厉害,建议CMS
-
初始化标记:STOP-the-world,只查看直接关联GC root的对象 -
并发标记:并发追溯标记,程序不会停顿
-
并发预清理:查找执行并发标记节点从年轻代晋级到老年代的对象,减少下一阶段标记的工作量
-
重新标记:STOP-THE-WORLD,暂停虚拟机,扫描CMS堆中的剩余对象 -
并发清理:清理垃圾对象,程序不会停顿
-
并发重置:重置CMS的数据结构
-
垃圾的产生如果在标记后产生,就只能等到下次再清理。标记清除算法。会出现内存空间碎片化的问题
G1收集器(-XX:+UseG1Gc,复制+标记-整理算法)
- 并行和并发
- 分代收集
- 空间整合
- 可预测的停顿
garbage First收集器
-
将整个Java对内存划分为多个大小相等的region
-
年轻代和老年代不再物理隔离
object的finalize()方法的作用是否与C++的析构函数作用相同
- 与c++的析构函数不同,析构函数调用确定,而它是不确定的
- 将未引用的对象放置于F-Queue队列
- 方法执行随时可能会被终止
- 给与对象最后一次重生的机会
java中的强引用、软引用,弱应用,虚应用
强引用:new ,即时oom也不会回收
软引用:对象处在有用非必须的状态,只有当内存空间不足时,GC会回收该引用的对象内存。用来实现高速缓存。
弱引用:非必须的对象,比软应用更弱一些。GC时会被回收。
虚引用:跟踪对象被垃圾收集器回收的活动,起哨兵作用,必须和referenceQueue联合使用
引用队列(referenceQueue)
- 无实际存储结构,存储逻辑依赖于内部节点之间的关系来表达
- 存储关联的且被GC的软引用,弱引用以及虚引用