草庐IT

【JVM】JVM01(概述-程序计数器-栈-堆)

温文艾尔 2024-02-21 原文

⭐️写在前面


⭐️属实好久不见了大伙们,👍首先恭喜自己荣获CSDN的JAVA领域博客新星👨‍🎓

在获得荣誉之后这将近20天温文艾尔干嘛去了呢,答案是我在搭建个人博客网站哈哈,👋于是停更了这么长时间,好消息是随着项目的结束,我的博客网站很快将要和大家见面了,从今天开始对jvm内容的更新,不久后会做一篇关于个人博客网站的文章

🎄如果文章对大家有帮助,欢迎点赞👍收藏⭐️评论📝支持,小温在这里谢过大家了,在这里祝福大家新年快乐!🎄

笔记整理于传智播客黑马程序员-jvm小滴课堂,老师讲的细致易懂

文章目录


⭐️1.JVM入门

⭐️1.1 jvm定义

Java Virtual Machine - java程序的运行环境(java二进制字节码的运行环境)

⭐️1.2 jvm优势

  • 一次编写,到处运行的基石

  • 自动内存管理,垃圾回收机制

   java语言出现时机比较早,同一时期它的竞争对手是c和c++,而c和c++是没有垃圾回收功能的,需要程序员自己
   释放内存,如果编码不当,很容易造成内存的泄漏,java虚拟机的垃圾回收功能减少了程序员的负担
  • 数组下标越界检查
  • 多态

⭐️1.3 JVM,JRE,JDK的比较

JVM运行时数据区


常见的JVM

⭐️2.Program Counter Register 程序计数器(寄存器)

程序计数器在jvm中的位置


程序计数器是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器

字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令分支循环跳转异常处理线程恢复等基础功能都需要依赖这个计数器来完成

程序计数器

作用:

记住下一条jvm指令的执行地址

特点:
1.是线程私有的

每个线程都有自己的程序计数器线程切换执行的过程中,为了准确知道指令执行到哪里,需要程序计数器发功


2.不会存在内存溢出(堆栈等都会出现内存溢出)

⭐️3.Java Virtual Machine Stacks(java虚拟机栈)

java虚拟机栈是作用于方法执行的一块java内存区域
栈在jvm中的位置

栈区

  1. 每个线程包含一个栈区,栈中只保存基础数据类型的对象和自定义对象的引用(不是对象),对象都存放在堆区中

  2. 每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问。

  3. 栈分为3个部分:基本类型变量区执行环境上下文操作指令区(存放操作指令).

- 每个线程运行时所需要的内存,称为虚拟机栈
- 
- 每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存,方法内有参数,局部变量,返回地址等
- 都需要占用内存
- 
- 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法

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

当调用第一个方法时,栈就给第一个方法划分栈帧空间,并且把它压入栈内,当方法执行完毕,方法对应的栈帧出栈,释放方法所占用的内存

栈演示

package day01;

/**
 * Description
 * User:
 * Date:
 * Time:
 */
public class StackTest {
    public static void main(String[] args) {
        method1();
    }

    private static void method1(){
        method2(1,2);
    }

    private static int method2(int a,int b){
        int c = a+b;
        return c;
    }
}

首先执行method1,method1对应的栈帧入栈,称为活动栈帧

继续执行,method1调用method2,method2对应的栈帧入栈,成为活动栈帧

method2执行完毕,method2对应的栈帧被释放掉,method1方法对应的栈帧称为活动栈帧

最后method1执行完毕,main方法对应的栈成为活动栈帧,当main方法执行完毕出栈,虚拟机栈空,方法全部结束

⭐️面试题

1.垃圾回收机制是否涉及栈内存?

不涉及,栈内存涉及的是一次一次的方法调用所产生的栈帧内存,而栈帧内存在每一次的方法调用后都会被弹出栈,被自动回收掉,所以不需要垃圾回收来管理栈内存,垃圾回收主要回收堆内存中的无用对象

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

栈内存可以通过虚拟机参数来进行指定 -Xss size

操作系统的栈默认大小
我们物理内存的大小是一定的,如果我们栈内存分的大,则线程数会变少,加入我们一个线程所用1兆内存,物理内存假设有500兆,我们理论上可以有500个线程同时运行,但如果设置每个线程的栈内存为2兆,那么理论上我们只能同时运行250个线程,线程数变少

故栈内存不是越大越好,栈内存大只是能够进行更多次的方法递归调用,而不会增强运行的效率,反而会导致线程数目的变少

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

我们举个例子

    int x = 0;
    for (int i=0;i<5000;i++){
        x++;
    }
    System.out.println(x);

当多个线程同时执行该方法,上面代码会不会造成x值的混乱?

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


可以看到,在每个线程都有自己私有的一个x变量,运行时互不干扰,如果执行上述方法,会同时+5000

但是当我们把x改为静态变量,结果就会不一样,static修饰的变量为两个方法所共享,并不私有

线程执行x++后,每次自增都会将结果写回静态区,如果不加线程保护,就会产生线程安全的问题

我们再看一个案例

package day01;

/**
 * Description
 * User:
 * Date:
 * Time:
 */
public class ThreadDemo {
    public static void main(String[] args) {

    }

    public static void m1(){
        StringBuilder sb = new StringBuilder();
        sb.append(1);
        sb.append(2);
        sb.append(3);
        System.out.println(sb.toString());
    }

    public static void m2(StringBuilder sb){
        sb.append(1);
        sb.append(2);
        sb.append(3);
        System.out.println(sb.toString());
    }

    public static StringBuilder m3(){
        StringBuilder sb = new StringBuilder();
        sb.append(1);
        sb.append(2);
        sb.append(3);
        return sb;
    }
}

m1方法不会出现线程安全问题,因为StringBuilder sb是我们方法内的一个局部变量,是私有的,其他线程不可能同时访问到该对象

m2方法不是线程安全的,因为StringBuilder sb是作为方法参数传入,有可能有其他的线程可以访问到它,它对其他线程是共享的

m3方法也不是线程安全的,StringBuilder sb虽然是方法内的局部变量,但是他作为一个方法的结果返回,便逃离了m3对它的作用范围

因此我们得出结论

- 如果方法内的局部变量没有逃离方法的作用范围,它就是线程安全的

- 如果局部变量引用了对象并逃离了方法的作用范围,他就是非线程安全的,如果是(int ,float,double)等非引用对象,即使逃离了方法的作用范围,也是线程安全的

⭐️4.栈内存溢出

1.栈帧过多导致栈内存溢出,例如方法递归调用没有结束标志

2.栈帧过大导致栈内存溢出

下面我们结合两个具体的案例来看栈溢出的几个场景

public class StackDemo01 {
    private static int count;
    public static void main(String[] args) {
        try {
            method1();
        }catch (Throwable e){
            e.printStackTrace();
            System.out.println(count);
        }
    }

    private static void method1(){
        count++;
        method1();
    }
}

输出结果:

java.lang.StackOverflowError
	。。。
20706

通过设置虚拟机参数来验证


重新运行,查看结果

java.lang.StackOverflowError
	。。。
2719

设置虚拟机参数之后,我们设置栈的总大小变小了,递归2719次便结束了

⭐️本地方法栈

我们平常见到的带有native关键字的方法都是用c/c++来实现,我们java代码通过本地的native方法接口去间接的调用真正的c/c++的实现

例如clone()方法

hashCode()方法

⭐️5.堆

1.堆是java内存区域中一块用来存放对象实例的区域,几乎所有的对象实例都在这里分配内存,此内存区域的唯一
目的就是存放对象实例

2.存储的全部是对象,每个对象都包含一个与之对应的class的信息。(class的目的是得到操作指令)
3.jvm只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身

4.java堆是垃圾收集器管理的主要区域,因此很多时候也被称作GC“堆”

5.java堆可以分成新生代和老年代,新生代可分为To SpaceFrom SpaceEden

特点

  • 它是线程共享的,堆中对象都需要考虑线程安全的问题
  • 有垃圾回收机制

堆内存溢出问题

对象被当作垃圾回收的条件是不再使用它,那么如果不断地产生对象,产生的新对象仍然有人在使用它们,这意味着
这些对象不能作为垃圾被处理,这样的对象达到一定的数量就会产生堆内存溢出问题

下面我们看一个具体的案例

public class Demo01 {
    public static void main(String[] args) {
        int i=0;
        try{
            ArrayList<Object> list = new ArrayList<>();
            String a = "hello";
            while (true){
                list.add(a);
                a=a+a;
                i++;
            }
        }catch (Throwable e){
            e.printStackTrace();
            System.out.println(i);
        }
    }
}

运行结果:

java.lang.OutOfMemoryError: Java heap space
	at java.util.Arrays.copyOf(Arrays.java:3332)
	at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124)
	at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:448)
	at java.lang.StringBuilder.append(StringBuilder.java:136)
	at day01.Demo01.main(Demo01.java:19)


java.lang.OutOfMemoryError: Java heap space

java堆空间不足导致的异常

堆内存诊断

1.jps工具

  • 查看当前系统中有哪些java进程

2.jmap工具

  • 查看堆内存占用情况

3.jconsole工具

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

有关【JVM】JVM01(概述-程序计数器-栈-堆)的更多相关文章

  1. ruby - 在 Ruby 程序执行时阻止 Windows 7 PC 进入休眠状态 - 2

    我需要在客户计算机上运行Ruby应用程序。通常需要几天才能完成(复制大备份文件)。问题是如果启用sleep,它会中断应用程序。否则,计算机将持续运行数周,直到我下次访问为止。有什么方法可以防止执行期间休眠并让Windows在执行后休眠吗?欢迎任何疯狂的想法;-) 最佳答案 Here建议使用SetThreadExecutionStateWinAPI函数,使应用程序能够通知系统它正在使用中,从而防止系统在应用程序运行时进入休眠状态或关闭显示。像这样的东西:require'Win32API'ES_AWAYMODE_REQUIRED=0x0

  2. ruby - 如何指定 Rack 处理程序 - 2

    Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack

  3. ruby - 在 Ruby 中编写命令行实用程序 - 2

    我想用ruby​​编写一个小的命令行实用程序并将其作为gem分发。我知道安装后,Guard、Sass和Thor等某些gem可以从命令行自行运行。为了让gem像二进制文件一样可用,我需要在我的gemspec中指定什么。 最佳答案 Gem::Specification.newdo|s|...s.executable='name_of_executable'...endhttp://docs.rubygems.org/read/chapter/20 关于ruby-在Ruby中编写命令行实用程序

  4. ruby-on-rails - Rails 应用程序之间的通信 - 2

    我构建了两个需要相互通信和发送文件的Rails应用程序。例如,一个Rails应用程序会发送请求以查看其他应用程序数据库中的表。然后另一个应用程序将呈现该表的json并将其发回。我还希望一个应用程序将存储在其公共(public)目录中的文本文件发送到另一个应用程序的公共(public)目录。我从来没有做过这样的事情,所以我什至不知道从哪里开始。任何帮助,将不胜感激。谢谢! 最佳答案 无论Rails是什么,几乎所有Web应用程序都有您的要求,大多数现代Web应用程序都需要相互通信。但是有一个小小的理解需要你坚持下去,网站不应直接访问彼此

  5. ruby - 无法运行 Rails 2.x 应用程序 - 2

    我尝试运行2.x应用程序。我使用rvm并为此应用程序设置其他版本的ruby​​:$rvmuseree-1.8.7-head我尝试运行服务器,然后出现很多错误:$script/serverNOTE:Gem.source_indexisdeprecated,useSpecification.Itwillberemovedonorafter2011-11-01.Gem.source_indexcalledfrom/Users/serg/rails_projects_terminal/work_proj/spohelp/config/../vendor/rails/railties/lib/r

  6. ruby-on-rails - Rails 应用程序中的 Rails : How are you using application_controller. rb 是新手吗? - 2

    刚入门rails,开始慢慢理解。有人可以解释或给我一些关于在application_controller中编码的好处或时间和原因的想法吗?有哪些用例。您如何为Rails应用程序使用应用程序Controller?我不想在那里放太多代码,因为据我了解,每个请求都会调用此Controller。这是真的? 最佳答案 ApplicationController实际上是您应用程序中的每个其他Controller都将从中继承的类(尽管这不是强制性的)。我同意不要用太多代码弄乱它并保持干净整洁的态度,尽管在某些情况下ApplicationContr

  7. ruby-on-rails - 如何在我的 Rails 应用程序 View 中打印 ruby​​ 变量的内容? - 2

    我是一个Rails初学者,但我想从我的RailsView(html.haml文件)中查看Ruby变量的内容。我试图在ruby​​中打印出变量(认为它会在终端中出现),但没有得到任何结果。有什么建议吗?我知道Rails调试器,但更喜欢使用inspect来打印我的变量。 最佳答案 您可以在View中使用puts方法将信息输出到服务器控制台。您应该能够在View中的任何位置使用Haml执行以下操作:-puts@my_variable.inspect 关于ruby-on-rails-如何在我的R

  8. ruby-on-rails - Ruby on Rails 计数器缓存错误 - 2

    尝试在我的RoR应用程序中实现计数器缓存列时出现错误Unknownkey(s):counter_cache。我在这个问题中实现了模型关联:Modelassociationquestion这是我的迁移:classAddVideoVotesCountToVideos0Video.reset_column_informationVideo.find(:all).eachdo|p|p.update_attributes:videos_votes_count,p.video_votes.lengthendenddefself.downremove_column:videos,:video_vot

  9. ruby - 检查是否通过 require 执行或导入了 Ruby 程序 - 2

    如何检查Ruby文件是否是通过“require”或“load”导入的,而不是简单地从命令行执行的?例如:foo.rb的内容:puts"Hello"bar.rb的内容require'foo'输出:$./foo.rbHello$./bar.rbHello基本上,我想调用bar.rb以不执行puts调用。 最佳答案 将foo.rb改为:if__FILE__==$0puts"Hello"end检查__FILE__-当前ruby​​文件的名称-与$0-正在运行的脚本的名称。 关于ruby-检查是否

  10. ruby-on-rails - 如何在 Gem 中获取 Rails 应用程序的根目录 - 2

    是否可以在应用程序中包含的gem代码中知道应用程序的Rails文件系统根目录?这是gem来源的示例:moduleMyGemdefself.included(base)putsRails.root#returnnilendendActionController::Base.send:include,MyGem谢谢,抱歉我的英语不好 最佳答案 我发现解决类似问题的解决方案是使用railtie初始化程序包含我的模块。所以,在你的/lib/mygem/railtie.rbmoduleMyGemclassRailtie使用此代码,您的模块将在

随机推荐