程序计数器:
程序计数器线程私有,每一个线程都有一个程序计数器,用于记录程序运行位置,且不存在内存溢出问题。
Java虚拟机栈:
每一个线程运行时所需要的内存称为虚拟机栈线程私有。
每个栈由多个栈帧组成,对应每次方法调用时所占用的内存。
每个线程只能有一个活动的栈帧,对应着当前正在执行的哪个方法。
栈帧:每个方法运行时需要的内存,记录了参数,局部变量和返回地址。
垃圾回收不涉及栈内存,栈里边是通过栈帧执行方法调用,在方法调用完成会自动执行出栈,垃圾回收管理的是堆内存。
栈内存不是越大越好,默认设置为1mb,如果栈过大会导致线程变少,因为物理内存是固定的,因为每一个线程都会有一个栈,所以栈内存过大就会导致可使用的线程数变少。
方法内的局部变量是否为线程安全的,每个线程都有自己对应的局部变量不会互相干扰。但是如果局部变量作为返回值时就会变成不安全有可能会 ,判断标准为看局部变量是否超出方法范围和赋值过程中是否使用外部变量。
栈内存溢出:StackOverflowError
1,栈帧过多导致栈内存溢出,例如方法的递归调用。(递归24864次)
2,栈帧过大会导致栈内存溢出。
线程运行诊断
cup占用过多定位:
用top命令定位占用cup进程
ps H -eo pid,tid,%cpu | grep 进程id (ps命令进一步定位哪个线程引起的问题)
jstack线程id,根据线程id找到有问题的线程,进一步定位到源码。
程序运行长时间没有结果:
使用jstack查看定位源码问题。
本地方法栈Native Method Stacks(线程私有)
用于使用java调用系统层面的方法。
堆Heap
用于存储new 创建的对象,线程共享存在垃圾回收机制
堆内存溢出OutOfMemoryError
创建对象过多且都在引用从而导致堆内存所余空间不足。
堆内存的诊断
jps工具用于查看当前系统中有哪些Java进程
jmap工具查看对内存的占用情况 jmap -heap 进程id
jconsole工具图形化界面多功能监测工具可以连续监测
方法区
所有java虚拟机线程共享,存储了类的成员变量方法数据,成员方法和构造方法即类相关的信息,在虚拟机启动时创建,逻辑上是堆的一部分,不强调方法区的位置,1.8以前叫永久代之后称为元空间。在1.8之后将方法区移动到了本地内存(Metaspace)元空间由Class ClassLoader 常量池组成。
内存溢出
1.8之前 OutOfMemoryError:PermGen space 永久代内存溢出
1.8之后 OutOfMemoryError:Metaspace 元空间内存溢出
运行时常量池
常量池是一张表,虚拟机指令根据这张表找到要执行的类名、方法名、参数类型、字面常量等信息
运行时常量池,常量池是*.class文件中的,当该类被加载他的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址。
StringTable字符串池hashtable结构不能扩容
一个字符串创建对象时会先去StringTable中查看是否已经存在,如果存在直接使用,如果不存在会创建出对象并加入到StringTable中。
String s1 = "a"
Stirng s2 = "b"
String s3 = "a"+"b"//ab
String s4 = s1+s2 //会先创建StringBuilder对象,然后将s1和s2作为参数加入,最后会new String("ab")此时对象会放入堆中。
String s5 = "ab"
String s6 = s4.intern()//将串池中还没有的值放入串池,如果有则不会放入,执行完返回串池中的值。此时串池中有"ab"所以s4的值不会放入其中而是返回已经存在的。
问:
s3==s4 fasle
s3==s5 true
s3==s6 true(因为在执行s5的时候串池中加入了"ab",执行s4的时候对象是在堆中的)
String x2 = new String("c")+new String("d")
String x1 = "cd"
x2.intern()//在jdk1.6中如果发现串池中没有,则会复制一份到串池,此时是两个对象
问:x1 == x2 (如果调换位置或者是jdk1.6)
StringTable在jvm1.6中位于永久代(空间较小),在jvm1.8时移到了堆中(空间充足)。
StringTable以哈希表的形式存储,为了提高存储效率可以通过提高桶的个数实现。为了节省大量字符串占用空间可以将字符串通过intern方法加入串池中。
直接内存Direct Memory
特点:常用于NIO操作,用于数据缓冲区,分配回收成本较高但是读写性能高,不受JVM内存回收管理归属于系统内存。
java不能直接访问系统内存,所以读取磁盘文件的时候会先由磁盘存入系统内存然后java会在堆内存中开辟一块缓冲去用于将系统内存中的数据拷贝到堆内存缓冲区中,这么做会导致效率低下为了解决这个问题从而使用直接内存即会在系统内划分出一块缓冲区用于java和系统共同io操作,减少拷贝次数提高效率。
我们可以通过ByteBuffer的allocateDirect(size)方法来申请直接内存,在ByteBuffer是通过UnSafe对象进行内存的申请的,DirectMemory垃圾回收的也是通过UnSafe来执行的,但是不推荐直接使用,想要获取UnSafe对象需要通过反射的方式获取,调用unSafe.freeMemory()方法进行直接内存的回收释放。ByteBuffer是用来Cleaner(虚引用)来监测ByteBuffer对象,一旦ByteBuffer对象被垃圾回收,那么就会由RefrenceHandler线程通过Cleaner的clean方法调用UnSafe的freeMemory来释放直接内存。
垃圾回收机制
判断对象是否可以被回收方法:
1,引用计数法:对对象进行引用记录,当引用记录为0时代表可以垃圾回收,但是会出现循环引用的情况导致不能进行垃圾回收而导致内存泄露。
2,可达性分析算法:首先确定根对象,即不可能被回收的对象,然后向下查找其他对象是否被根对象引用或间接引用,如果都不被引用代表可以被垃圾回收。
哪些对象可以作为根对象:
可以使用MAT配合jmap来分析内存中引用关系,先使用jmap -dump:format=b,live,file=1.bin 21384
内存中的引用关系
强引用 直接持有引用对象
软引用 持有的对象被回收后会进入引用队列,从而回收软引用对象本身,使用ReferenceQueue队列进行清除,可以在构建软引用的时候传人队列参数,当软引用被回收时,软引用会加入队列中,进行软引用操作时先对队列进行取值并清除出队列,完成已经为null的软引用回收。
弱引用 持有的对象被回收后会进入引用队列,从而回收弱引用对象本身,在触发gc的时候一般会被回收,回收弱引用本身也要用到引用队列逻辑。
虚引用 创建后会关联引用队列利用Cleaner,参考直接内存的回收过程
终结器引用 创建后会关联引用队列,回收时会放入引用队列,finallize()由一个优先级非常低的Finalizer线程去执行释放资源。
垃圾回收算法
标记清除算法,第一步先进行垃圾标记,第二步进行垃圾清除释放。优点速度快不需要额外操作,但是会产生较多的内存碎片,在存入较大对象时,虽然总的内存充足但是没有连续的内存空间去存储也会内存溢出。
标记整理算法,第一步先进行垃圾标记,第二步进行垃圾清除的同时会对内存进行整理,从而解决内存碎片问题。优点是没有内存碎片,因为整理过程中改变了对象的内存地址从而速度会较慢。
复制算法,将内存区分为大小相等的两块,同一时间内会有一块内存为空白,垃圾回收时会将存活的对象复制到二内存中,同时对一内存进行清空。然后重复此过程,优点时不会产生内存碎片,但是会双倍占用内存空间。
分代垃圾回收机制
将堆内存划分为新生代new generation和老年代tenured generation,新生代又分为三个区域Eden、From Survivor、To Survivor,三个区域内存占比为8:1:1。对象刚创建时会进入新生代中的Eden空间中,当触发垃圾回收时会将存活对象复制到To Survivor空间中同时进行标记加1,同时将From Survivor中存活对象也复制到To Survivor空间同时交换From和To空间,之后会重复此过程,经过多次的存活标记对象超过默认的15次后(可以设置)会将对象晋升到老年代中。当老年代内存空间不足时会先触发minor gc如果空间仍然不足会触发Full Gc(STW时间更长),从而实现一个包含新生代和老年代的垃圾回收过程。垃圾回收过程会暂停其他用户线程,等垃圾回收结束后其他用户线程才会恢复运行。
当新生代内存不足时,有新的内存需要分配占用会使新生代对象提前晋升老年代,当加入大对象时会直接放入老年代且不会出发GC。当放入的对象超过新生代和老年代的内存空间,会导致oom但在之前会触发两次垃圾回收,一次GC一次Full GC。当在子线程的内存溢出不会导致整个java进程的结束。
垃圾回收器分为三种
1,串行垃圾回收器:
-XX:+UseSerialGC = Serial(新生代复制算法) + SerialOld(老生代标记整理算法)
单线程执行,适用于堆内存较小的个人电脑。选取安全点暂停其他用户线程进行垃圾回收,回收完成恢复用户线程。
2,吞吐量优先:
-XX:+UseParallelGC(新生代复制算法) ~ -XX:+UseParallelOldGC(老生代标记整理算法)
多线程运行适合堆内存较大,设备多核cpu支持。让单位时间内STW时间最短。即在一段时间内控制STW时间。选取安全点暂停用户线程,开启多线程进行垃圾回收,多线程数量与cpu核相关,回收时会最大程度占用cup,回收后恢复用户线程。
3,响应时间优先,多线程运行适合堆内存较大,设备多核cup支持。尽可能使单次STW时间最短。即使每一次的STW时间尽量短。