精华内容
下载资源
问答
  • JSP九内置对象和四种属性范围解读

    万次阅读 多人点赞 2015-07-14 18:27:52
    本文首先主要讲解了JSP中四种属性范围的概念、用法与实例。然后在这个基础之上又引入了九内置对象,并对这几内置对象一个一个的进行分析的解读。内容很详细,例子都附有代码和运行的结果截图。

    林炳文Evankaka原创作品。转载请注明出处http://blog.csdn.net/evankaka  

         摘要:本文首先主要讲解了JSP中四种属性范围的概念、用法与实例。然后在这个基础之上又引入了九大内置对象,并对这几大内置对象一个一个的进行分析的解读。内容很详细,例子都附有代码和运行的结果截图。

    本文工程下载

    一、四种属性范围

    1.1、在JSP中提供了四种属性保存范围

    page:在一个页面内保存属性,跳转之后无效
    request:在一次服务请求范围内,服务器跳转后依然有效
    session:-在一次会话范围内,无论何种跳转都可以使用,但是新开浏览器无法使用
    application:在整个服务器上保存,所有用户都可以使用

    1.2、4种属性范围都支持的操作

    public void setAttribute(String name,Object value)
    public Object getAttribute(String name)
    public Object removeAttribute(String name)

    下面,我们来对四种范围来分别进行详细的介绍

    1.3、page范围

    在JSP中设置一个页的属性范围,必须通过pageContext完成,PageContext属性范围是最重要的JSP属性之一,但是如果使用纯粹的JSP代码开发,此属性显示不出用处,其发挥作用在 Struts ,WebWork 中

    如下:

    <%@page import="java.util.*"%>
    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Insert title here</title>
    </head>
    <body>
    <%
    //设置page属性范围,此属性只在当前JSP页面内起作用
    pageContext.setAttribute("name", "linbingwen");
    pageContext.setAttribute("time", new Date());
    %>
    姓名:${pageScope.name}<br>
    时间:${pageScope.time}<br>
    </body>
    </html>
    ${pageScope.name}这里用了EL表达式来取得值,输出结果如下,


    这里要注意发果在其它页面使用:

    如下:

    <%@page import="java.util.*"%>
    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Insert title here</title>
    </head>
    <body>
    姓名:${pageScope.name}<br>
    时间:${pageScope.time}<br>
    </body>
    </html>
    输出结果:


    这说明page范围的值只能在本页使用!

    1.4、request属性范围

    request将属性保存在一次请求范围之内:

    前提:必须使用服务器端跳转:<jsp:forward/> 应用点:MVC设计模式、Struts、 Webwork

    应用实例

    首先是设置request:

    request.jsp

    <%@page import="java.util.*"%>
    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Insert title here</title>
    </head>
    <body>
    <%
    //设置request属性范围
    request.setAttribute("name", "linbingwen");
    request.setAttribute("time", new Date());
    %>
     <jsp:forward page="requestResult.jsp"/> 
    </body>
    </html>

    然后是显示requestResult.jsp

    <%@page import="java.util.*"%>
    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Insert title here</title>
    </head>
    <body>
    获取request姓名:${requestScope.name}<br>
    获取request时间:${requestScope.time}<br>
    </body>
    </html>

    访问结果:


    这时比如requestResult1.jsp也想来访问这两个属性

    内容和requestResult.jsp一样:

    <%@page import="java.util.*"%>
    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Insert title here</title>
    </head>
    <body>
    获取request姓名:${requestScope.name}<br>
    获取request时间:${requestScope.time}<br>
    </body>
    </html>

    结果如下,说明request只针对服务器跳转有效,在两次跳转之间保存。



    1.5、session属性范围

    session:只要设置上去,则不管是什么跳转,都可以取得属性,主要用于验证用户是否登陆。EMAIL--->用户需要先进行登陆,登陆成功后再编辑邮件。与session有关的任何打开的页面都可以取得session

    比如session.jsp设置如下:

    <%@page import="java.util.*"%>
    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Insert title here</title>
    </head>
    <body>
    <%
    //设置session属性范围
    session.setAttribute("name", "linbingwen");
    session.setAttribute("time", new Date());
    %>
    <a href="sessionResult.jsp">获取session内容</a>
    </body>
    </html>
    然后是取出sesson的值sessionResult.jsp

    <%@page import="java.util.*"%>
    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Insert title here</title>
    </head>
    <body>
    获取session姓名:${sessionScope.name}<br>
    获取session时间:${sessionScope.time}<br>
    </body>
    </html>
    输出结果:


    如果还有一个sessionResult1.jsp和sessionResult.jsp一样如下

    <%@page import="java.util.*"%>
    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Insert title here</title>
    </head>
    <body>
    <%
    //设置request属性范围
    session.setAttribute("name", "linbingwen");
    session.setAttribute("time", new Date());
    %>
    <a href="sessionResult.jsp">获取session内容</a>
    </body>
    </html>
    
    然后是取出sesson的值sessionResult.jsp
    
    <%@page import="java.util.*"%>
    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Insert title here</title>
    </head>
    <body>
    获取session姓名:${sessionScope.name}<br>
    获取session时间:${sessionScope.time}<br>
    </body>
    </html>

    注意看上面的GIF动画和这里的获取到的时间是一样的,这也说明了这两个jsp页面取得了同一个值

    1.6、application属性范围

    只要设置一次,则所有的页面窗口都可以取得数据。这里的值将会保存在服务器上,所以每一个用户都可以看见。

    如下面application.jsp:

    <%@page import="java.util.*"%>
    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Insert title here</title>
    </head>
    <body>
    <%
    //设置request属性范围
    application.setAttribute("name", "linbingwen");
    application.setAttribute("time", new Date());
    %>
    <a href="applicationResult.jsp">获取application内容</a>
    </body>
    </html>

    然后是applicationResult.jsp

    <%@page import="java.util.*"%>
    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Insert title here</title>
    </head>
    <body>
    获取application姓名:${applicationScope.name}<br>
    获取application时间:${applicationScope.time}<br>
    </body>
    </html>

    输出结果:


    如果这时在新建一个网页或新建一个新的web项目,但是它们两个要运行在同一个Tomcat中,那么它同样也能访问到这个值。

    注意:

    pplication、session、request--==--》都与要跨多个页,属性保存是有内存开销的,设置过多的application或每一个session保存过多的对象,性能就降低了。

    原则:能用request就不要用session,能用session就不要用application

    application应用:在线人员统计、在线人员名单列表,要释放application资源,只能重新启动服务器.

    使用application缓存数据库的连接,每次使用时,从缓冲中取出,用完就返回。

    二、JSP九大内置对象详解

    内置对象(又叫隐含对象,有9个内置对象):不需要预先声明就可以在脚本代码和表达式中随意使用

    JSP内置对象映射表


    request              请求对象                 类型 javax.servlet.ServletRequest        作用域 Request
    response           响应对象                   类型 javax.servlet.SrvletResponse       作用域  Page
    pageContext      页面上下文对象         类型 javax.servlet.jsp.PageContext      作用域    Page
    session             会话对象                   类型 javax.servlet.http.HttpSession       作用域    Session
    application        应用程序对象            类型 javax.servlet.ServletContext          作用域    Application
    out                    输出对象                   类型 javax.servlet.jsp.JspWriter             作用域    Page
    config               配置对象                   类型 javax.servlet.ServletConfig            作用域    Page
    page                页面对象                   类型 javax.lang.Object                            作用域    Page
    exception         例外对象                   类型 javax.lang.Throwable                     作用域    page

    JSP中一共预先定义了9个这样的对象,分别为:request、response、session、application、out、pagecontext、config、page、exception

    2.1、request对象

    request 对象是 javax.servlet.httpServletRequest类型的对象。 该对象代表了客户端的请求信息,主要用于接受通过HTTP协议传送到服务器的数据。(包括头信息、系统信息、请求方式以及请求参数等)。request对象的作用域为一次请求。

    实现常有的方法

    1、获取数据

    getParameter;;接收请求参数的,

    2、对所有数据进行再编码

    public byte[] getBytes(“encoding”)
    如下实例将byte数组编码转换

    <%@ page contentType="text/html";charset=gbk"%>
    <html>
           <body>
                 <%
                         //接收内容
                         String name=request.getParameter("uname");
                         byte[] b=name.getBytes("ISO8859-1");
                         name=new String(b);
                         String name= new String(request.getParameter("uname").getBytes("ISO8859-1"));
                  %>
                  <h1>输入内容为:<%=name%></h1>
           </body>
    </html>

    3、设置统一的请求编码

    public void setCharacterEncoding(String env) throws UnsunpportedEncodingException
    如下设置

    <%@ page contentType="text/html";charset=gbk"%>
    <html>
           <body>
                  <%
                         //接收内容
                         request.setCharacterEncoding("GBK");
                         String name= request.getParameter("uname");
                  %>
                  <h1>输入内容为:<%=name%></h1>
           </body>
    </html>

    4、获取requst信息:

    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Insert title here</title>
    </head>
    <body bgcolor="#FFFFF0">
    <form action="" method="post">
       <input type="text" name="name">
       <input type="submit" value="提交">
    </form>
    请求方式:<%=request.getMethod()%><br>
    请求的资源:<%=request.getRequestURI()%><br>
    请求用的协议:<%=request.getProtocol()%><br>
    请求的文件名:<%=request.getServletPath()%><br>
    请求的服务器的IP:<%=request.getServerName()%><br>
    请求服务器的端口:<%=request.getServerPort()%><br>
    客户端IP地址:<%=request.getRemoteAddr()%><br>
    客户端主机名:<%=request.getRemoteHost()%><br>
    表单提交来的值:<%=request.getParameter("name")%><br>
    </body>
    </html>

    我们第一次访问是默认用GET方法,表单提交后使用POST方式。



    2.2、response对象

            response 代表的是对客户端的响应,主要是将JSP容器处理过的对象传回到客户端。response对象也具有作用域,它只在JSP页面内有效。response对象的主要使用1.设置HTTP头信息、重定向、设置COOKie
    (1).Web服务器收到一个http请求,会针对每个请求创建一个HttpServletRequest和HttpServletResponse对象,向客户端发送数据找HttpServletResponse,从客户端取数据找HttpServletRequest;

    (2).HttpServletResponse对象可以向客户端发送三种类型的数据:a.响应头b.状态码c.数据

    2.2.1、response对象所提供的方法。
    (1)设定表头的方法

    void addCookie(Cookie cookie) 新增cookie
    void addDateHeader(String name, long date) 新增long类型的值到name标头
    void addHeader(String name, String value) 新增String类型的值到name标头
    void addIntHeader(String name, int value) 新增int类型的值到name标头
    void setDateHeader(String name, long date) 指定long类型的值到name标头
    void setHeader(String name, String value) 指定String类型的值到name标头
    void setIntHeader(String name, int value) 指定int类型的值到name标头
    containsHeader( String name )判断指定名字的HTTP文件头是否已经存在,然后返回真假布尔值
    (2)设定响应状态码的方法
    void sendError(int sc) 传送状态码(status code)
    void sendError(int sc, String msg) 传送状态码和错误信息
    void setStatus(int sc) 设定状态码
    (3)用来URL 重写(rewriting)的方法
    String encodeRedirectURL(String url) 对使用sendRedirect( )方法的URL予以编码
    (4)设置重定向
    sendRedirect():设置重定向页面.
    2.2.2、使用范例
    (1)使用response对象可以设置HTTP的头信息。格式response.setHeader(“头信息名称”,”参数”),其中一个重要的头信息:refresh(刷新)。例如,每秒刷新一次也没,显示刷新次数:

    <%@page import="java.util.*"%>
    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Insert title here</title>
    </head>
    <body>
     <%!int i=0; %>  
     <%  
      //每秒刷新一次   
       response.setHeader("refresh","1");  
     %>  
    <%=i++ %>  
    </body>
    </html>
    输出结果如下:




    (2)重定向

    <%@page import="java.util.*"%>
    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Insert title here</title>
    </head>
    <body>
    <%  
          response.setHeader("refresh","3;URL=index.jsp") ;  
          %>  
          三秒后跳转!!!<br>  
          如果没有跳转,请按<a href="index.jsp">这里</a>!!!  
    </body>
    </html>
    来输出结果看看:

    输入http://localhost:8080/JspLearning/responseObject1.jsp,三秒后自动跳转到http://localhost:8080/JspLearning/index.jsp




    当然还可以使用response.sendRedirect("**.jsp");命令

    如下:

    <%@page import="java.util.*"%>
    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Insert title here</title>
    </head>
    <body>
    <%  
    response.sendRedirect("index.jsp");
    %> 
    </body>
    </html>
    输出结果,直接重定向返回到首页了,


    (3)设置cookie

    <%@pagecontentType="text/html;charset=gb2312"%>
    <HTML>
    <HEAD>
    <TITLE>Cookie的使用</TITLE>
    </HEAD>
    <BODY>
    <%
    Cookie c1 = newCookie("name","aaa") ;
    Cookie c2 = newCookie("password","111") ;
    // 最大保存时间为60秒
    c1.setMaxAge(60) ;
    c2.setMaxAge(60) ;
    // 通过response对象将Cookie设置到客户端
    response.addCookie(c1) ;
    response.addCookie(c2) ;
    %>
    </BODY>
    </HTML> 

    (4)读取cookie
    <%@page contentType="text/html;charset=gb2312"%>
    <HTML>
    <HEAD>
    <TITLE>Cookie的使用</TITLE>
    </HEAD>
    <BODY>
    <%
    // 通过request对象,取得客户端设置的全部Cookie
    // 实际上客户端的Cookie是通过HTTP头信息发送到服务器端上的
    Cookie c[] = request.getCookies() ;
    %>
    <%
    for(int i=0;i<c.length;i++){
    Cookie temp = c[i] ;
    %>
    <h1><%=temp.getName()%> --> <%=temp.getValue()%></h1>
    <%
    }
    %>
    </BODY>
    </HTML>

    2.3、session对象

    session 对象是由服务器自动创建的与用户请求相关的对象。服务器为每个用户都生成一个session对象,用于保存该用户的信息,跟踪用户的操作状态。session对象内部使用Map类来保存数据,因此保存数据的格式为 “Key/value”。 session对象的value可以使复杂的对象类型,而不仅仅局限于字符串类型。

    (1)session(会话)对象是类javax.servlet.Httpsession的一个对象。session是从客户端连接服务器开始,到与服务器断开为止。
    (2)session对象用于保存每个与服务器建立连接的客户端的信息,session的ID保存于客户端的Cookie中,这个session ID标识唯一和用户,与其他用户的session ID不同。
    (3)session对象的ID:
    当一个客户端访问服务器的一个JSP页面时,JSP引擎产生一个session对象,同时分配一个String类型的ID号,并发给客户端。客户端将其存储于Cookie.a其标志了一个唯一的ID;采用getID()方法返回session对象在服务器端的编号。服务器端通过此ID,唯一地识别一个用户,并提供特殊的服务。
    (4)session对象的有效期:
    存在以下几个情况时,session对象和其存储的数据会消失,情况有:
    当用户关闭当前正在使用的浏览器时;
    关闭网页服务器。
    用户未向服务器提出请求超预设时,Tomcat服务器预设为30分钟;
    运行程序结束session.
    出现以上四种情况时,session对象会消失。

    以下是一个简单的session登陆实例:

    login.jsp

    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Insert title here</title>
    </head>
    <body>
    <form action="login.jsp" method="post">  
        用户名:<input type="text" name="uname"><br>  
        密  码:<input type="password" name="upass"><br>  
        <input type="submit" value="登录">  
        <input type="reset" value="重置">  
    </form>  
    <%  
        String name = (String)request.getParameter("uname");  
        String password = (String)request.getParameter("upass");  
        if(!(name == null || "".equals(name) || password == null || "".equals(password))){  
            if("linlin".equals(name) && "123456".equals(password)){  
                //如果登录成功,则设置session的属性范围  
                session.setAttribute("userid", name);  
                response.setHeader("refresh","3;URL= welcome.jsp");   
    %>          
                <h3>用户登录成功!三秒后跳转到欢迎页……</h3>  
                <h3>如果没有跳转,请按<a href="welcome.jsp">这里</a></h3>  
    <%  
            }else{  
    %>  
                <h3>错误的用户名或密码!</h3>  
    <%  
            }   
        }  
    %>  
    </body>
    </html>

    welcome.jsp

    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Insert title here</title>
    </head>
    <body>
    <%   //如果已经设置过了session属性,则肯定不为空  
        if(session.getAttribute("userid") != null){  
    %>  
            <h3>欢迎<%=session.getAttribute("userid")%>登陆~~~~~~~~~~<a href="logout.jsp">注销</a></h3>  
    <%  
        }else{  
    %>  
            <h3>请先进行本系统的<a href="login.jsp">登录</a></h3>  
    <%  
        }  
    %>  
    </body>
    </html>

    logout.jsp

    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Insert title here</title>
    </head>
    <body>
    <%  
        response.setHeader("refresh", "2;URL=login.jsp");  
        session.invalidate();   //注销表示当前的session失效  
    %>  
    <h3>你已成功退出本系统,两秒后返回到首页!</h3>  
    <h3>如果没有跳转,请按<a href="login.jsp">这里</a></h3>  首页
    </body>
    </html>

    结果如下:输入正确用户名和密码后,等三秒,自动跳转到欢迎界面welcome.jsp。欢迎界面里点注销,重定向的注销界面login.jsp,等2秒后自动跳转到login.jsp



    2.4、application对象

    application 对象可将信息保存在服务器中,直到服务器关闭,否则application对象中保存的信息会在整个应用中都有效。与session对象相比,application对象生命周期更长,类似于系统的“全局变量”。

    运用实例:网页访问计数器。

    <%@page import="java.util.*"%>
    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Insert title here</title>
    </head>
    <body>
      <body>
        <%
            if(application.getAttribute("counter") == null)
            {
                application.setAttribute("counter", "1");
            }
            else
            {
                String strnum = null;
                strnum = application.getAttribute("counter").toString();
                int icount = 0;
                icount = Integer.valueOf(strnum).intValue();
                icount++;
                application.setAttribute("counter", Integer.toString(icount));
                
            }
                
        %>
        
            您是第<%=application.getAttribute("counter") %>位访问者!
    </body>
    </html>

    输出结果:


    运行结果就是访问到该页面之后显示你是第几位访客,刷新之后数目会增加,更换浏览器或者更换客户端地址都会使其访问值正常递增。
        application的存活范围比request和session都要大。只要服务器没有关闭,application对象中的数据就会一直存在,在整个服务器的运行过程当中,application对象只有一个,它会被所有的用户共享。其中getRealPath这个方法可以获取资源在服务器上的物理路径(绝对路径),常用来获取上传文件时要存储文件的路径。

    2.5、out 对象

    out 对象用于在Web浏览器内输出信息,并且管理应用服务器上的输出缓冲区。在使用 out 对象输出数据时,可以对数据缓冲区进行操作,及时清除缓冲区中的残余数据,为其他的输出让出缓冲空间。待数据输出完毕后,要及时关闭输出流。

    out对象的方法

    clear()清除网页上输出的内容
    clearBuffer()清除缓冲去的内容
    close()关闭缓冲区,清除所有的内容
    getBufferSize()取得缓冲区的大小
    getRemaining()取得缓冲区剩余大小
    isAutoFlush()获取缓冲是否进行自动清除的信息
    print()进行页面输出
    println()进行页面输出并换行

    使用out对象进行页面的输出

    <%@ page language="java" contentType="text/html;charset=gb2312" %>
    <!DOCTYPE html>
    <html>
        <head>
            <title>使用out对象输出内容</title>
        </head>
        <body>
            <%
                out.print("hello world");
                out.println("hello world");
            %>
        </body>
    </html>
    这里在页面上输出是没有什么区别的,如果想换行要使用html元素<br />
    使用out对象获得缓冲区使用大小
    <%@ page language="java" contentType="text/html;charset=gb2312" %>
    <!DOCTYPE html>
    <html>
        <head>
            <title>使用out对象求的缓冲区使用的大小</title>
        </head>
        <body>
            <%
                int all = out.getBufferSize();
                int remain = out.getRemaining();
                int use = all - remain;
                out.println("总的缓冲区大小为:"+all+"<br />");
                out.println("正在使用的缓冲区的大小为:"+remain+"<br />");
                out.println("剩余的缓冲区的大小为:"+use+"<br />");
                 
            %>
        </body>
    </html>

    输出结果


    2.6、pageContext 对象

    pageContext 对象的作用是取得任何范围的参数,通过它可以获取 JSP页面的out、request、reponse、session、application 等对象。pageContext对象的创建和初始化都是由容器来完成的,在JSP页面中可以直接使用 pageContext对象。

    这个对象代表页面上下文,该对象主要用于访问JSP之间的共享数据。

    pageContext是PageContext类的实例,使用pageContext可以访问page、request、session、application范围的变量。
    
    getAttribute(String name):取得page范围内的name属性。
    
    getAttribute(String name,int scope):取得指定范围内的name属性,其中scope可以是如下4个值:
    
    PageContext.PAGE_SCOPE:对应于page范围。
    
    PageContext.REQUEST_SCOPE:对应于request范围。
    
    PageContext.SESSION_SCOPE:对应于session范围。
    
    PageContext.APPLICATION_SCOPE:对应于application范围。

    实例:

    <%@page import="java.util.*"%>
    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Insert title here</title>
    </head>
    <body>
    <%
    
    //使用pageContext设置属性,该属性默认在page范围内
    pageContext.setAttribute("name","lin-pageContext");
    request.setAttribute("name","lin-request");
    session.setAttribute("name","lin-session");
    application.setAttribute("name","lin-application");
    
    %>
    page设定的值:<%=pageContext.getAttribute("name")%><br>
    request设定的值:<%=pageContext.getRequest().getAttribute("name")%><br>
    session设定的值:<%=pageContext.getSession().getAttribute("name")%><br>
    application设定的值:<%=pageContext.getServletContext().getAttribute("name")%><br>
    范围1内的值:<%=pageContext.getAttribute("name",1)%><br>
    范围2内的值:<%=pageContext.getAttribute("name",2)%><br>
    范围3内的值:<%=pageContext.getAttribute("name",3)%><br>
    范围4内的值:<%=pageContext.getAttribute("name",4)%><br>
    <!--从最小的范围page开始,然后是reques、session以及application-->
    <%pageContext.removeAttribute("name",3);%>
    pageContext修改后的session设定的值:<%=session.getValue("name")%><br>
    <%pageContext.setAttribute("name","lin-modify",4);%>
    pageContext修改后的application设定的值:<%=pageContext.getServletContext().getAttribute("name")%><br>
    值的查找:<%=pageContext.findAttribute("name")%><br>
    属性name的范围:<%=pageContext.getAttributesScope("name")%><br> 
    </body>
    </html>

    输出结果:


    2.7、config 对象

    config 对象的主要作用是取得服务器的配置信息。通过 pageConext对象的 getServletConfig() 方法可以获取一个config对象。当一个Servlet 初始化时,容器把某些信息通过 config对象传递给这个 Servlet。 开发者可以在web.xml 文件中为应用程序环境中的Servlet程序和JSP页面提供初始化参数。

    config 对象代表当前JSP 配置信息,但JSP 页面通常无须配置,因此也就不存在配置信息。该对象在JSP 页面中非常少用,但在Servlet 则用处相对较大。因为Servlet 需要配置在web.xml 文件中,可以指定配置参数。

    <%@page import="java.util.*"%>
    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Insert title here</title>
    </head>
    <body>
    <!-- 直接输出config的getServletName的值 -->  
    <%=config.getServletName()%>  
    <!-- 输出该JSP中名为name的参数配置信息 -->  
    name配置参数的值:<%=config.getInitParameter("name")%><br/>  
    <!-- 输出该JSP中名为age的参数配置信息 -->  
    age配置参数的值:<%=config.getInitParameter("age")%>  
    </body>
    </html>
    web.xml配置里加上:

        <servlet>    
                <!--指定servlet的名字-->  
                <servlet-name>config</servlet-name>    
                <!--指定哪一个JSP页面配置成Servlet-->  
                <jsp-file>/configObject.jsp</jsp-file>  
                <!--配置名为name的参数,值为linbingwen-->  
                <init-param>    
                    <param-name>name</param-name>    
                    <param-value>linbingwen</param-value>    
                </init-param>    
                <!--配置名为age的参数,值为30-->  
                <init-param>    
                    <param-name>age</param-name>    
                    <param-value>100</param-value>    
                </init-param>    
            </servlet>    
        <servlet-mapping>   
                <!--指定将config Servlet配置到/config路径-->   
                <servlet-name>config</servlet-name>    
                <url-pattern>/config</url-pattern>    
        </servlet-mapping> 
    输出结果:


    2.8、page 对象

    page 对象代表JSP本身,只有在JSP页面内才是合法的。 page隐含对象本质上包含当前 Servlet接口引用的变量,类似于Java编程中的 this 指针。

    1 什么是page对象 ?
    (1) page对象代表JSP页面本身
    page对象是当前JSP页面本身的一个实例,page对象在当前JSP页面中可以用this关键字来替代。

    (2) 在JSP页面哪些地方可以使用page对象
    在JSP页面的Java程序片中可以使用page对象
    在JSP页面的JSP表达式中可以使用page对象

    (3) page对象的基类是:java.lang.Object类。
    注意:如果直接通过page对象来调用方法,就只能调用Object类中的那些方法。

    (4) javax,servlet.jsp.JspPage接口
    JspPage接口继承于javax.servlet.Servlet接口。

    我们可以使用JspPage接口对page对象进行强制类型转换,再调用JspPage接口中的各种方法。

    (5) javax,servlet.jsp.HttpJspPage接口
    HttpJspPage接口继承于:
    javax.servlet.jsp.JspPage接口和javax.servlet.Servlet接口。

    我们可以使用HttpJspPage接口对page对象进行强制类型转换,再调用HttpJspPage接口中的各种方法。

    (6) 在JSP页面中使用this关键字,可调用哪些方法?
    在JSP页面中,this关键字表示当前JSP页面这个对象,可以调用的常见方法,如下所示:


    如下实例:

    <%@page import="java.util.*"%>
    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Insert title here</title>
    </head>
    <body>
    取page的值:<%=this.getServletInfo() %>
    </body>
    </html>
    输出结果:


    2.9、exception 对象

          Exception对象是用来处理Jsp页面文件在执行时所有发生的错误和异常;Jsp页面文件必须在isErrorPage=true的情况下才可以使用该对象;该对象一般配合Page指令一起使用,通过指定某个页面为错误处理页面,把所有的错误都集中到那个页面进行处理,可以使整个系统的性能得到加强;常用方法如下
    getMessage():返回错误信息
    toString:以字符串的形式返回一个对异常的描述
    printStackTrace():以标准错误的形式输出一个错误和错误的堆栈

    (1) 可能出错的页面:
    在有可能产生异常或错误的JSP页面中,使用page指令设置errorPage属性,属性值为能够进行异常处理的某个JSP页面。
    简单来说,只要在当前JSP页面中产生了异常,就交给另外一个专门处理异常的JSP页面。

    (2) 专门处理错误的页面:
    在专门负责处理异常的JSP页面中,使用page指令设置isErrorPage属性为true,并使用exception对象来获取出错信息。

    error.jsp

    <%@ page language="java" contentType="text/html;charset=UTF-8" import="java.util.*" isErrorPage="true"%>
    <%@ page import="java.io.PrintStream" %>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Insert title here</title>
    </head>
    <body>
    <%=exception %><br />
        <%=exception.getMessage() %><br />
        <%=exception.getLocalizedMessage() %><br />
         
        <%
            exception.printStackTrace(new java.io.PrintWriter(out));
        %>
    </body>
    </html>
    出错的页面

    <%@page import="java.util.*"%>
    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8" errorPage="error.jsp"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Insert title here</title>
    </head>
    <body>
    <%
                int[] arr = {1,2,4};
                out.println(arr[3]);
            %>
    </body>
    </html>
    输出的结果:


    不加errorPage="error.jsp"这句的话:代码就会暴出来了


    本文工程下载
    展开全文
  • 没有继承的对象属性排布 有继承的对象属性排布 如何计算对象大小 创建一个含有premain()方法的Java 类。 将创建好的Java类打成一个jar包 修改JVM启动配置 测试样例 参考书籍:《Java特种兵(上册)》 对象...

    目录

    对象内存结构

    没有继承的对象属性排布

    有继承的对象属性排布

    如何计算对象大小

    创建一个含有premain()方法的Java 类。

    将创建好的Java类打成一个jar包

    修改JVM启动配置

    测试样例


     参考书籍:《Java特种兵(上册)》 

    对象内存结构

    Class文件以字节码的形式存储在方法区当中,用来描述一个类本身的内存结构。当使用Class文件新建对象时,对象实例的内存结构又究竟是个什么样子呢? 

    如图所示,为了表示对象的属性、方法等信息,HotSpot VM使用对象头部的一个指针指向Class区域的方式来找到对象的Class描述,以及内部的方法、属性入口。除此之外,还在对象的头部划分了部分空间(Mark Word),用于描述与对象相关的其他信息,例如:是否加锁、GC标志位、Minor GC次数、对象默认的hashCode(System.identityHashCode(object)可获取对象的这个值)。

    在32位系统下,存放Class指针的空间大小是4字节,Mark Word空间大小也是4字节,因此就是8字节的头部,如果是数组还需要增加4字节来表示数组的长度。

    在64位系统及64位JVM下,开启指针压缩(参数是 -XX:+UseCompressedOops),那么头部存放Class指针的空间大小还是4字节,而Mark Word区域会变大,变成8字节,也就是头部最少为12字节。

    若未开启指针压缩,那么保存Class指针的空间大小也会变成8字节,那么对象头部会变成16字节。另外,在64位模式下,若未开启压缩,引用也会变成8字节。

    此外,Java对象将以8字节对齐在内存中,也就是对象占用的空间不是8字节的倍数,将会被补齐为8字节的倍数,这样做的好处是,在对象分配和查找的过程中不用考虑过多的偏移量问题。

    以下是在32位系统下一些常见对象占用的空间大小示例。 

    没有继承的对象属性排布

    在默认情况下,HotSpot VM会按照一个顺序排布对象的内部属性,这个顺序是,long/double-->int/float-->short/char-->byte/boolean-->Reference(与对象本身的属性顺序无关)。

    有继承的对象属性排布

    在HotSpot VM中,有继承关系的对象在创建时,父类的属性会被分配到相应的对象中,由于父类的属性不能和子类混用,所以它们必须单独排布在一个地方,可以认为它们就是从上到下的一个顺序。以两重继承为例,对象继承属性排布规则如下图所示。 

    这里的对齐有两种:一是整个对象的8字节对齐;二是父类到子类的属性对齐。在32位及64位压缩模式下,会按照4字节对齐。

    例如下面的例子: 

    class A {byte b;}
    class B extends A {byte b;}
    class C extends B {byte b;}

    如何计算对象大小

    有时,我们需要知道Java对象到底占用多少内存,有人通过连续调用两次System.gc()比较两次gc前后内存的使用量在计算java对象的大小,也有人根据Java虚拟机规范中的Java对象内存排列估算对象的大小,这两种方法或多或少都有问题,因为System.gc()并不一定促发GC,同一个类型的对象在32位与64位JVM中使用的内存会不一样,在64位虚拟机中是否开启指针压缩也会影响Java对象在内存中的大小。

    那么有没有一种既准确又方便的方法计算对象的大小呢?答案是肯定的。在Java 5中引入了Instrumentation类,这个类提供了计算对象内存占用量的方法;Hotspot支持instrumentation框架,其他的虚拟机也提供了类似的框架。

    使用Instrumentation类计算Java对象大小的过程如下:

    创建一个含有premain()方法的Java 类。

    package sizeof;
    
    import java.lang.instrument.Instrumentation;
    import java.lang.reflect.Array;
    import java.lang.reflect.Field;
    import java.lang.reflect.Modifier;
    import java.util.IdentityHashMap;
    import java.util.Map;
    import java.util.Stack;
    
    public class DeepObjectSizeOf {
    	
    	private static Instrumentation inst;
    
    	public static void premain(String agentArgs, Instrumentation instP) {
    		inst = instP;
    	}
    
    	public static long sizeOf(Object object) {
    		//计算当前对象的内存大小,不包含引用对象
    		return inst.getObjectSize(object);
    	}
    	
    	public static long deepSizeOf(Object obj) {//深入检索对象,并计算大小
    	       Map<Object, Object> visited = new IdentityHashMap<Object, Object>();
    	       Stack<Object> stack = new Stack<Object>();
    	       long result = internalSizeOf(obj, stack, visited);
    	       while (!stack.isEmpty()) {//通过栈进行遍历
    	          result += internalSizeOf(stack.pop(), stack, visited);
    	       }
    	       visited.clear();
    	       return result;
    	    }
    
    	    private static boolean needSkipObject(Object obj, Map<Object, Object> visited) {
    	       if (obj instanceof String) {
    	          if (obj == ((String) obj).intern()) {
    	             return true;
    	          }
    	       }
    	       return (obj == null) || visited.containsKey(obj);
    	    }
    
    	    private static long internalSizeOf(Object obj, Stack<Object> stack, Map<Object, Object> visited) {
    	       if (needSkipObject(obj, visited)) {
    	           return 0;
    	       }
    	       visited.put(obj, null);//将当前对象放入栈中
    	       long result = 0;
    	       result += sizeOf(obj);
    	       Class <?>clazz = obj.getClass();
    	       if (clazz.isArray()) {//如果数组
    	           if(clazz.getName().length() != 2) {//如果primitive type array,Class的name为2位
    	              int length =  Array.getLength(obj);
    	              for (int i = 0; i < length; i++) {
    	                 stack.add(Array.get(obj, i));
    	              }
    	           }
    	           return result;
    	       }
    	       return getNodeSize(clazz , result , obj , stack);
    	   }
    
    	   //这个方法获取非数组对象自身的大小,并且可以向父类进行向上搜索
    	   private static long getNodeSize(Class <?>clazz , long result , Object obj , Stack<Object> stack) {
    	      while (clazz != null) {
    	          Field[] fields = clazz.getDeclaredFields();
    	          for (Field field : fields) {
    	              if (!Modifier.isStatic(field.getModifiers())) {//这里抛开静态属性
    	                   if (field.getType().isPrimitive()) {//这里抛开基本关键字(因为基本关键字在调用java默认提供的方法就已经计算过了)
    	                       continue;
    	                   }else {
    	                       field.setAccessible(true);
    	                      try {
    	                           Object objectToAdd = field.get(obj);
    	                           if (objectToAdd != null) {
    	                                  stack.add(objectToAdd);//将对象放入栈中,一遍弹出后继续检索
    	                           }
    	                       } catch (IllegalAccessException ex) {
    	                           assert false;
    	                  }
    	              }
    	          }
    	      }
    	      clazz = clazz.getSuperclass();//找父类class,直到没有父类
    	   }
    	   return result;
    	  }
    }
    

    JVM会在应用程序运行之前调用这个Java 类的premain()方法(也就是在执行应用程序的main方法之前),JVM会在调用该方法时传入一个实现Instrumentation接口的实例,通过调用此接口实例的getObjectSize()方法可以计算出对象的大小(只计算当前对象的大小,不会进一步计算内部引用对象的大小)。 

    将创建好的Java类打成一个jar包

    在打包之前先创建一个MANIFEST.txt文件作为这个jar包的清单文件,其内容如下: 

    Manifest-Version: 1.0
    Premain-Class: sizeof.DeepObjectSizeOf

    按照Java类文件的包路径创建好目录(DeepObjectSizeOf.class文件放在sizeof文件夹中)。

    使用用如下命令创建jar包:

    jar -cmf MANIFEST.txt java_sizeof.jar sizeof/*

    修改JVM启动配置

    修改Eclipse IDE的JVM启动配置,增加-javaagent启动参数:

    -javaagent:jar文件路径

    我创建的 java_sizeof.jar放在D:\sizeof目录下,设置参数如下。

    测试样例

    创建一个测试类SizeOfMain.java,代码如下。

    package sizeof;
    
    public class SizeOfMain {
    	
    	public static void main(String[] args) {
    		System.out.println("new Integer(1) 对象大小:"
    				+ DeepObjectSizeOf.deepSizeOf(new Integer(1)));
    		System.out.println("new String(\"sizeof\") 对象大小:"
    				+ DeepObjectSizeOf.deepSizeOf(new String("sizeof")));
    	}
    }
    

    在64位机器上(不开启指针压缩):

    设置参数:-javaagent:d:\sizeof/java_sizeof.jar -XX:-UseCompressedOops

    执行结果:

    在64位机器上(开启指针压缩):

    设置参数:-javaagent:d:\sizeof/java_sizeof.jar -XX:+UseCompressedOops

    执行结果:

    展开全文
  • java中对象内存中的结构

    万次阅读 2020-09-02 17:25:09
    对象的结构 在JVM中,一般来说,Java对象都是分配在堆中,那么对象在堆中长什么样呢? 对象头包含以下几个部分: MarkWord:包含对象的线程锁状态,另外还...对象属性:占用内存空间取决于对象属性数量和类型。

    对象的结构

    在JVM中,一般来说,Java对象都是分配在堆中,那么对象在堆中长什么样呢?

    在这里插入图片描述

    对象头包含以下几个部分:

    • MarkWord:包含对象的线程锁状态,另外还可以用来配合GC、存放该对象的hashCode、分代年龄等。
    • Class Pointer:一个指向方法区中Class信息的指针,意味着该对象可随时知道自己是哪个Class的实例,默认开启压缩指针,占32位,关闭压缩,占64位。
    • 数组的长度:可选的,只有当对象是一个数组对象时才会有这个部分。
    • 对象的属性:占用内存空间取决于对象的属性数量和类型。
    • 对齐:为了保证对象头的字节数是8的倍数。

    在这里插入图片描述

    MarkWord

    MarkWord用来存储线程的锁状态、hashcode、分代年龄等信息,当对象处于不同状态时,MarkWord中的数据也跟随着对象的状态而变化。

    以上是Java对象处于5种不同状态时,Mark Word中64个bit的表现形式,上面每一行代表对象处于某种状态时的样子。

    注意以下几点:

    • 偏向锁标志位+锁标志位共同表示当前对象锁的状态。
    • 分代年龄:在GC中,如果对象在Survivor区复制一次,年龄增加1。当对象达到设定的阈值时,将会晋升到老年代。默认情况下,并行GC(除了CMS垃圾收集器)的年龄阈值为15,并发GC(CMS垃圾收集器)的年龄阈值为6。由于分代年龄只有4位,所以最大值为15,这就是-XX:MaxTenuringThreshold参数最大值为15的原因。
    • hashCode:31位的对象标识hashCode,采用延迟计算方式,只有在使用时才会计算,并会将结果写到该对象头中。如果对象在加锁前计算了hashcode,此时状态为无锁不可偏向,因为hashcode占用了偏向锁标志位的数据区域,这里的hashcode是指系统中最初始的hashcode,也就是调用基类Object.hashCode()方法产生的,而不是重写Object.hashCode()方法产生的hashcode,也可以使用System.identityHashCode(object)方法来获取。

    项目中可以引入JOL(Java Object Layout)来打印java对象头在内存中的字节码。

    		<dependency>
    			<groupId>org.openjdk.jol</groupId>
    			<artifactId>jol-core</artifactId>
    			<version>0.10</version>
    		</dependency>
    

    下面通过代码对上面的5中状态进行验证:

    无锁不可偏向

    JVM刚启动时4s(默认值为4s,可以通过JVM参数-XX:BiasedLockingStartupDelay修改)之内创建的对象的锁状态为无锁不可偏向。

        @Test
        public void testNoLockByBeforeFiveSecond() {
            Object object = new Object();
            System.out.println(ClassLayout.parseInstance(object).toPrintable());
        }
    

    运行结果如下:

    java.lang.Object object internals:
     OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
          0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
          4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
          8     4        (object header)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
         12     4        (loss due to the next object alignment)
    Instance size: 16 bytes
    Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
    

    当一个对象计算了hashCode,锁的状态就会变成无锁不可偏向,因为hashcode占用了偏向锁标志位的数据区域。

        @Test
        public void testNoLockByHashCode() throws InterruptedException {
            TimeUnit.SECONDS.sleep(5); // 参数是4s,这里用休眠5s
            Object object = new Object();
            System.out.println(Integer.toHexString(object.hashCode()));
            System.out.println(ClassLayout.parseInstance(object).toPrintable());
        }
    

    运行结果如下:

    5e8c92f4
    java.lang.Object object internals:
     OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
          0     4        (object header)                           01 f4 92 8c (00000001 11110100 10010010 10001100) (-1936526335)
          4     4        (object header)                           5e 00 00 00 (01011110 00000000 00000000 00000000) (94)
          8     4        (object header)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
         12     4        (loss due to the next object alignment)
    Instance size: 16 bytes
    Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
    

    可以发现对象的hashcode为5e8c92f4,与对象头中的hashcode一致。

    匿名偏向锁

    JVM启动4s后创建的对象锁的状态为匿名偏向锁。

        public void testAnonymousBiasLock() throws InterruptedException {
            TimeUnit.SECONDS.sleep(5); // 或者设置JVM args:-XX:BiasedLockingStartupDelay=0
            Object object = new Object();
            System.out.println(ClassLayout.parseInstance(object).toPrintable());
        }
    

    运行结果如下:

    java.lang.Object object internals:
     OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
          0     4        (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)
          4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
          8     4        (object header)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
         12     4        (loss due to the next object alignment)
    Instance size: 16 bytes
    Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
    

    偏向锁

    当没有发生资源的竞争,锁会偏向第一个持有锁的线程。

        @Test
        public void testBiasLock() throws InterruptedException {
            TimeUnit.SECONDS.sleep(5);
            Object object = new Object();
            synchronized (object) {
                System.out.println(ClassLayout.parseInstance(object).toPrintable());
            }
        }
    

    运行结果如下:

    java.lang.Object object internals:
     OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
          0     4        (object header)                           05 98 4e 00 (00000101 10011000 01001110 00000000) (5150725)
          4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
          8     4        (object header)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
         12     4        (loss due to the next object alignment)
    Instance size: 16 bytes
    Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
    

    注意此时对象头中会存有一个线程ID,但是此ID不是java中thread对象getId()方法返回的id,也不是操作系统中ID,这个ID是JVM生成的,可以通过jstack命令查看线程的ID。

    $ jstack 16344
    ....
    "main" #1 prio=5 os_prio=0 tid=0x0000000002239800 nid=0x4c18 in Object.wait() [0x000000000280e000]
       java.lang.Thread.State: WAITING (on object monitor)
            at java.lang.Object.wait(Native Method)
            - waiting on <0x000000076b605d30> (a java.lang.Thread)
            at java.lang.Thread.join(Thread.java:1252)
            - locked <0x000000076b605d30> (a java.lang.Thread)
            at java.lang.Thread.join(Thread.java:1326)
            at com.morris.concurrent.syn.upgrade.SynchronizedStatus.testBiasLock(SynchronizedStatus.java:74)
    ....
    

    tid对应对象头中的线程id,而nid对应操作系统级别的线程id。

    轻量级锁

    当对象头中的锁偏向于线程T1,T1释放锁后,其他线程来获取,此时偏向锁会升级为轻量级锁(线程之间交替执行,没有资源竞争)。

        /**
         * 偏向锁 -> 轻量级锁
         */
        @Test
        public void testLightLock() throws InterruptedException {
            TimeUnit.SECONDS.sleep(5);
            Object object = new Object();
    
            Thread t = new Thread(() -> {
                synchronized (object) {
                }
            });
            t.start();
            t.join();
    
            synchronized (object) {
                System.out.println(ClassLayout.parseInstance(object).toPrintable());
            }
    
            System.out.println(ClassLayout.parseInstance(object).toPrintable());
        }
    

    运行结果如下:

    java.lang.Object object internals:
     OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
          0     4        (object header)                           f0 e0 7c 02 (11110000 11100000 01111100 00000010) (41738480)
          4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
          8     4        (object header)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
         12     4        (loss due to the next object alignment)
    Instance size: 16 bytes
    Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
    
    java.lang.Object object internals:
     OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
          0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
          4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
          8     4        (object header)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
         12     4        (loss due to the next object alignment)
    Instance size: 16 bytes
    Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
    

    当JVM刚启动或者使用JVM参数-XX:-UseBiasedLocking=false禁用了偏向锁,这时无锁不可偏向锁就会升级为轻量级锁。

        @Test
        public void testLightLock2() throws InterruptedException {
            Object object = new Object();
            synchronized (object) {
                System.out.println(ClassLayout.parseInstance(object).toPrintable());
            }
        }
    

    运行结果如下:

    java.lang.Object object internals:
     OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
          0     4        (object header)                           88 e5 7a 02 (10001000 11100101 01111010 00000010) (41608584)
          4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
          8     4        (object header)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
         12     4        (loss due to the next object alignment)
    Instance size: 16 bytes
    Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
    

    注意轻量级锁释放后,对象头中锁的状态会变为无锁不可偏向。

    重量级锁

    当产生了资源竞争,轻量级锁会升级为重量级锁。

        @Test
        public void testHeavyLock() throws InterruptedException {
            Object object = new Object();
    
            Thread t = new Thread(() -> {
                synchronized (object) {
                }
            });
            t.start();
    
            synchronized (object) {
                TimeUnit.SECONDS.sleep(1);
                System.out.println(ClassLayout.parseInstance(object).toPrintable());
            }
        }
    

    运行结果如下:

    java.lang.Object object internals:
     OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
          0     4        (object header)                           fa c1 ec 1b (11111010 11000001 11101100 00011011) (468500986)
          4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
          8     4        (object header)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
         12     4        (loss due to the next object alignment)
    Instance size: 16 bytes
    Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
    

    Class Pointer

    JVM通过这个指针确定对象是哪个类的实例,该指针的长度为JVM的一个字大小,即32位的JVM为32位,64位的JVM为64位。

    如果应用的对象过多,使用64位的指针将浪费大量内存,统计而言,64位的JVM将会比32位的JVM多耗费50%的内存。为了节约内存可以使用选项+UseCompressedOops开启指针压缩,默认开启,其中,oop即ordinary object pointer普通对象指针。开启该选项后,下列指针将压缩至32位:

    • 每个Class的属性指针(即静态变量)
    • 每个对象的属性指针(即对象变量)
    • 普通对象数组的每个元素指针

    当然,也不是所有的指针都会压缩,一些特殊类型的指针JVM不会优化,比如JDK8中指向元空间的Class对象指针、本地变量、堆栈元素、入参、返回值和NULL指针等。

    问题:Object object = new Object()中object对象到底占用多少个字节?

    答案:16个字节。

    开启压缩指针:markword(64bit) + classpointer(32bit) + 对齐(32bit) = 16byte 。

    不开启压缩指针:markword(64bit) + classpointer(64bit) = 16byte 。

    展开全文
  • Java对象内存布局

    千次阅读 2020-06-19 11:20:31
    Java对象内存布局 (一)简述 曾经有这样一道面试题,问:Object ob = new Object()中的ob占几个字节。想回答这个题目就必须要知道Java对象内存布局问题。 对象布局研究的问题的实质就是看看java的对象内存中...

    Java对象的内存布局

    (一)简述

    曾经有这样一道面试题,问:Object ob = new Object()中的ob占几个字节。想回答这个题目就必须要知道Java对象的内存布局问题。

    对象布局研究的问题的实质就是看看java的对象在内存中是如何储存的,应该遵循什么样的格式。首先我们都知道,堆中存储的对象包括普通对象和数组对象。而每个对象在内存中都由三个部分组成,分别为对象头、实例数据和对齐填充。

    在这里插入图片描述
    其中,普通对象的对象头中有markword和classPointer两个部分,而数组对象比普通对象多了一个数组长度部分。下面我们来仔细看看每个部分所占的大小和他们的功能。

    (二)对象头

    markword中包含一系列的标记位,比如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等等。在32位系统占4字节,在64位系统中占8字节。

    Class Pointer用来指向对象对应的Class对象(其对应的元数据对象)的内存地址。在32位系统占4字节,在64位系统中占8字节(指针压缩后为4字节,后面会细讲)。

    Length指数组长度。如果是数组对象,会有一个保存数组长度的空间,占4个字节,这个很简单没啥好讲的。

    现在我们大概了解了对象头。但是,对象头是个非常重要也是非常复杂的东西,仅仅了解是完全不够的,我们还需要知道很多的细节。

    1. Markword

    Markword是对象头最重要的一个部分,也是面试问的最多的部分,这里面涉及GC、锁升级等等细节内容,下面我们看看Markword的8字节——64bit里面究竟有啥:

    |------------------------------------------------------------------------------|--------------------|
    |                                  Mark Word (64 bits)                         |         状态       |
    |------------------------------------------------------------------------------|--------------------|
    | unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:1 | lock:2 |       一般状态      |
    |------------------------------------------------------------------------------|--------------------|
    | thread:54 |       epoch:2        | unused:1 | age:4 | biased_lock:1 | lock:2 |        自旋锁       |
    |------------------------------------------------------------------------------|--------------------|
    |                       ptr_to_lock_record:62                         | lock:2 |        轻量级锁     |
    |------------------------------------------------------------------------------|--------------------|
    |                     ptr_to_heavyweight_monitor:62                   | lock:2 |        重量级锁     |
    |------------------------------------------------------------------------------|--------------------|
    |                                                                     | lock:2 |       GC回收标记    |
    |------------------------------------------------------------------------------|--------------------|
    

    下面来简单介绍一下里面的一些细节的含义:

    1. lock:2位的锁状态标记位,由于希望用尽可能少的二进制位表示尽可能多的信息,所以设置了lock标记。该标记的值不同,整个Markword表示的含义不同。
    2. biased_lock:对象是否启用偏向锁标记,只占1个二进制位。为1时表示对象启用偏向锁,为0时表示对象没有偏向锁。
    3. age:4位的Java对象年龄。在GC中,如果对象在Survivor区复制一次,年龄增加1。当对象达到设定的阈值时,将会晋升到老年代。默认情况下,并行GC的年龄阈值为15,并发GC的年龄阈值为6。由于age只有4位,所以最大值为15,这就是-XX:MaxTenuringThreshold选项最大值为15的原因。
    4. identity_hashcode:25位的对象标识Hash码,采用延迟加载技术。调用方法System.identityHashCode()计算,并会将结果写到该对象头中。当对象被锁定时,该值会移动到管程Monitor中。
    5. thread:持有偏向锁的线程ID。
    6. epoch:偏向时间戳。
    7. ptr_to_lock_record:指向栈中锁记录的指针。
    8. ptr_to_heavyweight_monitor:指向管程Monitor的指针。

    当对象被synchronized关键字当成同步锁时,围绕这个锁的一系列操作都和Markword有关。JDK1.6以后的版本在处理同步锁时存在锁升级的概念,JVM对于同步锁的处理是从偏向锁开始的,随着竞争越来越激烈,处理方式从偏向锁升级到轻量级锁,最终升级到重量级锁。锁升级的过程比较复杂,我会在我的《java多线程和高并发》的专题中讲,这里就大概的写一点过程(看不明白可以先跳过):

    1. 当没有加锁时,这是一个普通的对象,Markword记录对象的HashCode,锁标志位是01,是否偏向锁那一位是0。
    2. 当对象被当做同步锁并有一个线程A抢到了锁时,锁标志位还是01,但是否偏向锁那一位改成1,前23bit记录抢到锁的线程id,表示进入偏向锁状态。
    3. 当线程A再次试图来获得锁时,JVM发现同步锁对象的标志位是01,是否偏向锁是1,也就是偏向状态,Markword中记录的线程id就是线程A自己的id,表示线程A已经获得了这个偏向锁,可以执行同步锁的代码。
    4. 当线程B试图获得这个锁时,JVM发现同步锁处于偏向状态,但是Markword中的线程id记录的不是B,那么线程B会先用CAS操作试图获得锁,这里的获得锁操作是很有可能失败的,因为线程A一般不会自动释放偏向锁。如果抢锁成功,就把Markword里的线程id改为线程B的id,代表线程B获得了这个偏向锁,可以执行同步锁代码。如果抢锁失败,则继续执行步骤5。
    5. 偏向锁状态抢锁失败,代表当前锁有一定的竞争,偏向锁将升级为轻量级锁(自旋锁)。JVM会在当前线程的线程栈中开辟一块单独的空间,里面保存指向对象锁Markword的指针,同时在对象锁Markword中保存指向这片空间的指针。上述两个保存操作都是CAS操作,如果保存成功,代表线程抢到了同步锁,就把Markword中的锁标志位改成00,可以执行同步锁代码。如果保存失败,表示抢锁失败,竞争太激烈,继续执行步骤6。
    6. 轻量级锁抢锁失败,JVM会使用自旋锁,自旋锁不是一个锁状态,只是代表不断的重试,尝试抢锁。从JDK1.7开始,自旋锁默认启用,自旋次数由JVM决定。如果抢锁成功则执行同步锁代码,如果失败则继续执行步骤7。
    7. 自旋锁重试之后如果抢锁依然失败,同步锁会升级至重量级锁,锁标志位改为10。在这个状态下,未抢到锁的线程都会被阻塞。

    下面放一个图来更直观的看一下Markword的结构:

    在这里插入图片描述

    2. Class Pointer

    这一部分用于存储对象的类型指针,该指针指向它的类元数据,JVM通过这个指针确定对象是哪个类的实例。该指针的位长度为JVM的一个字大小,即32位的JVM为32位,64位的JVM为64位。如果应用的对象过多,使用64位的指针将浪费大量内存。为了节约内存,JVM默认开启了选项+UseCompressedOops(开启指针压缩)。其中,oop即ordinary object pointer,翻译为普通对象指针。开启该选项后,64位的JVM中的普通对象指针将压缩至32位。

    会被压缩的指针:

    1. 每个Class的属性指针(静态成员变量)
    2. 每个对象的属性指针
    3. 普通对象数组的每个元素指针

    PS:指向PermGen的Class对象指针,本地变量,堆栈元素,入参,返回值,NULL指针等等不会被压缩。

    指针是如何压缩的?Java虚拟机中32位的指针可以寻址到2的32次方个地址空间,也就是32GB的内存空间(超过32GB会关闭压缩指针,换句话说就是32位指针无法映射到大于32GB的内存上)。在对压缩指针解引用时,我们需要将其左移3位,再加上一个固定的偏移量,便可以成为寻址到32GB地址空间伪64位指针了。

    3. Length

    数组对象特有的一块空间,占四个字节,保存了数组的长度。四个字节一共32位,去掉一个符号位,一共31位,最大值为2的31次方,可以说是很大的一个空间了。当然,这并不是数组长度的唯一限制因素,一些其他因素可能会导致数组最大长度不足2的31次方,最简单的例子就是计算机的可用内存不足,无法分配这么多空间。

    (二)Instance data

    实例数据是对象真正存储的有效信息: 代码中定义的各种类型的字段内容(包含父类继承的和子类定义的,都需要记录下来)。
    这部分的存储顺序受到虚拟机分配策略(FieldsAllocationStyle)和字段在源码中的顺序的影响。
    HostSpot 的默认分配策略为:

    longs/doubles
    ints
    shorts/chars
    bytes/booleans

    可以看出:相同宽度的字段总是被分在一起。另外,父类中定义的变量会出现在子类的变量之前。如果CompactFields参数值为true(默认为true),那么子类之中较窄的变量也可能会插入父类变量的空隙之中。

    (三)对齐填充

    第三部分对齐填充并不是必然存在的,也没有特别的含义,它仅仅起着占位符的作用。由于HotSpot VM的自动内存管理系统要求对象起始地址必须是8字节的整数倍,换句话说就是对象的大小必须是8字节的整数倍。对象头正好是8字节的倍数,因此当对象实例数据部分没有对齐的话,就需要通过对齐填充来补全。

    计算机中内存大小的基本单位是字节(byte),理论上来讲,可以从任意地址访问某种基本数据类型,但是实际上,计算机并非逐字节大小读写内存,而是以2,4或8的倍数的字节块来读写内存,如此以来就会对基本数据类型的合法地址作出一些限制,即它的地址必须是2,4或8的倍数。那么就要求各种数据类型按照一定的规则在空间上排列,这就是对齐。

    那为什么java中的对齐填充一定要使对象的大小是8字节的整数倍呢?因为,64位操作系统在读取内存时,是一次性读8个字节(一个字节是8位,64位就是八个字节),这样的8个字节称为一个块。比如,我们有一个对象大小是9个字节,如果我们不进行对齐填充,那么很显然他会跨两个块,一个块已经装满,但另一个块会剩下7字节。如果未来我们又有一个8字节的对象,那么因为这个7字节的块放不下,所以他也会跨两个块。然后,未来我们要读取时,就需要读取两次内存才能将这个对象拿到,这是非常影响效率的。

    那有了对齐填充会怎么样呢?首先,那个之前9个字节对象将被补齐为16字节,因为一个9字节的对象是必然要读取两次内存的,所以补齐完全不会影响效率。下一个8字节的对象就会处于一个块中,这样只要读取一次内存就能拿到这个对象。相比上面的两次读取,这样的效率几乎高了一倍。其实,对齐填充很像一个整理碎片的过程,仔细想想还挺像的。

    2020年6月19日

    展开全文
  • C++对象内存模型

    千次阅读 2016-11-21 14:37:07
    谈VC++对象模型 (美)简.格雷 程化 译 译者前言 一个C++程序员,想要进一步提升技术水平的话,应该多了解一些语言的语意细 节。对于使用VC++的程序员来说,还应该了解一些VC++对于C++的诠释。 Inside the ...
  • 认识JVM--第二篇-java对象内存模型

    千次阅读 2011-07-03 23:57:14
    前一段写了一篇《认识JVM》,不过在一些方面可以继续阐述的,在这里继续探讨一下,本文重点在于在heap区域内部...3、一个对象放在内存中的是如何存放的 4、调用的指令分析 5、对象宽度对其问题及空间浪费 6、指令
  • 创建对象必然要在虚拟机中分配内存,虚拟机提供了两种策略:指正碰撞和空闲列表 指针碰撞法 很容易理解,在内存中,一遍是已经被分配的空间,一遍的未分配的空间,如果新建了8字节对象,那么指针就往未分配空间...
  • .Net 垃圾回收和大对象处理

    千次阅读 2015-03-15 21:28:41
    英文原文:Maoni Stephens,编译:赵玉开...比如内存碎片整理 —— 在内存中移动大对象的成本是昂贵的,让我们研究一下垃圾回收器是如何处理大对象的,大对象对程序性能有哪些潜在的影响。 对象堆和垃圾回收 在.Net 1.
  • Recordset记录集对象属性

    千次阅读 2012-01-28 00:38:54
    因为删除、更新、添加操作不需要返回记录集,因此可以直接使用连接对象或是命令对象的Exexut方法,但是利用记录集对象有时会更简单,此外,通过记录集对象能够实现比较复杂的数据库管理任务,比如要采用分页显示记录...
  • 对List对象列表属性值的快速搜索

    千次阅读 2018-07-30 09:27:17
    对List对象列表属性值的快速搜索 对于数据的搜索已有很多成熟的方案,比如Apace Lucene框架,结合ikanalyer等分词器能实现很复杂和高效的搜索,或直接使用sql语言对数据库关键字进行搜索等。 但这些搜索都很重,...
  • 深入理解Java虚拟机-Java内存区域与内存溢出异常

    万次阅读 多人点赞 2020-01-03 21:42:24
    文章目录概述运行时数据区域程序计数器(线程私有)Java虚拟机栈(线程私有)局部变量表操作数栈动态链接方法返回地址小结本地方法栈(线程私有)Java堆(全局共享)方法区(全局共享)运行时常量池直接内存HotSpot...
  • Recordset记录集对象属性(一)

    千次阅读 2014-03-14 21:34:44
    因为删除、更新、添加操作不需要返回记录集,因此可以直接使用连接对象或是命令对象的Exexut方法,但是利用记录集对象有时会更简单,此外,通过记录集对象能够实现比较复杂的数据库管理任务,比如要采用分页显示记录...
  • 转自... 随着技术的发展,智能手机硬件配置越来越高,可是它和现在的PC相比,其运算能力,续航能力,存储空间等都还是受到很的限制,同时用户对手机的体验要 求远远高于PC的桌面应用程序。
  • 为什么写这篇文章? 其实一般的程序猿根本不用了解...等等等等,一句话,当你想深入了解java对象内存中,如何存储,或者每个对象占用多空间时,你会感谢这篇文章 本文主要分析jvm中的情况,实验环境为64位wind...
  • 内存抖动出现原因主要是频繁(很重要)在循环里创建对象(导致大量对象在短时间内被创建,由于新对象是要占用内存空间的而且是频繁,如果一次或者两次在循环里创建对象内存影响不大,不会造成严重内存抖动这样可以...
  • Redis内存优化

    千次阅读 2020-04-16 14:50:06
    使用maxmemory参数限制最大可用内存,当超出内存上限maxmemory时使用LRU等删除策略释放空间以及防止所用内存超过服务器物理内存。 2.配置内存回收策略 Redis所用内存达到maxmemory上限时会触发相应的溢出控制策略...
  • 目录前言1、JVN内存结构1、对象分配规则3、解释内存中的栈(stack)、堆(heap)和静态区(static area)的用法4、Perm Space中保存什么数据?会引起OutOfMemory吗?5、什么是类的加载6、如何⾃定义⼀个类加载器?你使⽤过...
  • 比如,user类和student类,在数据库映射的表不同,类内部的属性不同,因此它们需要不同的SQL查询语句和不同属性的seter、geter方法来将查询的数据封装到对象中。对象类少还不说,要是数量过多,就需要大量的查询类...
  • C# 垃圾回收中的大对象

    千次阅读 2015-12-14 17:59:58
    CLR垃圾回收器根据所占...比如内存碎片整理 ------ 在内存中移动大对象的成本是昂贵的,让我们研究一下垃圾回收器是如何处理大对象的,大对象对程序性能有哪些潜在的影响。 大对象堆和垃圾回收 在.Net 1.0和2
  • Android内存优化总结

    万次阅读 2018-11-14 15:57:44
    内存简介: RAM(random access memory)随机存取存储器。说白了就是内存。 一般Java在内存分配时会涉及到以下区域: ...寄存器(Registers):速度最快的存储...堆(Heap):堆内存用来存放由new创建的对象和...
  • lua 面向对象

    千次阅读 2019-05-18 23:42:39
    对象属性和方法封装在闭包里,创建对象时只需复制属性和方法就行了,缺点也显而易见,这是一种伪共享机制,创建对象时要复制类中所有属性和方法,而不是引用,这就造成用这种方法创建过多对象会浪费内存 local function...
  • JVM内存结构 JVM内存分为线程私有区和线程共享区 线程私有区 1、程序计数器 ✓(记录当前线程执⾏到哪⼀条字节码指令...✓(线程执⾏⽅法的时候内部存局部变量会存堆中对象的地址等等数据) 线程私有的,与线程在同
  • GC(Garbage Collection),垃圾回收机制,简单地说就是程序中及时处理废弃不用了的内存对象的机制,防止内存中废弃对象堆积过多造成内存泄漏。Objective-C语言本身是支持垃圾回收机制的,但有平台局限性,仅限于Mac...
  • ANDROID内存优化(汇总——中)

    万次阅读 多人点赞 2014-08-29 00:08:25
    ANDROID内存优化汇总,从各个方面去介绍如何减少内存开销和重用资源
  • Android 如何获取App内存大小

    万次阅读 2015-05-08 16:25:15
    Android获取内存使用的方式有很多种,但是各个方式查看到的结果可能会有微略不同 方式一:使用ActivityManager的getMemoryInfo(ActivityManager.MemoryInfo outInfo) ActivityManager.getMemoryInfo()主要是用于...
  • 一、内存泄露垃圾回收器无法回收原本应该被回收的对象,这个对象就引发了内存泄露。内存泄露的危害: (1)过多内存泄露最终会导致内存溢出(OOM)(2)内存泄露导致可用内存不足,会触发频繁GC,不管是Android...
  • Android内存优化汇总

    千次阅读 2017-09-05 22:07:47
    所以我将本文定义为一个工具类的文章,如果你在ANDROID开发中遇到关于内存问题,或者马上要参加面试,或者就是单纯的学习或复习一下内存相关知识,都欢迎阅读。(本文最后我会尽量列出所参考的文章)。 内存...
  • App内存占用优化

    千次阅读 2017-01-12 18:47:04
    RAM(Random-access memory)在任何软件开发中都是非常宝贵的资源,移动操作系统由于其物理内存的局限性更是如此。尽管ART(Android Runtime)与Dalvik虚拟机会执行常规的垃圾回收,但这并不意味着可以忽略App中的...
  • 继承 在JavaScript中的继承的...继承是面向对象的基础,是代码重用的一种重要机制。 继承的作用 实现继承的主要作用是: ① 子类实例可以共享超类属性和方法。 ② 子类可以覆盖和扩展超类属性和方法。 继承的

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 81,445
精华内容 32,578
关键字:

对象属性过多内存大