目录:
(1)JVM概念
(2)程序计数器-作用
(3)栈
问题辨析:
内存溢出:
(4)线程诊断
(5)本地方法栈
(1)JVM概念
定义:
刚开始java源代码Helloword.java,经过javac编译成了class字节码,.class字节码再经使用ava程序加载到java虚拟机里面就可以运行了,jvm就是java程序的运行环境。
好处:
java语言是跨平台的, 正是由于 JVM,JVM屏蔽了字节码跟底层系统之间的差异,对外提供了一个一致的运行环境,jvm最终用解释的方法解释二进制字节码,来达到代码平台的无关性
JVM+基础类库:才能构成完整意义上的java运行时环境,JRE是完整运行时环境的缩写
JVM+基础类库+编译工具(javac等等):java开发工具包
java为二进制字节码,经过类加载器加载到JVM里去运行,类都是放在方法区的,类创建的实例对象都是放在堆的部分,堆的对象当在调用方法时会用到虚拟机栈、程序计数器、本地方法栈
方法执行时是由执行引擎中的解释器逐行进行执行
方法中的热点代码也就是频繁执行的代码会由JIT的及时编译器做编译或者认为优化后的执行
GC:对堆里面不在使用的对象进行垃圾回收
还有一些java代码不方便实现的功能,必须调用底层操作系统的功能,需要跟操作系统的功能打交道,需要借助本地方法接口来调用操作系统提供的一些功能方法
(2)程序计数器-作用
解释器会解释指令为机器码交给 cpu 执行,程序计数器会记录下一条指令的地址行号,这样下一次解释器会从程序计数器拿到指令然后进行解释执行。
程序计数器是寄存器来实现的,寄存器是cpu组件里读取最快的一个单元,因为我们读取指令地址是非常频繁的,所以jvm虚拟机在设立的时候,就把我们cpu的寄存器当做计数器来存储地址,将来去读取这个地址
多线程的环境下,CPU会有一个调度器组件,给他们分配时间片,比如说给线程1分配一个时间片,在这个时间片内如果没有执行完,就会把线程1的状态进行暂存,切换到线程2去,线程2执行完时间片,再切换回来执行线程1剩余的部分。如果两个线程发生了上下文切换,那么程序计数器会记录线程下一行指令的地址行号,以便于接着往下执行。
(3)栈
特点:先进后出
java中的虚拟机栈:线程运行的内存空间,一个线程需要一个栈,多个线程会有多个虚拟机栈,一个栈内由多个栈帧组成,一个栈帧对应一次方法的调用,线程是要执行代码的,代码是由一个个方法组成,,每个方法运行需要的内存称为栈帧
Frames:可以理解为虚拟机栈:调用方法,依次进入
方法2执行结束释放掉:
方法1结束:只剩下一个主方法
主方法再执行方法执行结束:
问题辨析:
解答1 否:因为栈内存无非就是一次一次的方法调用,所产生的栈针内存,栈针内存调用结束后,会被弹出栈,会自动的被回收掉,因此不需要垃圾回收来管理我们的栈内存
解答2 否:栈内存可以在运行代码时通过虚拟机参数来指定,不指定Linux、macOS、Oracle默认1024KB。不是,因为物理内存的大小是一定的,比如说,一个线程使用的是栈内存,一个线程使用1兆内存,假设总共的物理内存有500兆,理论上可以有500个线程同时运行,但是给每个线程的栈内存设置2兆的内存,那么只能同时运行250个线程,所以,栈内存并不是越大越好,并不会增强运行效率
解答3:看这个变量是否线程安全,看这个变量多个线程对这个变量是共享的,还是这个变量对每个线程是私有的
多个线程同时执行这个方法,是否造成这个x值的混乱呢?不会,因为x变量是方法内的局部变量,一个线程对应一个栈,线程中每一次方法调用,都会产生一个新的栈帧
这个变量时线程私有的,所以不会有在多线程的环境下会有多线程的影响的问题
如果x为static的话,如果不加线程保护的话,会产生线程安全问题。是每个线程共享的,需要考虑线程安全,是每个线程私有的,不用考虑线程安全
m1是线程安全的、m2不是线程安全的、m3是线程不是安全的
m2:不是方法的局部变量,而是方法的参数传递进来,有可能有其他的线程能够访问倒它,就不是线程私有的了
m3方法sb是局部变量,他作为返回值,逃离了方法的作用范围,有可能被别的线程访问到,出现问题
内存溢出:
在方法的递归调用中没有设置正确的结束条件,就会导致自己调用自己不能结束,就会导致内存溢出
案例:
通过虚拟参数,可以设置栈内存小一些,看一看方法递归次数是否会减少
通过-Xss设置参数
重新启动:这一次只循环了5222次。导致内存溢出
栈帧过大导致内存溢出:这种情况很少出现,因为栈帧里都是局部变量啊、方法参数呀、他们占用的内存都很少
案例二:
运行出现了栈内存溢出的错误
是由于我们引用的两个类循环引用问题导致jaon解析时出现内存溢出,怎么解决呢在json引用时打破循环引用
给员工类加注解:@JsonIgnore:转换时忽略这个属性转换。把双向关联改为单项关联,只通过部门去关联员工了,员工不在关联部门了
改动成功后再次运行:
(4)线程诊断
在Linux中后天运行一下java代码:
使用top命令检测后台进程 对cpu的使用占用情况:
发现这个java代码占用了cpu的99.3。top命令只能定位到某个进程,不能定位到某个线程导致cpu占用
使用ps命令查看线程对cpu的占用情况:ps H -eo pid,tid,%cpu
H:把进行的所有线程信息展示出来
-eo:规定输出哪些内容, 比如pid:进程id、tid:线程id
%cpu:对cpu的占用
是把Linux里面所有的进程、线程、占用情况都输出了
如果数量过多,使用grep进行 筛选已知的进程编号
使用jdk提供的工具jstack到底是那个线程出现的问题了
jstack 进程id
它能把这个进程中所有的java线程列出来
用jstack输出的线程编号是16进制的,需要把十进制的线程编号做一个换算
发现thread1的nid为7f99,所以问题在thread1,这个线程转态是RUNNABLE,一直在运行,使它占用cpu过多时间片
他还能把具体的java代码行数显示出来,有问题的代码
然后再查询java源代码,根据它指示的行数:再进行具体的解决
运行另外一个程序
等了很久没有出现想要的结果,可能是某个线程出现了死锁,如何解决线程死锁的问题呢
使用jstack 进程id 查看一下:
可以看到线程的信息
发现了死锁
在java代码中29行、21行出现死锁
(5)本地方法栈
是虚拟机调用本地方法时需要给本地方法提供的一个内存空间,本地方法运行的时候使用的内存叫本地方法栈