主要对JVM进行一个汇总 大致的了解
所有java程序运行的基础是 java虚拟机的内存模型
*****虚拟机内存分部包括五个部分:
1、程序计数器:每一个线程都必须要用一个独立的程序计数器,可以看做是当前线程的字节码行指示器 用于记录下 一条要运行的命令,(循环、跳转、异常处理线程恢复等都依赖于此) 多线程是通过不断切换处理器(一个内核 cup 的多少核),线程处理过程中不断切换,线程切换后恢复到指定位置,每条线程都需要一个独立的程序计数器 如果 当前执行的是java方法 那么程序计数器记录的是字节码地址 如果为native方法 则程序计数为空2、虚拟机栈: 描述java方法执行的内存模型,每个方法的执行都会创建一个栈针 存储局部变量 方法出等信息,局部变 量存储基本数据类型 以及对象的字节码指令地址,编译时期就已经知道了所需要的内存空间
3、本地方法栈:主要是为nativate方法服务的
4、堆:是被所有线程共享的区域,用于存放对象实例
5、方法区:哥哥线程共享的区域用于存放已加载的类信息、常量、静态变量、(很多人吧方法去称之为永久带,只是说为 方法区域服务)
运行常量池:是方法区域的一部分 —>将在类加载后进入方法区域的运行时常量
直接内存:不是虚拟机运行时候数据区的一部分,也不是java虚拟机定义范围内的内存,NIO 引入了基于通道和缓冲区的的I/o方式,可是直接使用native函数库直接分配堆外内存,直接使用个directBytebuffer堆内存进行操作避免了java堆和Nativate中来回复制数据
****对象创建:
创建一个对象都在内存中做了什么事情?1:先将硬盘上指定位置的Person.class文件加载进内存。
2:将类型信息(包括静态变量,方法等)加载进方法区。
3,执行该类中static代码块,如果有的话,对Person.class类进行初始化
4:在堆内存中开辟一个实体空间,分配了一个内存首地址值。new
5:在该实体空间中进行属性的空间分配,并进行了默认初始化。
6:对空间中的属性进行显示初始化。
7:进行实体的构造代码块初始化。
8:调用该实体对应的构造函数,进行构造函数初始化。()
9:将首地址赋值给p ,p变量就引用了该实体。(指向了该对象)
对象的内存布局:
分为3块区域:对象头、实例数据和对齐填充
1、对象头:分为两个部分 1:存储自身运行时的数据 如哈希码、gc分代年龄,锁状态、线程持有的锁
2:类型指针,通过这个指针确定那个对象是那个类的实例
2、实例数据 定义字段内容
3、要求字节必须是8的倍数 不够填充 没必要记忆他
**对象方位定位:
java程序需要使用栈上的reference数据操作堆上的具体数据,reference只是冠梁是指向对象的引用,并没有定位该如何去访问具体位置,目前方位是句柄池和直接指针访问(存放具体地址信息)
1的优点:句柄池就像中间截止一样对象移动(比如垃圾回收)改变句柄池中的指针就好reference不需要修改
2的优点访问速度加快
查看GC参数
再argument配置:-XX:+PrintGCDateStamps -XX:+PrintGCDetails -Xloggc:./gclogs
查看log即可
垃圾回收和内存分配策略:
1、那些内存需要回收:
(1)程序计数器、虚拟机栈、本地方法栈 3个区域随线程生而生 灭而灭,java堆和方法区域不一样 比如一个接口多个 实现类所需要的内存不一样,其中的每一个方法有多少分支需要对象多少内存也不知道,只有在程序运行期间才知道,因此 内存的创建和回收都是动态的。方法1:程序计数器每当有程序引用时候计数器添加1 引用失效时候计数器减去1,蛋此方法不可行 如嵌套相互使用, obA。instance = objB obB.instance = objA 则计数器无法使用 因此这种说法是无效的。
可达性分析算法:
方法2:通过GC Root 判断是否使用 以GC root作为起点(GC Root既然是引用那么就是引用对象) 判断判断到这个 对象是否可达 不可达则可以删除
GCRoot引用对象包括:
1、虚拟机栈中的引用搞对象
2、方法区中类静态属性的引用对象
3、方法去常量引用对象
4、本地方法去即Nativate方法引用对象
引用到底是什么:上面提到对象定位reference类型数据代表引用了另一块内存的起始地址 就称为这块内存引用
其实可以细分为:
1、强引用(不会对他进行回收)
2、软引用(内存回收之前会对他进行二次回收)
3、弱引用(无论内存是否足够都会回收其关联对象)
4、徐引用
(2)确定哪些是或者的那些事死去的
如果没有发现被GCRoot引用链并不意味这宣告死亡,要进行筛选 流程:
1、当对象变成(GC Roots)不可达时GC会判断该对象是否覆盖了finalize方法,若未覆盖,则直接将其回收。 2、否则,若对象未执行过finalize方法,将其放入F-Queue队列,由一低优先级线程执行该队列中对象的 finalize方法。执行finalize方法完毕后,GC会再次判断该对象 是否可达,若不可达,则进行回收,否则,对 象“复活”。
2、什么时候进行回收:其实上面已经说了
3、如何回收
方法区域回收:很多人认为方法区是永久带,其实是可以会后的只是效率低 新生代效率回收再70~95% 其实可以回收 比如此常量和实 例再堆内存无任何引用可以进行回收
垃圾回收算法:
1、标记清除算法:上面已经讲过主要是进行标记引用了进行++操作 但是:效率低 标记和清除两个步骤效率都低,并且 有很多不连续的内存碎片,如果下次需要进行分配大内存时候会再次进行回收操作2、复制算法:将内存花费为两1:1的两小块 当这块内存用完了,将活着的对象复制到另一块上面,不用考虑效率问题
这种代价太高,上面的1:1可以分为一块较大的eden区域和两块较小的survivor区域 回收时 将eden和一块 survivor活着的对象复制到survivor区域 最后清理掉他们两个
3、标记整理算法:如果存活率较高 呢么上面说的复制整理算法就会效率低很多 如果不想浪费50%的空间 {刚刚一个 survivor就是等着放或者的对象,}就要对对象进行担保 万一100%都是活着的—————>根据老年代的特点提出标记整理 算法,和标记清除算法一样,只是不进行回收,将存活的对象向前移动,然后直接清除到边界之外的内存
4、分代收集算法:其实是根据上面的情况进行整合,根据存活周期的不同将内存划分为不同的几块,一般讲堆分为新生代 和老年代,如果回收时候发现大批死去的对象 采用复制,老年代成活率高,没有额外内存进行担保 就是用标记-清/整理
按系统线程分
串行收集:串行收集使用单线程处理所有垃圾回收工作,因为无需多线程交互,实现容易,而且效率比较高。但是,其局限性也比较明显,即无法使用多处理器的优势,所以此收集适合单处理器机器。当然,此收集器也可以用在小数据量(100M左右)情况下的多处理器机器上。
--适用情况:数据量比较小(100M左右);单处理器下并且对响应时间无要求的应用。
--缺点:只能用于小型应用
并行收集:并行收集使用多线程处理垃圾回收工作,因而速度快,效率高。而且理论上CPU数目越多,越能体现出并行收集器的优势。
--适用情况:“对吞吐量有高要求”,多CPU、对应用响应时间无要求的中、大型应用。举例:后台处理、科学计算。
--缺点:垃圾收集过程中应用响应时间可能加长
并发收集:相对于串行收集和并行收集而言,前面两个在进行垃圾回收工作时,需要暂停整个运行环境,而只有垃圾回收程序在运行,因此,系统在垃圾回收时会有明显的暂停,而且暂停时间会因为堆越大而越长。
--适用情况:“对响应时间有高要求”,多CPU、对应用响应时间有较高要求的中、大型应用。举例:Web服务器/应用服务器、电信交换、集成开发环境
HotSpot 算法实现 参考http://jbutton.iteye.com/blog/1569746
以GC root为例子如何检查引用 特别耗时 通过oopMap数据结构达到算法,再安全点(方法调用,循环)和安全区域(线程sleep或挂起状态)中对指令生成oopMap
serial收集器:对他进行回收 单线程收集 其他线程都会停止
parNew:好一些不保证同事完全收集 但可以不终止
CMS:标记清楚
内存分配
对象优先分配在新生代大对象直接进入老年代(需要大量连续空间的java对象)避免了新生代来回复制造成消耗‘
长期存活的进入老年代(给对象定义一个age计数器)没经过一次gc就进行+1 15岁进入老年代
动态年龄判断(如果Survivor区中相同年龄所有对象大小的总和大于Survivor区空间的一半,年龄大于或等于该年龄的对象在Minor GC时将复制至老年代
空间分配担保:这个前面已经出现过多次了,由于新生代使用复制算法,当Minor GC时如果存活对象过多,无法完全放入Survivor区,就会向老年代借用内存存放对象,以完成Minor GC
在触发Minor GC时,虚拟机会先检测之前GC时租借的老年代内存的平均大小是否大于老年代的剩余内存,如果大于,则将Minor GC变为一次Full GC,如果小于,则查看虚拟机是否允许担保失败(-XX:+/-HandlePromotionFailure。从jdk6.0开始,允许担保失败已变为HotSpot虚拟机所有收集器默认设置,虚拟机将不再识别该参数设置,详见JDK-6990095 : Deprecate and eliminate -XX:-HandlePromotionFailure),如果允许担保失败,则只执行一次Minor GC,否则也要将Minor GC变为一次Full GC(直到GC结束时才能确定到底有多少对象需要被移动至老年代,所以在GC前,只能使用粗略的平均值进行判断)
监测工具:
待更新优化: jps(hotspot进程) jinfo细腻集配置 jstack jstat(虚拟机统计监视)
什么时候进行垃圾回收:
1.1空间分配担保:minorGC之前会判断老年代空间是否大于新生代对象空间,如果大于进行minorGC 如果小于 判断设置中是否允许冒险,冒险尝试进行minorGC有风险,判断老年代最大可用空间是否大于晋升老年代大小大于进行minorGC 否则fullGC2、当年轻代内存满时,会引发一次普通GC,该GC仅回收年轻代。需要强调的时,年轻代满是指Eden代满,Survivor(经过垃圾回收没有没回收的对象)满不会引发GC
3、当年老代满时会引发Full GC,Full GC将会同时回收年轻代、年老代
4、当永久代满时也会引发Full GC,会导致Class、Method元信息的卸载
5、system。gc
JVM:调优
(保证堆内存够小1、将转移到老年代的对象数量降低到最小;2、减少full GC的执行时间;)
1.Java线程池(java.util.concurrent.ThreadPoolExecutor)2、连接池
3、JVM参数
针对JVM堆的设置,一般可以通过-Xms -Xmx限定其最小、最大值
年轻代和年老代将根据默认的比例(1:2)分配堆内存 大小影响各个地方gc的时间
4、运行在更好的机器
5、选择合适的GC收集器
6、减少使用全局变量和大对象;
调优方法
一切都是为了这一步,调优,在调优之前, 我们需要记住下面的原则:
1、多数的Java应用不需要在服务器上进行GC优化;
2、多数导致GC问题的Java应用,都不是因为我们参数设置错误,而是代码问题;
3、在应用上线之前,先考虑将机器的JVM参数设置到最优(最适合);
4、减少创建对象的数量;
5、减少使用全局变量和大对象;
6、GC优化是到最后不得已才采用的手段;
7、在实际使用中,分析GC情况优化代码比优化GC参数要多得多;
类加载机制:
加载过程加载——>验证——>准备——>解析——>初始化
类加载时机:
1)遇到new、getstatic、putstatic 或 invokestatic这4条字节码指令时,如果类还没初始 化,则触发初始化
(2)对类进行反射调用时
(3)初始化子类。但父类还没有初始化时,先触发父类初始化
(4)当虚拟机启动时,需指定一个需要执行的主类(包含main方法),虚拟机会先初始化该类
1、装载:查找和导入Class文件
通过累的全限定名称定义二进制流
在Java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口
2、校验:检查载入Class文件数据的正确性;
对二进制流进行验证 文件格式
3、准备:给类的静态变量分配存储空间;
对类的静态变量进行初始值分配不包括累的实例变量 实例变量是堆内存对象实例化分配的
4、解析:将符号引用替换成直接引用的过程
5、初始化:就是上面的加载时机
到了初始化阶段,才真正开始执行类中定义的java程序代码(或者说字节码)
java HelloWorld
命令的时候,JVM会将HelloWorld.class加载到内存中,并形成一个Class的对象HelloWorld.class。
其中的过程就是类加载过程:
1、寻找jre目录,寻找jvm.dll,并初始化JVM;
2、产生一个Bootstrap Loader(启动类加载器);
3、Bootstrap Loader自动加载Extended Loader(标准扩展类加载器),并将其父Loader设为Bootstrap Loader。
4、Bootstrap Loader自动加载AppClass Loader(系统类加载器),并将其父Loader设为Extended Loader。
5、最后由AppClass Loader加载HelloWorld类
类加载器
(1) Bootstrap ClassLoader : 将存放于<JAVA_HOME>\lib目录中的,或者被-Xbootclasspath参数所指定的路径中的,(2) Extension ClassLoader : 将<JAVA_HOME>\lib\ext目录下的,或者被java.ext.dirs系统变量所指定的路径中的所有类库加载。开发者可以直接使用扩展类加载器。
(3) Application ClassLoader : 负责加载用户类路径(ClassPath)上所指定的类库,开发者可直接使用
双亲委派机制:如果一个类加载器接收到了类加载的请求,它首先把这个请求委托给他的父类加载器去完成,每个层次的类加载器都是如此,因此所有的加载请求都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它在搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。