上图为jvm的主要结构,学习jvm应该有以下2个模块可以去思考
1.类加载机制
加载的过程就是将class文件转为内存中的可被jvm使用的java类型
加载》》验证》》准备》》解析》》初始化》》使用》》卸载
java在什么情况洗会初始化
new 关键字,读取或者设置静态字段,调用静态方法,通过反射进行调用,类的子类被初始化的时候,如果自己没有实例化,则会初始化,jvm启动时候带main方法那个主类
1.1加载
将class文件的二进制字节流按照jvm所需要的格式存在方法区。在java堆中实例化对象
类加载器
双亲委派机制,从底层往上BootStrap >> Extension >> Application
如果类加载器收到类加载的请求,首先会委派给父加载器进行加载,所以都会传送到BootStrap,当父加载器无法加载的时候才会让子加载器去加载。好处是保证了java的稳定和安全
自定义类加载器
一般web容器会自定义类加载器。
1.2验证
文件开头是否是魔数
版本号(jdk高版本可执行低版本)
常量池的常量是否有不被支持的常量类型
。。。等等(需要了解class文件)
java语法检查(抽象类,final等)
1.3准备
在方法区对类进行分配内存和变量的初始值设置。针对类变量而不是实例变量。初始值指数据类型的零值
比如 static int value = 33; 此时应该是int类型的初始值0
1.4解析
1.5初始化
2.运行时数据区
运行时数据区由方法区,堆,java栈,本地方法栈,pc寄存器(也可以叫做程序计数器),如图所示,方法区和堆是所有线程共享的。
2.1pc寄存器
pc寄存器的作用就是记录一个线程当前所执行的字节码的行号。因为线程在上下文切换的时候要继续原来的步骤,所以每个线程都会有自己的pc寄存器(就是程序计数器,这个更好理解)。
2.2java栈
java栈也是线程私有的,生命周期与线程相同。Java 栈由栈帧组成,一个帧对应一个方法调用。调用方法时压入栈帧,方法返回时弹出栈帧并抛弃。Java栈的主要任务是存储方法参数、局部变量、中间运算结果, 并且提供部分其它模块工作需要的数据。每个栈桢分配多少内存在类结构确定下来的时候就已经确定
2.3本地方法栈
省略
2.4java堆(新生代,老年代)
在虚拟机启动的时候创建,存储对象的实例以及数组
对象一般会优先在Eden区,Eden区空间不足的时候进行一次Minor GC(针对年轻代进行的垃圾回收)
大对象会直接在old区,一般指需要大量连续内存空间的对象,比如长字符串和数组。old区会进行full GC 即Major GC 一般会导致Minor GC
在新生代中,存活时间超过 Minor GC15次以上的会进入老生代。
还有就是在Survivor中年龄相同的对象总和 待遇Survivor空间一半以上的,年龄大于或者等于这些对象的对象,都可以进入老年代,无需等等15次
2.5方法区(持久代)
存放
a.类及其父类的全限定名(java.lang.Object没有父类)
b.类的类型(Class or Interface)
c.访问修饰符(public, abstract, final)
d.实现的接口的全限定名的列表
e.常量池 //这个比其他的特别
f.字段信息
g.方法信息
h.静态变量
i.ClassLoader引用
j.Class引用
一般垃圾回收针对常量池的卸载和对类型的卸载(即没什么用的类)
对常量的回收,可以理解为下文的GC Roots,即没有任何引用链,可以被回收。
没用的类是指同时满足下面条件
- 该类的实例都已经被回收(堆中不存在该类的对象)
- 加载这个类的classloader已经被回收
- 该类对应的java.lang.class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法//TODO 这三个条件的理解
因为方法区的回收能够回收的很少,所以会被叫做持久代
2.6判断哪些对象可以被回收
2.6.1引用计数算法
给一个对象添加一个引用计数器,其他地方有1个引用就+1,最终 如果一个对象的计数器为0,则可以被回收。不过这个存在循环计数的情况,就是两个对象相互引用,所以sun并没有采取这个算法。
//TODO 什么是引用?强引用/弱引用/软引用/虚引用 发现随着经验的增加,对一些名词的理解会有不同
2.6.2根搜索算法
把这些对象作为GC Roots
- java栈中引用的对象
- 方法区中的类的静态属性引用的对象
- 方法区中的常量引用的对象
- 本地方法栈中引用的对象
通过GC Roots作为起点,向下搜索,走过的路径称为引用链,如果一个对象跟GC Roots没有引用链,则可以被回收
//TODO不过就算一个对象没有引用链,也不是一定会被回收,还有两次标记过程,finalize()方法
2.7垃圾回收的算法
2.7.1标记清除算法
上面讲过的标记为可回收的对象,然后进行清楚,1效率慢2产生大量内存碎片
2.7.2copy算法
刚开始人们会把堆的新生代分为两个相同大小的内存,垃圾回收的时候将一个内存块所有存活的对象copy到另一块。
不过后来发现绝大部分内存对象都是朝生夕死的,所以进行了优化
如图所示,将堆的新生代分为3部分。一般使用Eden和s中的1个,进行垃圾回收的时候,将存活的部分copy到s中的另一个。然后清理刚才的s和Eden.
同时注意,如果此时的s空间不足,可以去老年代进行分担。同时一些存活时间比较久的也会被copy到老年代。
2.7.3标记整理算法
这个是为老年代准备的算法,先标记,然后将存活的对象都像内存区间的一端移动,然后清理掉整理后的那端//TODO 具体算法不清楚
2.7.4分代收集算法
其实就是分为老年代和新生代
2.7.4具体的实现
7个回收器,以后可以具体看。
3.其他
3.1java对象访问
一般会有两种
3.2内存溢出
堆内存溢出
一般可以把dump出来的文件进行分析(Eclipse Memory Analyzer),查看泄漏对象到GC Roots的引用链。内存泄漏和内存溢出区别??
java栈内存溢出
一般在java栈总量一定的时候,每个线程的栈容量越大,越容易导致内存溢出。一般栈深度都是够用的。
3.3内存分析一般的工具
- jps 显示所有的jvm 进程
- jstat 收集jvm各方面的运行数据
- jinfo 显示jvm的配置信息
- jmap 生成jvm的内存快照文件(heapdump文件)
- jhat 可以分析heapdump文件
- jstack 显示虚拟机的线程的快照
- jconsole 可视化的
- VisualVM 高级工具,需要再下载
参考:http://www.cnblogs.com/floerggyy/archive/2008/04/01/1133353.html