其他分享
首页 > 其他分享> > JVM《基础篇》

JVM《基础篇》

作者:互联网

我们学的是oracle的jvm

学习路线:

程序计数器:

定义:

Program counter Register 程序计数器(寄存器)

作用,是记住下一条jabm指令得执行地址

特点

是线程私有得,每一个线程都有自己得程序计数器(主要用于记住,当前执行到线程代码得地址)

不会存在内存溢出

回顾数据结构栈:

先进后出,后进先出的原则,

参考递归

栈-线程运行需要的内存空间,

一个栈有多个栈帧组成

栈帧对应一次方法的调用,所以,在线程运行的时候每个方法需要的内存我们称为栈帧。

栈帧:每个方法运行时需要的内存;

这里回顾一下方法调用的操作:main 调用方法a ,a方法内部调用的b,这个时候按照栈的加载进入程序,最后释放的是main方法;

Java Virtual Machine Stacks(java虚拟机栈)

*)每个线程运行时需要的内存,称为虚拟机栈

*)每个栈帧由多个(Rrame重新载入帧)组成,对应着每次方法调用时所占用的内存

*)每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法 (栈顶部栈帧称为活动栈帧)

问题::

1.垃圾回收是否涉及我们的栈内存?

不需要,栈内存是栈帧的一次次调用,栈帧内存在每一次结束调用后,都会被弹出栈,会被自动回收掉,不需要垃圾回收,垃圾回收回收的是堆内存中的无用对象,栈内存不需要。不会也不需要

2.栈内存分配越大越好么?

内存越大,线程数越小,(物理内存是固定的,),假设:一个线程使用的栈内存使用的1兆的内存,我物理内存是500,理论上可以运行五百个线程;

如果我给每个给每个栈内存设置了2兆的内存,理论上就变成只能运行250个线程。

建议使用系统默认内存

错,栈内存过大,相应就是代码增多,提高了解耦难度,冗余度也随之提高

这里可以指定栈内存的大小(-Xss),不指认大小,会跟根据操作系统默认大小

Windows,会根据自身的虚拟内存来影响每个栈的大小

3.方法内部的局部变量是否线程安全?

1.分析这个变量是否私有还是线程共有:是否static修饰

线程不共用一个变量的时候:

局部变量,对应一个栈帧,线程内每次调用一个方法都会产生一个栈帧,

也就是说不同线程拿到的成员变量都是自己私有的一个变量。他们各自++互不干扰;

我们通过案来理解

这个栈帧是安全的,栈帧加载后创建出的局部变量,其他线程是访问不到的

这个局部变量是不安全的,它以形参方式传递进来,有可能会被其他线程访问并且修改掉数据(形参共享)

局部变量本身是安全的,但是被当成返回结果返回了,这就有可能被其他线程拿到这个结果来引用,并且并发的去修改它,这个时候就不再是安全的了。

栈内存溢出:

学习什么情况下导致 内存溢出

1.栈帧过多,栈内存是固定的

导致溢出

递归调用如果没有设置一个正确的计数条件,就会导致不停的产生栈帧。(在不明却递归条件的值是否安全是,避免使用递归)

2.栈帧过大!

基本不会出现这种情况,基本不存在,栈帧过大出现溢出,都是栈帧过多。

设置栈内存的大小:

1

2

3:日常开发中,第三方库也会导致 stackOverflowError

转json用OjectMapper的时候,部门包含员工,员工包含部门,形成一个递归映射的关系,导致现栈内存溢出。 (两个类之间的循环引用关系导致递归死循环)

解决方式:

在转json转的过程中打断这种关系,比如:

我转换员工的时候,遇到这个部门转换,我们就不转了;(把双向关系,打破转变成单项关系)

线程运行诊断:

案例1:cpu占用过多:!!!

定位线程(Linux)

一定要避开这个问题!!

案例2:程序运行很长时间没有结果:

Native Mthod Stacks (本地方法栈)

就是一些java基本构成的一些方法,他们没有具体返回值,native的方法声明,它的方法实现都是通过c来实现的, 我们的java代码通过这个native间接的去调用本地方法接口去调用c或从c++的实现,Object里面的方法基本都是。

Heap(堆)

通过new关键字,创建对象都会使用堆内存

特点:

*)它是线程共享的,堆中对象都要考虑线程安全的问题;

*)有垃圾回收机制

outOfMemoryError错误经典案例:

4G运行了26次空间不够

-Xmx8m堆内存空间设置(8m)

17次空间不够

堆内存诊断(Heap stacks)

jps工具:

查看当前系统中有那些Java进程

命令jps查看进程id

jmap工具:

查看堆内存占用清空(工具使用代码:jmap -heap)

jconsole工具

图形界面的,多功能监测工具,可以连续检测

(命令就是jconsole)

最好用的工具jvistualvm:

案例:垃圾回收后,内存占用仍然很高

堆赚储

Method Area(方法区):

CGlib动态代理的实现Spring,mybatis,ClassWriter。他们底层都实现了一个叫ClassVisitor的字节码接口。用于实现动态代理,

CGlib也有ClassWriter,他们都出自一个类,都是在运行期间动态生成类字节码。代理技术里面广泛引用到这个技术。

堆中方法区,定义永久代

元空间(现在使用操作操作 系统的分配)

1.6的时候存储在永久代,但是1.8之后永久代这个实现被废弃了,

但是方法区还是概念上的东西;

它的实现,变成了method space(元空间),不过它已经不占用堆内存了。

已经不是由jvm在管理,移存到本地内存去了(heap堆中);

线程共享区域,存储了一些类结构的信息;

field 成员变量;

method data 方法数据;

code for methods and consrructors

就是我们的成员方法以及构造器方法他们的代码 包括一些special method特殊方法;

还有运行时常量池:run-time-constant-pool(单独说)

方法区内存溢出:

1.8以前会导致永久代内存溢出

-XX:MaxMetaspaceSize=8m

永久代会被GC管理

这里主要演示的是元空间的内存溢出,元空间中是基于系统内存来分配的,跟物理运行内存有关;

public class PermGenOverflowTest extends ClassLoader{
    @Test
    public  void permGenOverflow(){
        int j=0;
        try{
            PermGenOverflowTest test = new PermGenOverflowTest();
            for (int i = 0; i < 10000; i++,j++) {
                //ClassWriter 生成二进制字节码
                ClassWriter writer = new ClassWriter(0);
                //版本号      权限修饰符      类名        父类    接口
                writer.visit(Opcodes.V1_8,Opcodes.ACC_PUBLIC,"Class"+i,null,"java/lang/Object",null);
                byte [] code = writer.toByteArray();
                test.defineClass("Class"+i,code,0, code.length);//只执行了类的加载
            }
        }finally {
            System.out.println(j);
        }
    }
}

Metaspace空间导致的内存溢出

1.8之后会导致元空间内存溢出

这里介绍一个类:Clasloader类//用来加载类的二进制字节码

ClassWtier作用时生成类的二进制字节码

添加参数给元内存空间指定大小

动态生成场景

运行时常量池:

常量池中进行的赋值操作,程序计数器也是在这里

javap -c 类名.class

常量池与串池的关系:

常量池中的信息,都会被加载到运行时常量池中;这时都是常量池中的符号,还没

有变为java中的字符串对象;

类的组成

反编译一个类查看

javap -v App.class//反编译一个类,查看这个类信息

这个是时候,串池是这样的StringTable[]

串池:

是一个hashtable的结构,不能扩容

1.如果字符a在串池中没有,StringTable[“a”]中添加a,都是串池先找一圈;

2.懒惰的模式;执行到,用到它的时候才会去创建或查找它;

在串池中,相同数据(地址)只会存储一次

位置不一样new的在堆里面

所以这里是新存储了两条地址,里面有new操作

hash链表特性:相同值,相同地址认定为同一个数据,相同数据会

讲解:

这里说一下,intern操作是把当前对象(数据)放入串池(结果是true)

String a = new String("b")+new String("a");
String b= a.intern();
System.out.println(a==b);

这里讲解一下如果intern放入串池的内容存在不会再放入,返回原值

所以这里a是false b是true

String c = "ba";
String a = new String("b")+new String("a");
String b= a.intern();


System.out.println(a==c);
System.out.println(b==c);

1.8是直接将对象放入串池

 主要是看水先入池  
 String a = new String("b")+new String("a");
a.intern();
 String c = "ba";
 System.out.println(a==c);

1.6是复制一份放入串池

这里是1.6测试:

String Tabele位置

确认位置通过报错这里先看1.6:

1.8的

关掉垃圾回收机制后得出报错是heap溢出

String Table 调优:

添加的垃圾回收的信息:会打印内存占用,如果有垃圾会收会打印GC

这里是一些类名啊,变量名啊,他们也是以字符串存储的;也属于常量池;SymbolTable

String Table 底层类似于hashtable的实现;是数组加链表的结构,数组的个数称为桶

默认桶个数60013(红黑树结构 )

这里没有引发垃圾回收机制因为100次还不足以引发但是可以看到串池的增长是一个百个

这里存一万个,发现只存了7000多

这里打印了一条由于内存空间分配失败触发了垃圾回收

GC垃圾回收的规则

就是我用了98%的经历来回收垃圾,但是只起到2%的作用,我就开始抛出异常不在处理

StringTable也是回受到垃圾回收机制的管理的, 当内存空间不足时,string table 那些 没有被引用的字符串常量,也会被回收;

演示StringTable的垃圾回收机制:

 

标签:串池,String,基础,线程,内存,JVM,new,栈帧
来源: https://blog.csdn.net/qq_53322735/article/details/121865696