一、 JVM
1. JVM原理
1 > JVM是 Java程序运行的环境,也是一个操作系统的一个应用程序,一个进程,也有自己的生命周期,也有自己的代码和数据空间。
2 > JVM 在整个JDK中处于底层,负责与操作系统的交互,用来屏蔽操作系统环境,提供一个完整的JAVA运行环境。
3 > 操作系统装入JVM是通过JDK中java.exe来完成的,其中,四步实现JVM环境搭建:
1 - 创建JVM装载环境和配置
- 首先查找jre路径(Java是通过GetApplicationHome api来获得当前的Java.exe绝对路径,进一步确定jre路径)
- 然后装载JVM.cfg文件(GetArch函数可以判断出:JRE路径+\lib+\ARCH(CPU构架)+\JVM.cfgARCH(CPU构架))
- 在运行java XXX时,一般地,CheckJVMType函数会从 根据JVM.cfg定义的JVM 获取JVM的类型。
- 最后获得JVM.dll的路径:JRE路径+\bin+\JVM类型字符串+\JVM.dll就是JVM的文件路径
2 - 装载JVM.dll
- 通过上述的步骤找到了JVM路径,Java通过LoadJavaVM来装入JVM.dll文件(装入工作很简单就是调用Windows API函数:LoadLibrary装载JVM.dll动态连接库.然后把JVM.dll中的导出函数JNI_CreateJavaVM和JNI_GetDefaultJavaVMInitArgs挂接到InvocationFunctions变量的CreateJavaVM和GetDefaultJavaVMInitArgs函数指针变量上。JVM.dll的装载工作宣告完成。)
3 - 初始化JVM.dll并挂在到JNIEnv(JNI调用接口)实例
- 初始化JVM,获得本地调用接口,这样就可以在Java中调用JVM的函数了.调用InvocationFunctions->CreateJavaVM也就是JVM中JNI_CreateJavaVM方法获得JNIEnv结构的实例
4 - 调用JNIEnv实例装载并处理class类
- java 将.java文件编译成字节码文件,由类加载器加载字节码文件,实质上是把类文件从硬盘读取到内存中(详细:JVM加载class文件的原理机制- http://www.cnblogs.com/blogonfly/articles/3778426.html)
2.JVM加载class文件的原理机制(http://www.cnblogs.com/blogonfly/articles/3778426.html)
①.java中的所以类,必须被加载到JVM中才能运行,这个加载是由类加载器完成的,类加载器所做的工作实质上是把类文件从硬盘读取到内存中
②.java中的类大致分为三种:
1、系统类
2、扩展类
3、程序员自定义的类
③.类加载的方式,两种
1、隐式装载,程序在运行过程中碰到new等方式生成对象时,隐式调用类加载器加载对应的类到JVM中
2、显示装载,通过class.forName()等方法,显示加载需要的类
④.类加载的动态性体现
一个应用程序总是由n多个类组成,java程序启动时,并不是一次性把全部的类加载后在运行的,它总是先把保证运行的基础类一次性加载到JVM中,其他的类等到JVM用到的时候再加载,这样节约内存开销。
⑤.java类装载器
java中的类装载器把类载入JVM中,值得注意的是JVM的类装载器有3个
Bootstrap ClassLoader - 负责加载系统类
ExtClassLoader - 负责加载扩展类
AppClassLoader - 负责加载应用类
三个类加载器一方面各自负责各自的区块,另一方方面实现委托模型
⑥.类加载器之间是如何协调工作的
碰到一个类需要加载时,java采用了委托模型机制,这个机制简单来讲,就是类装载器有载入类的需求时,会先请示器parent使用其搜索路径帮忙载入,如果parent找不到,那么才会自己依照自己的搜索路径搜索类。
下面举一个例子来说明,为了更好的理解,先弄清楚几行代码:
public class Test{
public static void main(String[] arg){
ClassLoader c = Test.class.getClassLoader(); //获取Test类的类加载器
System.out.println(c);
ClassLoader c1 = c.getParent(); //获取c这个类加载器的父类加载器
System.out.println(c1);
ClassLoader c2 = c1.getParent();//获取c1这个类加载器的父类加载器
System.out.println(c2);
}
}
运行结果:
sun.misc.Launcher$AppClassLoader@addbf1
sun.misc.Launcher$ExtClassLoader@42e816
null
可以看出Test是由AppClassLoader加载器加载的
AppClassLoader的Parent 加载器是 ExtClassLoader 但是ExtClassLoader的Parent为 null 是怎么回事呵,如果留意的话,前面有提到Bootstrap Loader是用C++语言写的,依java的观点来看,逻辑上并不存在Bootstrap Loader的类实体,所以在java程序代码里试图打印出其内容时,我们就会看到输出为null。
类加载器ClassLoader(抽象类)描述JVM加载class文件的原理机制
类装载器就是寻找类或接口字节码文件进行解析并构造JVM内部对象表示的组件,
⑦.装载类到JVM的步骤
1、装载:查找和导入class文件
2、连接:其中解析步骤是可选的
1)检查:检查载入的class文件数据的正确性
2)准备:给类的静态变量分配存储空间
3)解析:将符号引用转换成直接引用
3、初始化:对静态变量,静态代码块执行初始化
⑧.Java 类加载器怎么实现将同一个对象加载两次?java装载类使用"全盘负责委托机制":是指一个ClassLoader装载一个类时,除非显示使用另一个ClassLoader,该类所依赖及引用的类也由这个ClassLoader载入。"委托机制"是指先委托父类装载器寻找目标类,只有在找不到的情况下才从自己的路径中查找并载入。这一点是从安全的方面考虑的,试想一下如果有人写了一个恶意的基础类(如java.lang.String)并加载到JVM将引起严重后果,但是全盘负责制,java.lang.String永远是由根装载器来装载的,避免以上情况发生。
平时了解到的就是Java类加载器因为双亲委托模型,一个类只会被加载一次。
- 创建一个classloader,parent设置成null (参见: http://sunfish.iteye.com/blog/1472643)
- 创建两个自定义类加载器同时加载 类的包路径和类加载器决定类的唯一性
3. JVM内存模型
根据Java虚拟机规范,JVM将内存划分为:
New(年轻代)Tenured(年老代)永久代(Perm)其中New和Tenured属于堆内存,Heap = {Old + NEW = { Eden , from, to } },堆内存会从JVM启动参数(-Xmx:3G)指定的内存中分配,Perm不属于堆内存,有虚拟机直接分配,但可以通过-XX:PermSize -XX:MaxPermSize 等参数调整其大小。
年轻代(New):年轻代用来存放JVM刚分配的Java对象
New又分为几个部分:
Eden:Eden用来存放JVM刚分配的对象
Survivor1
Survivro2:两个Survivor空间一样大,当Eden中的对象经过垃圾回收没有被回收掉时,会在两个Survivor之间来回Copy,当满足某个条件,比如Copy次数,就会被Copy到Tenured。显然,Survivor只是增加了对象在年轻代中的逗留时间,增加了被垃圾回收的可能性。
年老代(Tenured):年轻代中经过垃圾回收没有回收掉的对象将被Copy到年老代永久代(Perm):永久代存放Class、Method元信息,其大小跟项目的规模、类、方法的量有关,一般设置为128M就足够,设置原则是预留30%的空间。
4.JVM调优建议
首先,Jdk自带的监控工具JvisualVM使用:http://jingyan.baidu.com/article/e9fb46e172e3747521f76611.html
ms/mx:定义YOUNG+OLD段的总尺寸,ms为JVM启动时YOUNG+OLD的内存大小;mx为最大可占用的YOUNG+OLD内存大小。在用户生产环境上一般将这两个值设为相同,以减少运行期间系统在内存申请上所花的开销。
NewSize/MaxNewSize:定义YOUNG段的尺寸,NewSize为JVM启动时YOUNG的内存大小;MaxNewSize为最大可占用的YOUNG内存大小。在用户生产环境上一般将这两个值设为相同,以减少运行期间系统在内存申请上所花的开销。
PermSize/MaxPermSize:定义Perm段的尺寸,PermSize为JVM启动时Perm的内存大小;MaxPermSize为最大可占用的Perm内存大小。在用户生产环境上一般将这两个值设为相同,以减少运行期间系统在内存申请上所花的开销。
SurvivorRatio:设置Survivor空间和Eden空间的比例