java内存结构学习
方法区,堆,本地方法栈,程序技术器,栈
线程私有:
栈:为执行java方法服务。
本地方法栈:主要是native方法。
程序计数器:主要存放当前执行指令的地址
线程共享:
堆:存放类的实例信息,绝大多数创建的实例对象会存放在这里。
方法区:主要存放类的信息,常量,静态变量。垃圾回收器针对这块的回收主要是针对常量池和类的卸载。
jvm只是规定了有方法区这么个概念和它的作用,并没有规定如何去实现它,jdk8永久代被废弃了,永久代替换成本地内存。
jvm类加载过程:
(1)加载:jvm去查找字节流(.class文件),将.class文件中的二进制数据读入内存,放在运行时区域的方法区内,然后在堆中创建java.lang.Class对象,用来封装类在方法区的数据结构。
(2)链接:
1.验证:验证加载进来的二进制数据是否满足虚拟机规范,不会造成安全错误。
2.准备:负责为类的静态成员分配内存,并设置初始值。
3.解析:将类的二进制数据中的符号引用替换成直接引用。
符号引用:即一个字符串。
直接引用:可以理解为一个内存地址。
(3)初始化:
初始化,则是为标记为常量值的字段赋值的过程,只对static修饰的变量和语句进行初始化。
jvm调优:
(1)设置新生代和老年代的:
设置原则:
a.尽可能的让对象在新生代被回收,让对象尽量的存活在survivor,使之在新生代被回收。
b.如果内存不够,则增大survivor区,减少eden区。
设置参数:
-Xmx:设置堆最大可用值
-Xms:设置堆初始值
-Xmn:设置新生代最大可用值
-XX:newRatio:设置新生代与新生代的比例
-XX:SurvivorRatio 设置Eden与survivor的比例
(2)栈内存设置
-Xss1024k:设置每个线程的堆栈大小
栈内存设置过小,则容易栈溢出,设置过大,就会,线程数量就不好控制,如果是多线程的应用,容易造成内存溢出。
总结常用的jvm排查工具:
(1)查看哪些对象占用内存。
现象:查看gc.log的日志,发现full gc很频繁
思路:是否发生了内存泄露,要知道内存泄露就最好先知道是哪些对象占用了内存,并且长时间没有释放。
排查:
a.查看jvm内存查看(gc日志),发现老年代占用内存6.8G,总的堆内存大小为7.1G,使用占比95%,也就是老年代很容易满,很容易触发full gc。
b.定位是哪些类在占用内存
gcore 进程pid(gcore产生core文件,这比直接使用使用jmap dump快的多,可以避免对线上服务造成影响,core文件的大小跟进程使用的堆大小一样大。)
jmap dump;format=b,file=heap.hrpof.bin ../bin/java core.xxxx(将core文件转换成bin文件,hprof文件跟进程实际的堆内存一样大)
使用eclipse的MAT工具分析出持有的最大的几个类,假设类A,类B
由于从数据库查询,从文件中查询数据,从hbase中查询数据,缓存到A的字段属性中,每次操作时间很长,用空间换时间。
c.临时处理:
重新规划老年代的比例。
-Xms太低,导致内存频繁申请和回收。
d.作业迁移,定时计算。
(2)查看线程占用应用的时间长短。
现象:后台java进程占用CPU过高
思路;是否有线程一直没有释放,查看线程占用时间。
排查:
a.ps -mp pid -o THREAD,tid,time 查看进程信息
找到耗时最高的线程xxx,占用cpu yyy小时
b.print “%x\d” tid
将需要打印的线程id转换为16进制格式
c.jstack pid| grep tid -A 60
打印线程的堆栈信息