1 堆栈
Java把内存分为栈内存和堆内存。在函数中定义的一些基本类型的变量和对象的引用变量都在函数的栈内存中分配。堆内存用于存放由new创建的对象和数组。在堆中分配的内存,由java虚拟机自动垃圾回收器来管理。在堆中产生了一个数组或者对象后,还可以在栈中定义一个特殊的变量,这个变量的取值等于数组或者对象在堆内存中的首地址,在栈中的这个特殊的变量就变成了数组或者对象的引用变量,以后就可以在程序中使用栈内存中的引用变量来访问堆中的数组或者对象,引用变量相当于为数组或者对象起的一个别名,或者代号。
数组和对象在没有引用变量指向它的时候,才变成垃圾,不能再被使用,但是仍然占着内存,在随后的一个不确定的时间被垃圾回收器释放掉。这个也是java比较占内存的主要原因。
从堆栈的功能和作用上来讲,堆主要用来存放对象的,栈主要是用来执行程序的。
1 New(年轻代)用来存放JVM刚分配的Java对象
1.1 Eden:Eden用来存放JVM刚分配的对象
1.2 Survivor1
1.3 Survivor2:两个Survivor空间一样大,当Eden中的对象经过垃圾回收没有被回收掉时,会在两个Survivor之间来回Copy,当满足某个条件,比如Copy次数,就会被Copy到Tenured。显然,Survivor只是增加了对象在年轻代中的逗留时间,增加了被垃圾回收的可能性。
2 Tenured(年老代)年轻代中经过垃圾回收没有回收掉的对象将被Copy到年老代
3 Perm(永久代)存放Class、Method元信息,其大小跟项目的规模、类、方法的量有关,一般设置为128M就足够,设置原则是预留30%的空间。
New和Tenured属于堆内存,堆内存会从JVM启动参数(-Xmx:3G)指定的内存中分配,Perm不属于堆内存,有虚拟机直接分配,但可以通过-XX:PermSize -XX:MaxPermSize 等参数调整其大小。
2 垃圾回收的算法
2.1 Serial算法
2.2 并行算法:用多线程进行垃圾回收,回收期间会暂停程序的执行,
2.3 并发算法:也是多线程回收,但期间不停止应用执行。所以,并发算法适用于交互性高的一些程序。
JVM98%的时间都花费在内存回收;每次回收的内存小于2%:满足这两个条件将触发OutOfMemoryException,这将会留给系统一个微小的间隙以做一些Down之前的操作,比如手动打印Heap Dump。
3 内存泄漏及解决方法
3.1 系统奔溃前的现象
每次垃圾回收的时间越来越长,由之前的10ms延长到50ms左右,FullGC的时间也有之前的0.5s延长到4、5s【垃圾回收分为两部分:内存标记和清除(复制),标记部分只要内存大小固定时间是不变的,变的是复制部分,因为每次垃圾回收都有一些回收不掉的内存,所以增加了复制量,导致时间延长)】
FullGC的次数越来越多,最频繁时隔不到1分钟就进行一次FullGC【因为内存的积累,逐渐耗尽了年老代的内存,导致新对象分配没有更多的空间】
年老代的内存越来越大并且每次FullGC后年老代没有内存被释放。【因为年轻代的内存无法被回收,越来越多的被copy到年老代】
之后系统会无法响应新的请求,逐渐到达OutOfMemoryError的临界值。
3.2 生成堆的dump文件
Weblogic的JVM启动参数添加以下参数:
-XX:+HeapDumpOnCtrlBreak
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/home/weblogic/log/heapdump/heapdump_20170420094956.hprof
3.3 分析dump文件
选用Eclipse专门的静态内存分析工具:Mat
3.4 分析内存泄漏
通过Mat能清楚地看到,哪些对象被怀疑为内存泄漏,哪些对象占的空间最大及对象的调用关系。还可以分析线程状态,可以观察到线程被阻塞在哪个对象上,从而判断系统的瓶颈。
4 性能调优
4.1 线程池:解决用户响应时间长的问题
Java线程池(java.util.concurrent.ThreadPoolExecutor)有几个重要的参数配置:
corePoolSize:核心线程数(最新线程数)
maximumPoolSize:最大线程数,超过这个数量的任务会被拒绝,用户可以通过RejectedExecutionHandler接口自定义处理方式
keepAliveTime:线程保持活动的时间
workQueue:工作队列,存放执行的任务。Java线程池需要传入一个Queue参数(workQueue)用来存放执行的任务,对Queue的不同选择,线程池有完全不同的行为:
SynchronousQueue: 一个无容量的等待队列,一个线程的insert操作必须等待另一线程的remove操作,采用这个Queue线程池将会为每个任务分配一个新线程
LinkedBlockingQueue : 无界队列,采用该Queue,线程池将忽略maximumPoolSize参数,仅用corePoolSize的线程处理所有的任务,未处理的任务便在LinkedBlockingQueue中排队
ArrayBlockingQueue: 有界队列,在有界队列和 maximumPoolSize的作用下,程序将很难被调优:更大的Queue和小的maximumPoolSize将导致CPU的低负载;小的Queue和大的池,Queue就没起到应有的作用。
线程池的设计思路是,任务应该放到Queue中,当Queue放不下时再考虑用新线程处理,如果Queue满且无法派生新线程,就拒绝该任务。设计导致“先放等执行”、“放不下再执行”、“拒绝不等待”。所以,根据不同的Queue参数,要提高吞吐量不能一味地增大maximumPoolSize。
4.2 连接池
c3p0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。目前使用它的开源项目有Hibernate,spring等。
4.3 JVM启动参数:调整各代的内存比例和垃圾回收算法,提高吞吐量
设置启动参数,希望达到一些目标:GC的时间足够的小;GC的次数足够的少;发生Full GC的周期足够的长。前两个目标是相悖的,要想GC时间短就必须有一个更小的堆,要保证GC次数少,就必须有一个更大的堆。所以
4.4 程序算法:改进程序逻辑算法提高性能
5 JVM参数解析
-Xms100g --设置JVM启动时堆内存的初始化大小
-Xmx100g --设置堆的最大值
-Xmn16g --设置年轻代的空间大小,剩下的为老年代的空间大小。
-Xdebug --JVM调试参数,用于远程调试
-Xnoagent
-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=15000
-XX:+ExplicitGCInvokesConcurrent --避免显式地调用GC,即在应用程序中调用system.gc()
-XX:+PrintFlagsFinal --显示所有可设置的参数及”参数处理”后的默认值
-XX:AutoBoxCacheMax=20000 --相当于int自动装箱Integer的过程,设置为20000后,每秒查询率会提升。
-XX:+AlwaysPreTouch --启动时把参数中设置的内存全部清一遍,会导致启动变慢,但之后访问更流畅。
-XX:TargetSurvivorRatio=80 --允许80%的Survivor区被占用(JVM默认为50%)。提高对于Survivor区的使用率。
-XX:+UseG1GC --G1垃圾收集器(Garbage First)Java 7后才可以使用的特性,它的长远目标时代替CMS收集器。G1收集器是一个并行的、并发的和增量式压缩短暂停顿的垃圾收集器。G1收集器和其他的收集器运行方式不一样,不区分年轻代和年老代空间。它把堆空间划分为多个大小相等的区域。当进行垃圾收集时,它会优先收集存活对象较少的区域,因此叫 “Garbage First”。
-XX:MetaspaceSize=64m --指元空间的初始大小
-XX:MaxMetaspaceSize=256m --设置元空间的最大值
-Dcom.sun.management.jmxremote=true --设置JVM允许远程jmx进行调用查看的相关配置
-Dcom.sun.management.jmxremote.port=44708
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false
-Djava.rmi.server.hostname=192.168.238.138 --设置监控的服务器ip
-XX:+PrintGCDateStamps -- GC发生的时间信息
-XX:+PrintGCDetails --打开PrintGCDetails开关,了解GC详细信息
-Xloggc:./gclogs --设置日志产生的路径
-XX:+UnlockCommercialFeatures --开启商业特性
-XX:+FlightRecorder --飞行记录,和-XX:+UnlockCommercialFeatures一起使用
6 垃圾回收器
1、串行回收器(Serial Collector)
Serial收集器是一个新生代收集器,单线程执行,回收期间会暂停所有应用线程的执行。所以可能不适合服务器环境。它最适合的是简单的命令行程序。
通过JVM参数-XX:+UseSerialGC可以使用串行垃圾回收器。
2、并行回收器(Parallel Collector)
JVM默认的垃圾回收器,与串行垃圾回收期不同的是,它使用多线程进行垃圾回收。相似的是,在执行垃圾回收的时候,它也会冻结所有的应用程序线程。
通过-XX:+UseParallelGC命令行可选项强制指定。
3、并发标记扫描垃圾回收器(Concurrent Mark-Sweep Collector)
并发标记垃圾回收使用多线程扫描堆内存,是一种以获取最短回收停顿时间为目标的收集器。
-XX:+UseConcMarkSweepGC
CMS收集器是基于”标记-清除”算法实现的,整个收集过程大致分为4个步骤:初始标记、并发标记、重新标记、并发清除。其中,初始标记和重新标记这两个步骤需要停顿其他用户线程,由于整个过程中耗时最长的并发标记和并发清除过程中,收集器线程都可以与用户一起工作,所以整体来说,CMS收集器的内存回收过程是与用户线程一起并发执行的。
4、G1垃圾回收器(G1 Garbage Collector)
G1收集器是JDK1.7提供的一个新收集器,G1收集器基于”标记-整理”算法实现,也就是说不会产生内存碎片。G1收集器收集的范围是整个Java堆(包括新生代,年老年)。
7 类加载机制
类加载过程:使用java编译器可以把java代码编译为存储字节码的Class文件,使用其他语言的编译器一样可以把程序代码翻译为Class文件,java虚拟机不关心Class的来源是何种语言。
在Class文件中描述的各种信息,最终都需要加载到虚拟机中才能运行和使用。JVM把描述类数据的字节码.class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的java类型,这就是虚拟机的类加载机制。
类从被加载到虚拟机内存开始,到卸载出内存为止,它的整个生命周期包括:加载、验证、准备、解析、初始化、使用和卸载七个阶段。