1.JVM内存管理的机制
内存空间划分为:Sun JDK在实现时遵照JVM规范,将内存空间划分为堆、JVM方法栈、方法区、本地方法栈、PC寄存器。
![](https://i-blog.csdnimg.cn/blog_migrate/80a6a343e71e5917fed02d5fca90eb92.jpeg)
(jvm参数配置是在tomcat中配置)
Java堆: 堆用于存储对象实例及数组值,可以认为Java中所有通过new创建的对象的内存都在此分配,Heap中对象所占用的内存由GC进行回收,在32位操作系统上最大为2GB,在64位操作系统上则没有限制,其大小可通过-Xms和-Xmx来控制,-Xms为JVM启动时申请的最小Heap内存,默认为物理内存的1/64但小于1GB;-Xmx为JVM可申请的最大Heap内存,默认为物理内存的1/4但小于1GB,默认当空余堆内存小于40%时,JVM会增大Heap到-Xmx指定的大小,可通过-XX:MinHeapFreeRatio=来指定这个比例;当空余堆内存大于70%时,JVM会减小Heap的大小到-Xms指定的大小,可通过-XX:MaxHeapFreeRatio=来指定这个比例,对于运行系统而言,为避免在运行时频繁调整Heap 的大小,通常将-Xms和-Xmx的值设成一样。
JVM方法栈: 为线程私有,其在内存分配上非常高效。当方法运行完毕时,其对应的栈帧所占用的内存也会自动释放。
当JVM方法栈空间不足时,会抛出StackOverflowError的错误,在Sun JDK中可以通过-Xss来指定其大小。
方法区: 要加载的类的信息(名称、修饰符等)、类中的静态变量、类中定义为final类型的常量、类中的Field信息、类中的方法信息。方法区域也是全局共享的,在一定条件下它也会被GC,当方法区域要使用的内存超过其允许的大小时,会抛出OutOfMemory的错误信息。在Sun JDK中这块区域对应Permanet Generation,又称为持久代,默认最小值为16MB,最大值为64MB,可通过-XX:PermSize及-XX:MaxPermSize来指定最小值和最大值。
本地方法栈: 用于支持native方法的执行,存储了每个native方法调用的状态。在Sun JDK的实现中,和JVM方法栈是同一个。
PC寄存器: 占用的可能为CPU寄存器或操作系统内存。
2.Java堆和栈的区别
Java把内存划分成两种:一种是栈内存,一种是堆内存。
在函数中定义的一些基本类型的变量和对象的引用变量都在函数的栈内存中分配。当在一段代码块定义一个变量时,Java就在栈中为这个变量分配内存空间,当超过变量的作用域后,Java会自动释放掉为该变量所分配的内存空间,该内存空间可以立即被另作他用。
堆内存用来存放由new创建的对象和数组。在堆中分配的内存,由Java虚拟机的自动垃圾回收器来管理。在堆中产生了一个数组或对象后,还可以在栈中定义一个特殊的变量,让栈中这个变量的取值等于数组或对象在堆内存中的首地址,在栈中的这个特殊的变量就变成了数组或者对象的引用变量,以后就可以在程序中使用栈内存中的引用变量来访问堆中的数组或者对象,引用变量相当于为数组或者对象起的一个别名,或者代号。
引用变量是普通变量,定义时在栈中分配内存,引用变量在程序运行到作用域外释放。而数组&对象本身在堆中分配,即使程序运行到使用new产生数组和对象的语句所在地代码块之外,数组和对象本身占用的堆内存也不会被释放,数组和对象在没有引用变量指向它的时候,才变成垃圾,不能再被使用,但是仍然占着内存,在随后的一个不确定的时间被垃圾回收器释放掉。这个也是java比较占内存的主要原因。但是在写程序的时候,可以人为的控制。
3.Java内存泄露和内存溢出
内存泄漏:分配出去的内存回收不了
内存溢出:指系统内存不够用了
4.内存回收
收集器:引用计数收集器、跟踪收集器
引用计数收集器:对于Java这种面向对象的会形成复杂引用关系(如ObjectB和ObjectC互相引用)的语言而言,引用计数收集器不是非常适合,Sun JDK在实现GC时也未采用这种方式。
跟踪收集器实现算法:复制(Copying)、标记-清除(Mark-Sweep)和标记-压缩(Mark-Compact)
复制:当要回收的空间中存活对象较少时,复制算法会比较高效,其带来的成本是要增加一块空的内存空间及进行对象的移动。
标记-清除:在空间中存活对象较多的情况下较为高效,但由于标记-清除采用的为直接回收不存活对象所占用的内存,因此会造成内存碎片。
标记-压缩:在标记-清除的基础上还须进行对象的移动,成本相对更高,好处则是不产生内存碎片。
对象优先在Eden分配,当Eden区没有足够空间分配时,虚拟机将发起一次Minor GC;
Minor GC:新生代GC,指发生在新生代的垃圾收集动作,因为Java对象大多都具备朝生夕灭的特性,所以MinorGC非常频繁,一般回收速度也比较快;
MajorGC/FullGC:发生在老年代的GC,MajorGC的速度一般会比MinorGC慢10倍以上;
大对象直接进入老年代
长期存活的对象将进入老年代:虚拟机给每个对象定义了一个对象Age计数器。如果对象在Eden出生并经过第一次MinorGC后仍然存活,并且能被Suervivor容纳的话,将被移动到Survivor空间中,并且对象年龄增设为1.对象在Survivor每熬过一次MinorGC,年龄就增加1岁,当它的年龄增加到一定程度(默认15岁),就将会被晋升到老年代中。
动态对象年龄判定:如果在Survivor空间中相同年龄所有对象代销的总和大于Survivor空间的一般,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄。
JVM
Java虚拟机是Java EE平台的基础,它是中间件和应用程序被部署和裕兴的地方。
Jim向中间件软件和你的Java/Java EE程序提供:
①而简直形式的Java/Java EE程序运行环境
②一些程序功能特性和工具(IO基础设施,数据结果,线程管库,安全,监控等等)
③借助垃圾回收的动态内存分配和管理
Java线程堆栈是一个给定时间的快照,它能提供所有创建出来的Java线程的完整清单。
一个Java线程包括:
线程的名称,线程类型&优先级,Java线程ID,原生线程ID,Java线程状态和详细信息,Java线程栈跟踪,Java堆内存分解
不再引用和不再需要时两个不同的概念,不再引用是从虚拟机的角度看对象,而不再需要是从程序员角度看。在某些场合下,需要清洗的告诉虚拟机,那么对象才会不被引用到。
对象&对象引用
Person p1 = new Person();
p1是对象引用
将一个对象引用设为null,是表示让该引用不指向任何其它物理内存,仅此而已 .将一个引用设为null,都是业务逻辑的需要,而不是为了内存关系的需要。将对象引用设为null,对内存泄露是没有实际价值的。
Java进程内存, 指整个Java进程占用的内存。等于Java堆内存+Perm内存+本地内存:
进程大小是java堆内存、Perm内存、本地内存与加载的可执行文件和库所占用内存的总和。
1. Java堆内存,这是JVM用来分配java对象的内存。即通过-Xmx-Xms设置的用来分配给Java对 象的内存。当执行一句java分配对象的代码:new String();那么就是从这块内存进行分配 的。如果未指定最大的堆大小,那么该极限值由JVM 根据诸如计算机中的物理内存量和 该时刻的可用空闲内存量这类因素来决定。始终建议您指定最大的java 堆值
2. Perm内存(PermGen space的全称是Permanent Generation space,是指内存的永久保存区域), 即通过-XX:PermSize设置的内存,这块内存是虚拟机用来加载class字节码文件的内存。 这块内存在系统运行期一般比较固定。因为类文件是有限的。这里所说的一般,还有一些 不一般的情况,目前在有些面向方面的编程中,会动态进行代码织入29操作,即系统在运 行期间会修改或者增加字节码,这种情况下会导致类改变或者增加新的类,类需要重新 加载,如果持续地有新类产生,可能会导致这块内存一直增加,直到这块内存溢出.
3. Java进程本地内存,这是JVM 用于其内部操作的内存。JVM 将使用的本地内存数量取决 于生成的代码量、创建的线程、GC 期间用于保存java 对象信息的内存,以及在代码生成、 优化等过程中使用的临时空间。如果有一个第三方本地模块,那么它也可能使用本地内 存。例如,本地JDBC 驱动程序将分配本地内存。最大本地内存量受到任何特定操作系统 上的虚拟进程大小限制的约束,也受到用-Xmx 标志指定用于java 堆的内存量的限制。例 如,如果应用程序能分配总计为3 GB 的内存量,并且java 堆的大小设为1 GB,那么本地 内存量的最大值可能在2 GB 左右。即JVM使用的本地内存由如下几部分组成:
(a) Java.exe是C/C++写的程序,运行过程中自然需要内存,包括操作系统加载该程序, 和java.exe运行过程中自己分配的内存。
(b) JNI调用动态库使用的内存,即JNI中调用new或者malloc的内存等。
堆内存 Java虚拟机为Java对象(即Java代码中通过new操作符创建的对象)预留的空间。这块内存通过-Xmx和-Xms指定。
Perm内存 Java虚拟机为加载class预留的空间,这块空间通过-XX:PermSize指定。
本地内存 Java虚拟机本身是C/C++写的,运行期间自然也需要内存,另外如果系统 中有JNI调用,那么Native 代码中使用的内存也是这块。这块内存没有参数进行指 定。 这块内存主要是被JVM用作存放Class和Meta信息的,这些信息一经载入,就很少发生变 化,class就属于这种类型的数据,当Class在被Loader时就会被放到PermGen space中, 它和存放 类实例(Instance)的Heap区域不同,GC(Garbage Collection)不会在主程序运行期对PermGenspace进 行清理 。
当在Java中创建一个java线程的时候,同时也创建一个操作系统的 线程,而实际工作的是这个操作系统的线程。Java中的线程实际上是一个虚拟的。
并行垃圾回收,这里的并行相对于垃圾回收线程自己,即垃圾回收线程有多个,垃圾回收 线程是并行运行的。
并发垃圾回收,这里的并发是相对于Java应用程序而言的,垃圾回收和应用程序并发运行。
JVM命令行参数分为三种:
①标准的运行期参数
②-X扩展参数
③-XX扩展参数
JIT:允许实时将Java解释型程序自动编译成本地机器语言,以使程序执行的速度更快。有些JVM包含JIT编译器。
为了能做到平台无关,Java先把源代码编译成字节码,由JVM得interpreter转换成适合该平台的机器码。但解析运行很慢,于是把比较常用的bytecode先用JIT编译处理,下次遇到相同代码可直接调用以加快速度。
字节码解析很慢,JVM运行时解析器必须读取字节码译码,然后执行,循环往复,这个过程是非常消耗事件的,JIT就是为了解决这个。
JIT不是JVM的组成部分,但它是一个Java运行环境的一个标准组件。
正确看待虚拟机:
虚拟机实际上是一个程序,当程序启动时,就开始执行八寸在.class文件中的字节码指令。.class中的指令的执行是由虚拟机程序来执行的。.class相当于脚本
java.lang.StackOverflowError 出现这种情况是由于代码存在递归调用导致的调用层次太多,超过了系统的限制。通过观察异常堆栈,很容易发现问题所在。
如何计算GC消耗
过高的GC消耗会使系统变慢,同时暗示过多的创建和释放对象,需要进行优化。计算方法比较简单,可以扫描和分析文件,将所有GC条目消耗的时间除以应用运行的总时间即可,
公式为:GC时间和/系统运行时间
假设上例运行了2分钟即120秒,GC时间和为0.5225017秒,则GC占用CPU比率为0.5225017/120,约等于0.004,等于0.4%,因此GC消耗占了0.4%。
注意:系统在运行时间内并不是100%的占用CPU,偶尔也会有其他程序抢占CPU,可以通过一些操作系统工具来统计占用比例,假设实际上在2分钟内只占用了10%的CPU时间,则上例的GC消耗为:0.5225017/(120*10%),约等于4%,因此GC消耗实际占了4%
GC性能指标
参考一些JAVA性能调优网站上的指标,GC时间在低于5%的时候很理想,5%-15%为可接受,超过15%则被认为GC过于频繁,对象数量过多。
Java的自动垃圾回收只能保证内存被回收,不能保证资源被回收,两者是不同的概念。不同的资源回收方法不同,必须显式的调用资源的回收代码
Double-Checked Locking( 双检查锁)是一种非常广泛使用的一种代码模式,用在多线程场合下的懒初始化 (lazy initialization)
public synchronized MyInstance getInstance(){
if(this.instance == null){
this.instance = new MyInstance90;
}
return this.instance;
}
池的设计:常见的池有对象池,线程池,连接池;
对象池化得基本思路是:将用过的对象保存起来,等下一次需要这种对象的时候,再拿出来重复使用,从而在一定程度上减少频繁创建对象所造成的开销。只有重复生成某种大对象的操作成为影响性能的关键因素的时候,才适合进行对象池化。对一些小对象,使用池化技术不能带来任何性能的提升,反而会导致一定的性能下降。
Java问题定位技术
功能性问题定位属于简单的问题,可使用单步跟踪等方法
复杂问题定位需着重稳定性和可靠性。
1.线程堆栈 也称 作线程调用堆栈。
Java线程堆栈是虚拟机中线程(包括锁)状态的一个瞬间快照,即系统在某 个时刻所有线程的运行状态,包括每一个线程的调用堆栈,锁的持有情况等信息。每一种Java虚 拟机(SUN JVM、IBM JVM、JRokit、GNU JVM等等)都提供了线程转储(thread dump)的后门, 通过这个后门可以将那个时刻的线程堆栈打印出来。
线程堆栈的信息都包含:
• 线程的名字,ID,线程的数量等。
• 线程的运行状态,锁的状态(锁被哪个线程持有,哪个线程再等待锁等)。
• 调用堆栈(即函数的调用层次关系)。调用堆栈包含完整的类名,所执行的方法,源代码 的行数。
Java线程实际上和本地线程指的是同一个东西,只有本地线程才是真正的线程实体,
Java线程实际上就是指这个本地线程,它并不是一个另外存在的的实体。
Java线程”runnable"表示当前线程处于运行状态。这个runnable状态是从虚拟机的角度来看的,表示这个线程正在运行。但是处于Runnable状态的线程不一定真地消耗CPU. 处于Runnable的线程 只能说明该线程没有阻塞在java的wait或者sleep方法上,同时也没等待在锁上面。但是如果该线程调用了本地方法7,而本地方法处于等待状态,这个时候虚拟机是不知道本地代码中发生了什么,此时尽管当前线程实际上也是阻塞的状态,但实际上显示出来的还是runnable状态, 这种情况下是不消耗CPU的 。
类的初始化
对象的初始化
当执行obj.wait(2000),当前线程正在被挂起,该线程当前不消耗CPU;
当执行Thread.sleep(2000),表示当前线程被挂起一段时间,该线程当前不消耗CPU;
线程死锁原因:
当两个或多个线程正在等待被对方占有的锁,死锁就会发生。
在Java事务管理中,三种可用的模型分别为 本地事务模型,编程式事务模型,声明式事务模型
本地事务模型:开发者管理的只是资源连接,而不是事务。真正管理本地事务的是数据库系统。
编程式事务模型:不鼓励,唯一使用的接口javax.transaction.UserTransaction
使用的方法:begin(),commit(),rollback(),getStatus()
声明式事务模型:只需在配置文件中配置,管理的是事务而不是连接
接口:TransactionManager
ACID指原子性Atomocity,一致性Consistency,隔离性Isolation,持久性Durability
JTS Java Transaction Service
JTA是一组可供开发者们管理事务的接口,JTS是JTA的底层事务实现者。关系类似于JDBC与底层数据库驱动。
JTA 等效于 JDBC API, 而 JTS 则等效于相应 的实现驱动。
Garbage-First,面向服务器的垃圾收集器,主要针对配置多处理器及大容量内存的机器,以极高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征。
可以像CMS收集器一样,GC操作与应用的线程一起并发执行
紧凑的空闲内存区且没有很长的GC停顿时间
需要可预测的GC暂停耗时
不想牺牲太多吞吐量性能
启动后不需要请求更大的Java堆
G1 vs CMS:G1是一款压缩型的收集器,G1通过有效的压缩完全避免了对细微空闲内存的分配,不用依赖于regions,大大简化了收集器,还消除了潜在的内存碎片问题。
G1执行垃圾回收的处理方式与CMS相似. G1在全局标记阶段(global marking phase)并发执行, 以确定堆内存中哪些对象是 存活的。标记阶段完成后,G1就可以知道哪些heap区的empty空间最大。它会首先回收这些区,通常会得到大量的自由空间. 这也是为什么这种垃圾收集方法叫做Garbage-First(垃圾优先)的原因。顾名思义, G1将精力集中放在可能布满可收回对象 的区域, 可回收对象(reclaimable objects)也就是所谓的垃圾. G1使用暂停预测模型(pause prediction model)来达到用户定 义的目标暂停时间,并根据目标暂停时间来选择此次进行垃圾回收的heap区域数量
被G1标记为适合回收的heap区将使用转移(evacuation)的方式进行垃圾回收. G1将一个或多个heap区域中的对象拷贝到其 他的单个区域中,并在此过程中压缩和释放内存. 在多核CPU上转移是并行执行的(parallel on multi-processors), 这样能减少 停顿时间并增加吞吐量. 因此,每次垃圾收集时, G1都会持续不断地减少碎片, 并且在用户给定的暂停时间内执行. 这比以前 的方法强大了很多. CMS垃圾收集器(Concurrent Mark Sweep,并发标记清理)不进行压缩. ParallelOld 垃圾收集只对整个堆 执行压缩,从而导致相当长的暂停时间。
需要强调的是, G1并不是一款实时垃圾收集器(real-time collector). 能以极高的概率在设定的目标暂停时间内完成,但不保证 绝对在这个时间内完成。 基于以前收集的各种监控数据, G1会根据用户指定的目标时间来预估能回收多少个heap区. 因此, 收集器有一个相当精确的heap区耗时计算模型,并根据该模型来确定在给定时间内去回收哪些heap区.
注意 G1分为两个阶段: 并发阶段(concurrent, 与应用线程一起运行, 如: 细化 refinement、标记 marking、清理 cleanup) 和 并行阶段(parallel, 多线程执行, 如: 停止所有JVM线程, stop the world). 而 FullGC(完整垃圾收集)仍然是单线程的, 但如果进 行适当的调优,则应用程序应该能够避免 full GC。
S Concurrent Mark Sweep并发标记清理收集器,回收年老代内存。