jvm特点:Java语言编译程序只需要生成能在Jvm上运行的目标代码(字节码),就可以在其他平台上不加修改的直接运行,Jvm在运行字节码的时候,把字节码解析成具体平台上的操作指令
操作系统的堆和栈
堆:一般是有程序员分配释放,若不释放,程序结束时则由os回收,分配方式类似于链表
栈:有操作系统自动分配释放,存放函数参数值,局部变量值等
Jvm内存图
Jvm位于操作系统的堆当中,类加载到虚拟机的过程:当classloder启动的时候,它会去主机硬盘上,将A.class文件加载到Jvm方法区,在方法区中这个字节文件会被虚拟机拿来new A字节码,然后在堆内存当中生成一个A字节码对象。然后A字节码这个内存文件有俩个引用,一个指向A的class对象,一个指向加载加载自己的classloader
Jvm堆和栈的特点:
栈:函数中定义的基本类型的变量,对象的引用都在栈内存中分配。数据执行完毕,变量就会被释放,栈的更新速度快,因为局部变量的生命周期短。栈是数据共享的
堆:用于存储new出来的对象和数组,是由jvm垃圾回收机制不定时回收的。堆是线程共享的
分析数据共享:
int a = 3;
int b = 3;
编译器先处理int a = 3;首先它会在栈中创建一个变量为a的引用,然后查找有没有字面值为3的地址,没找到,就开辟一个存放3这个字面值的地址,然后将a指向3的地址。接着处理int b = 3;在创建完b的引用变量后,由于在栈中已经有3这个字面值,便将b直接指向3的地址。这样,就出现了a与b同时均指向3的情况,即共享了3这个栈数据。
对于String类型来说,编译期已经创建好的(String a="123" ,双引号定义的内容)存在常量池里面,如果是运行期则存在堆中(String b=new String("123"),new 出来的对象),而等号的左边为引用对象。
对于栈和常量池中的数据可以共享,即可以有多个引用对象;而对于堆来说,数据不可以共享,只能有一个引用对象。
再来分析线程共享:
所有线程共享堆,但每个线程都有自己的寄存器和自己的栈。
最后总结一下数据共享的有栈、寄存器、PC,线程共享的有:堆、全局变量、静态变量、方法区。
Jvm的内存组成:
1.程序计数器(pc寄存器):
用于保存程序当前执行的指令地址。当CPU需要执行指令时,需要从程序计数器中得到当前需要执行的指令所在存储单元的地址,然后根据得到的地址获取到指令,在得到指令之后,程序计数器便自动加1或者根据转移指针得到下一条指令的地址,如此循环,直至执行完所有的指令.每个线程都会有自己独立的程序计数器,并且不相互影响,在JVM规范中规定,如果线程执行的是native方法,那么程序计数器保存的值是undefined.还有就是程序计数器中内存的数据所占用的空间大小不会随着程序的执行而改变,因此对于程序计数器这块内存是不会发生OOM的.
2.堆:用于存放new出来的对象会数组,线程共享的,jvm启动的时候创建,是jvm挂美丽内存最大的一块.。可以通过设置-Xmx和-Xms的值来控制,堆内存当中又可以划分为:
a.新生代(young ):新生代又可以分为Eden区和Survivor,新生代的大小可以由-Xmn来控制,也可以由-XX:SurvivorRatio来控制Eden和Survivor的比例
a1.Eden:新建的对象都是用新生代分配内存空间
a2.Survivor:当Eden空间不足时,会把存活的对象转移到Survivor
b.旧生代:用于存放新生代经过多次垃圾回收依然存活的对象
c.持久代(Permanent Space): 实现方法区,但不等同于方法区,只不过是Hotspot JVM用Permanent Space来实现方法区而已,有些虚拟机没有Permanent Space而用其他机制来实现方法区。用于存放已加载的类信息,方法信息,常量池等。
java.lang.OutOfMemoryError:Java heap space异常,说明Java虚拟机的堆内存不够,
原因:
1.java虚拟机的堆内存设置不够,可通过参数-Xms,-Xmx来调整
2.代码中创建了大量对象,并且长时间不能够被垃圾收集器收集
java.lang.OutOfMemoryError:PerGen space,说明java虚拟机对永久代设置不够
1.程序启动需要加载大量的第三方jar包,例如:在一个tomcat下部署了大量应用
2.大量动态反射的类不断被加载
3.栈:栈分为虚拟机栈和本地方法栈
虚拟机栈:其中存放每个方法执行时创建的栈桢,每个线程执行每个方法的时候都会在栈中申请一个栈桢,每个栈桢包括局部变量区,操作数栈,动态链接,方法返回地址,使用-Xss设置每个线程的大小
局部变量表:存储基本类型的变量和对象的引用,内存是在编译期间分配的,在运行期间不会改变大小
操作数栈:程序在执行过程中的计算都是通过操作数栈来完成的
动态链接:每个栈桢包含一个指向运行时常量池的引用,Class文件常量池中包含大量的符号引用,字节码中的方法调用指令就以常量池中指向方法的引用为参数,这些引用在运行时转化为直接引用,称为动态连接
方法返回地址:每个方法执行完毕之后要返回之前执行他的地方,因此栈桢中需要保存一个方法的返回地址
本地方法栈:用于支持native方法的执行
4.方法区:虚拟机启动时创建,线程共享,用于存放将要加载的类信息,静态变量,final类型的常量,属性和方法信息。
类加载机制
1.Bootstrap ClassLoader
负责加载$JAVA_HOME中 jre/lib/rt.jar里所有的class,由c++实现,不是classLoader子类
2.Extension ClassLoader
负责加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中 jre/lib/*.jar 或者 -Djava.ext.dirs指定目录下的jar包
3.App ClassLoader
负责加载classpath中指定的jar包及目录中的class
4.Custom ClassLoder
属于应用程序根据自身需要自定义的ClassLoader
双亲委派模式:某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成加载任务就成功返回,只有父类加载器无法完成此加载任务时,才自己去加载
Jvm垃圾回收:
jvm采用gc分代收集算法:将jvm堆分为新生代和老年代,根据各个年代的特点采用最合适的算法,新生代中,存活对象较少,所以采用复制算法,老年代中对象的存活率较高,而且没有额外的空间分配担保,所以采用标记清除算法
垃圾收集器
Serial收集器:新老生代都是串行收集 -XX:+UseSerialGC
ParNew收集器:
新生代多线程(复制算法),老生代单线程(标记-整理算法)
-XX:+UserParNewGC ParNew收集器
-XX:+ParallelGCThreads 限制线程数,默认是cpu核数
Parallel收集器:
新生代多线程(复制算法),老生代单线程(标记-整理算法)
-XX:+UserParallelGC Parallel收集器
Parallel Old收集器:
使用多线程和标记-整理算法
-XX:+UserParallelOldGC
CMS收集器:
采用标记-清除算法
优点: 并发收集、低停顿
缺点: 产生大量空间碎片、并发阶段会降低吞吐量
-XX:+UseConcMarkSweepGC 使用CMS收集器
-XX:+ UseCMSCompactAtFullCollection Full GC后,进行一次碎片整理;整理过程是独占的,会引起停顿时间变长
-XX:+CMSFullGCsBeforeCompaction 设置进行几次Full GC后,进行一次碎片整理
-XX:ParallelCMSThreads 设定CMS的线程数量(一般情况约等于可用CPU数量)
G1收集器
空间整合,G1收集器采用标记整理算法,不会产生内存空间碎片,可预测停顿
-XX:+UnlockExperimentalVMOptions -XX:+UseG1GC #开启;
-XX:MaxGCPauseMillis =50 #暂停时间目标;
- XX:GCPauseIntervalMillis =200 #暂停间隔目标;
-XX:+G1YoungGenSize=512m #年轻代大小;
-XX:SurvivorRatio=6 #幸存区比例